kern-orm 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{expression-tYmWRuKR.d.mts → index-CNqYlONX.d.mts} +513 -170
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +608 -130
- package/dist/testing.d.mts +1 -1
- package/dist/{text-4Xs09pIf.mjs → text-BUNl8wpl.mjs} +31 -4
- package/dist/types.d.mts +55 -56
- package/dist/types.mjs +1 -1
- package/package.json +2 -1
- package/dist/index-pj2AhTRY.d.mts +0 -134
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as createList, C as isType, D as isEqualityCompatible, F as oneNode, I as trueNode, M as createSelectNode, N as falseNode, O as valuesQueryNode, P as noFilters, S as defineType, T as equalityOperators, _ as toBooleanExpression, a as stringifyJson, b as toValueNode, c as textOperators, d as compareNodes, f as createIsNullNode, g as operandNode, h as isOperandSource, j as createNode, k as defineGenericOperator, l as orderingOperators, m as isExpression, n as text, p as getType, s as encodeBytea, u as compare, v as toExpression, w as Column, x as getBooleanType, y as toNode } from "./text-BUNl8wpl.mjs";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
3
|
// src/ast/boolean.ts
|
|
4
4
|
/** Combines predicate nodes with `AND` or `OR`, flattening nested groups of the same kind. */
|
|
@@ -33,9 +33,10 @@ function combineFilters(filters) {
|
|
|
33
33
|
return combineNodes("and", operands);
|
|
34
34
|
}
|
|
35
35
|
// src/expression/predicates.ts
|
|
36
|
-
/** Converts a condition operand into a node
|
|
36
|
+
/** Converts a condition operand into a node: a query nests in it, and an object of column values stays unresolved until `where`. */
|
|
37
37
|
function toConditionNode(operand) {
|
|
38
|
-
|
|
38
|
+
if (isOperandSource(operand)) return operand[operandNode]();
|
|
39
|
+
return createNode({
|
|
39
40
|
kind: "values",
|
|
40
41
|
values: operand
|
|
41
42
|
});
|
|
@@ -471,9 +472,11 @@ function needsParentheses(kind, position) {
|
|
|
471
472
|
case "param":
|
|
472
473
|
case "boolean":
|
|
473
474
|
case "values":
|
|
475
|
+
case "nestedQuery":
|
|
474
476
|
case "tableName":
|
|
475
477
|
case "subquery":
|
|
476
478
|
case "exists":
|
|
479
|
+
case "textSubquery":
|
|
477
480
|
case "aggregate": return false;
|
|
478
481
|
case "cast": return position === "interpolation";
|
|
479
482
|
case "raw": return position !== "interpolation";
|
|
@@ -482,6 +485,7 @@ function needsParentheses(kind, position) {
|
|
|
482
485
|
case "not":
|
|
483
486
|
case "comparison":
|
|
484
487
|
case "in":
|
|
488
|
+
case "inQuery":
|
|
485
489
|
case "isNull": return position !== "condition";
|
|
486
490
|
default: return kind;
|
|
487
491
|
}
|
|
@@ -709,12 +713,18 @@ function compileExpression(context, scope, node) {
|
|
|
709
713
|
case "or": return node.operands.map((operand) => compileOperand(context, scope, operand, "condition")).join(node.kind === "and" ? " AND " : " OR ");
|
|
710
714
|
case "not": return `NOT ${compileOperand(context, scope, node.operand, "condition")}`;
|
|
711
715
|
case "values": throw new Error("An object condition can only be used in `where`");
|
|
716
|
+
case "nestedQuery": throw new Error("A query in an expression must be lowered before compilation");
|
|
712
717
|
case "comparison": return `${compileOperand(context, scope, node.left, "value")} ${node.operator} ${compileOperand(context, scope, node.right, "value")}`;
|
|
713
718
|
case "in": {
|
|
714
719
|
let text = `${compileOperand(context, scope, node.operand, "value")} IN (${compileOperand(context, scope, node.list[0], "value")}`;
|
|
715
720
|
for (let index = 1; index < node.list.length; index++) text += `, ${compileOperand(context, scope, node.list[index], "value")}`;
|
|
716
721
|
return `${text})`;
|
|
717
722
|
}
|
|
723
|
+
case "inQuery": {
|
|
724
|
+
const { query } = node;
|
|
725
|
+
if (query.kind === "nestedQuery") throw new Error("A query in an expression must be lowered before compilation");
|
|
726
|
+
return `${compileOperand(context, scope, node.operand, "value")} IN ${compileSubquery(context, scope, query, "A subquery of IN")}`;
|
|
727
|
+
}
|
|
718
728
|
case "isNull": return `${compileOperand(context, scope, node.operand, "value")} IS NULL`;
|
|
719
729
|
case "raw": {
|
|
720
730
|
let text = node.fragments[0];
|
|
@@ -722,12 +732,9 @@ function compileExpression(context, scope, node) {
|
|
|
722
732
|
return text;
|
|
723
733
|
}
|
|
724
734
|
case "tableName": return compileInterpolatedTable(context, node);
|
|
725
|
-
case "subquery":
|
|
726
|
-
const { length } = node.query.projections;
|
|
727
|
-
if (length !== 1) throw new Error(`A scalar subquery must select one field, not ${length}`);
|
|
728
|
-
return `(${compileQuery(context, scope, node.query)})`;
|
|
729
|
-
}
|
|
735
|
+
case "subquery": return compileSubquery(context, scope, node.query, "A scalar subquery");
|
|
730
736
|
case "exists": return `EXISTS (${compileQuery(context, scope, node.query)})`;
|
|
737
|
+
case "textSubquery": return `(${compileQuery(context, scope, node.query)})`;
|
|
731
738
|
case "aggregate": {
|
|
732
739
|
let text = `${node.name}(${node.distinct ? "DISTINCT " : ""}`;
|
|
733
740
|
for (let index = 0; index < node.arguments.length; index++) text += `${index === 0 ? "" : ", "}${compileExpression(context, scope, node.arguments[index])}`;
|
|
@@ -737,6 +744,12 @@ function compileExpression(context, scope, node) {
|
|
|
737
744
|
default: return node;
|
|
738
745
|
}
|
|
739
746
|
}
|
|
747
|
+
/** Compiles `query` in parentheses as a subquery of one field, and rejects a query of another number of fields, naming the subquery as `subject`. */
|
|
748
|
+
function compileSubquery(context, scope, query, subject) {
|
|
749
|
+
const { length } = query.projections;
|
|
750
|
+
if (length !== 1) throw new Error(`${subject} must select one field, not ${length}`);
|
|
751
|
+
return `(${compileQuery(context, scope, query)})`;
|
|
752
|
+
}
|
|
740
753
|
/** Encodes the value of a parameter node with its type, or by its JavaScript type without one. */
|
|
741
754
|
function encodeParameter(node) {
|
|
742
755
|
if (node.value === void 0) throw new TypeError("Query parameter is undefined; use null for NULL");
|
|
@@ -787,7 +800,8 @@ function visitNodes(node, deep, visit) {
|
|
|
787
800
|
case "param":
|
|
788
801
|
case "boolean":
|
|
789
802
|
case "tableName":
|
|
790
|
-
case "values":
|
|
803
|
+
case "values":
|
|
804
|
+
case "nestedQuery": return;
|
|
791
805
|
case "cast":
|
|
792
806
|
case "not":
|
|
793
807
|
case "isNull":
|
|
@@ -805,11 +819,16 @@ function visitNodes(node, deep, visit) {
|
|
|
805
819
|
visitNodes(node.operand, deep, visit);
|
|
806
820
|
for (const item of node.list) visitNodes(item, deep, visit);
|
|
807
821
|
return;
|
|
822
|
+
case "inQuery":
|
|
823
|
+
visitNodes(node.operand, deep, visit);
|
|
824
|
+
if (deep && node.query.kind === "select") visitQuery(node.query, visit);
|
|
825
|
+
return;
|
|
808
826
|
case "raw":
|
|
809
827
|
for (const value of node.values) visitNodes(value, deep, visit);
|
|
810
828
|
return;
|
|
811
829
|
case "subquery":
|
|
812
830
|
case "exists":
|
|
831
|
+
case "textSubquery":
|
|
813
832
|
if (deep) visitQuery(node.query, visit);
|
|
814
833
|
return;
|
|
815
834
|
case "aggregate":
|
|
@@ -834,12 +853,31 @@ function visitQuery(query, visit) {
|
|
|
834
853
|
if (query.limit !== void 0) visitNodes(query.limit, true, visit);
|
|
835
854
|
if (query.offset !== void 0) visitNodes(query.offset, true, visit);
|
|
836
855
|
}
|
|
856
|
+
// src/query/exists.ts
|
|
857
|
+
let existsProjection;
|
|
858
|
+
/** Returns the projection of whether a query has a row, the only projection of a query made by `exists`. */
|
|
859
|
+
function getExistsProjection() {
|
|
860
|
+
existsProjection ??= Object.freeze({
|
|
861
|
+
key: "exists",
|
|
862
|
+
expression: trueNode,
|
|
863
|
+
type: getBooleanType()
|
|
864
|
+
});
|
|
865
|
+
return existsProjection;
|
|
866
|
+
}
|
|
867
|
+
/** Tells whether `node` is a query of `exists`, whose value is whether the rows that its filters, limit, and offset choose exist. */
|
|
868
|
+
function isExistsQuery(node) {
|
|
869
|
+
return node.value && isExistsProjection(node.projections[0]);
|
|
870
|
+
}
|
|
871
|
+
/** Tells whether a selected field is the projection of `exists`, which only `exists` selects. */
|
|
872
|
+
function isExistsProjection(field) {
|
|
873
|
+
return field === existsProjection;
|
|
874
|
+
}
|
|
837
875
|
// src/query/aggregate.ts
|
|
838
876
|
/** Type of a count: a `bigint` of PostgreSQL decoded as a number, rejecting a count that a number cannot hold exactly. */
|
|
839
877
|
const countType = /* @__PURE__ */ defineType({ sqlType: "bigint" }).transform({
|
|
840
878
|
decode: (text) => decodeSafeInteger(text, "Count"),
|
|
841
879
|
encode: (value) => String(value)
|
|
842
|
-
});
|
|
880
|
+
}).withOperators(orderingOperators);
|
|
843
881
|
/** Types of the sums of the values of `smallint` and `integer` types, by the type of the values, created once for each. */
|
|
844
882
|
const sumTypes = /* @__PURE__ */ new WeakMap();
|
|
845
883
|
const noFallbacks = /* @__PURE__ */ Object.freeze([]);
|
|
@@ -961,6 +999,10 @@ function addFallback(field, fallback) {
|
|
|
961
999
|
function isAggregateQuery(node) {
|
|
962
1000
|
return node.value && isAggregateProjection(node.projections[0]);
|
|
963
1001
|
}
|
|
1002
|
+
/** 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. */
|
|
1003
|
+
function isSelectedAsValue(node) {
|
|
1004
|
+
return isAggregateQuery(node) || isExistsQuery(node);
|
|
1005
|
+
}
|
|
964
1006
|
/** Tells whether a selected field is the projection of an aggregate, which only the aggregate methods select. */
|
|
965
1007
|
function isAggregateProjection(field) {
|
|
966
1008
|
return "ordered" in field;
|
|
@@ -971,7 +1013,7 @@ function toCountType() {
|
|
|
971
1013
|
function toArgumentType(argument) {
|
|
972
1014
|
return argument;
|
|
973
1015
|
}
|
|
974
|
-
/** Returns the type of the sum of the values of type `argument`: for `smallint` and `integer` values, `argument` that rejects a sum that a number cannot hold exactly, and `argument` for others. */
|
|
1016
|
+
/** 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. */
|
|
975
1017
|
function toSumType(argument) {
|
|
976
1018
|
if (argument.castType !== "smallint" && argument.castType !== "integer") return argument;
|
|
977
1019
|
let type = sumTypes.get(argument);
|
|
@@ -982,7 +1024,7 @@ function toSumType(argument) {
|
|
|
982
1024
|
return argument.decode(text);
|
|
983
1025
|
},
|
|
984
1026
|
encode: (value) => argument.encode(value)
|
|
985
|
-
});
|
|
1027
|
+
}).withOperators(argument.operators);
|
|
986
1028
|
sumTypes.set(argument, type);
|
|
987
1029
|
}
|
|
988
1030
|
return type;
|
|
@@ -1370,66 +1412,112 @@ function createRef(source, key) {
|
|
|
1370
1412
|
// src/lowering/lower.ts
|
|
1371
1413
|
const noJoins = /* @__PURE__ */ Object.freeze([]);
|
|
1372
1414
|
const noOrder = /* @__PURE__ */ Object.freeze([]);
|
|
1415
|
+
const noProjections = /* @__PURE__ */ Object.freeze([]);
|
|
1373
1416
|
function lowerStatement(node, definitions, db) {
|
|
1417
|
+
const context = {
|
|
1418
|
+
definitions,
|
|
1419
|
+
db
|
|
1420
|
+
};
|
|
1374
1421
|
switch (node.kind) {
|
|
1375
|
-
case "query": return lowerSelect(node,
|
|
1376
|
-
case "record": return lowerRecord(node,
|
|
1377
|
-
case "text": return node;
|
|
1378
|
-
default: return lowerMutation(node,
|
|
1422
|
+
case "query": return lowerSelect(node, context, true);
|
|
1423
|
+
case "record": return lowerRecord(node, context);
|
|
1424
|
+
case "text": return lowerText(node, context);
|
|
1425
|
+
default: return lowerMutation(node, context);
|
|
1379
1426
|
}
|
|
1380
1427
|
}
|
|
1381
|
-
/** Lowers a
|
|
1382
|
-
function
|
|
1383
|
-
const
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1428
|
+
/** Lowers a statement of SQL text, replacing the queries in the expressions interpolated into it. */
|
|
1429
|
+
function lowerText(node, context) {
|
|
1430
|
+
const sql = lowerExpression(node.sql, context);
|
|
1431
|
+
if (sql === node.sql) return node;
|
|
1432
|
+
return Object.freeze({
|
|
1433
|
+
kind: "text",
|
|
1434
|
+
sql,
|
|
1435
|
+
fields: node.fields,
|
|
1436
|
+
rows: node.rows,
|
|
1437
|
+
value: node.value
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
/** 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. */
|
|
1441
|
+
function lowerMutation(node, context) {
|
|
1442
|
+
const { target, returning, rows, value } = node;
|
|
1443
|
+
const lowered = returning === void 0 ? void 0 : lowerProjections(returning, context);
|
|
1387
1444
|
switch (node.kind) {
|
|
1388
|
-
case "insert":
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1445
|
+
case "insert": {
|
|
1446
|
+
const values = lowerEach(node.values, context, lowerRow);
|
|
1447
|
+
if (lowered === returning && values === node.values) return node;
|
|
1448
|
+
return Object.freeze({
|
|
1449
|
+
kind: "insert",
|
|
1450
|
+
target,
|
|
1451
|
+
columns: node.columns,
|
|
1452
|
+
values,
|
|
1453
|
+
returning: lowered,
|
|
1454
|
+
rows,
|
|
1455
|
+
value
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
case "update": {
|
|
1459
|
+
const set = lowerEach(node.set, context, lowerAssignment);
|
|
1460
|
+
const filters = lowerFilters(node.filters, context);
|
|
1461
|
+
if (lowered === returning && set === node.set && filters === node.filters) return node;
|
|
1462
|
+
return Object.freeze({
|
|
1463
|
+
kind: "update",
|
|
1464
|
+
target,
|
|
1465
|
+
set,
|
|
1466
|
+
filters,
|
|
1467
|
+
returning: lowered,
|
|
1468
|
+
rows,
|
|
1469
|
+
value
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
case "delete": {
|
|
1473
|
+
const filters = lowerFilters(node.filters, context);
|
|
1474
|
+
if (lowered === returning && filters === node.filters) return node;
|
|
1475
|
+
return Object.freeze({
|
|
1476
|
+
kind: "delete",
|
|
1477
|
+
target,
|
|
1478
|
+
filters,
|
|
1479
|
+
returning: lowered,
|
|
1480
|
+
rows,
|
|
1481
|
+
value
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1414
1484
|
default: return node;
|
|
1415
1485
|
}
|
|
1416
1486
|
}
|
|
1487
|
+
/** Lowers a row of `VALUES` of an insert, whose `undefined` stands for `DEFAULT`. */
|
|
1488
|
+
function lowerRow(row, context) {
|
|
1489
|
+
return lowerEach(row, context, lowerValue);
|
|
1490
|
+
}
|
|
1491
|
+
function lowerValue(node, context) {
|
|
1492
|
+
return node === void 0 ? void 0 : lowerExpression(node, context);
|
|
1493
|
+
}
|
|
1494
|
+
function lowerAssignment(assignment, context) {
|
|
1495
|
+
const value = lowerExpression(assignment.value, context);
|
|
1496
|
+
return value === assignment.value ? assignment : Object.freeze({
|
|
1497
|
+
key: assignment.key,
|
|
1498
|
+
value
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1417
1501
|
/** Lowers a query as `lowerQuery` does, or as `lowerStatement` does when `positions` is set. */
|
|
1418
|
-
function lowerSelect(node,
|
|
1502
|
+
function lowerSelect(node, context, positions) {
|
|
1503
|
+
if (isExistsQuery(node)) return selectExists(node, context);
|
|
1504
|
+
checkRelation(node, context);
|
|
1505
|
+
if (isAggregateQuery(node)) return lowerAggregate(node, context);
|
|
1506
|
+
const selected = node.projections;
|
|
1507
|
+
const projections = lowerProjections(selected, context);
|
|
1508
|
+
const filters = lowerFilters(node.filters, context);
|
|
1509
|
+
const orderBy = lowerOrder(node.orderBy, context, selected, projections);
|
|
1419
1510
|
const { relation } = node;
|
|
1420
|
-
if (relation !== void 0 && definitions !== void 0 && !definitions.has(relation)) throw new Error(`Relation "${formatRelationName(relation.key, relation.name)}" is not part of the schema`);
|
|
1421
|
-
if (isAggregateQuery(node)) return lowerAggregate(node);
|
|
1422
|
-
const projections = lowerProjections(node.projections, definitions, db);
|
|
1423
1511
|
const guaranteedBy = node.guaranteed && relation !== void 0 ? formatRelationName(relation.key, relation.name) : void 0;
|
|
1424
|
-
const page = node.offset !== void 0 &&
|
|
1512
|
+
const page = node.offset !== void 0 && selected.some((field, index) => isPagedField(field, projections[index], orderBy)) ? lowerPage(node, projections, filters, orderBy, positions, guaranteedBy) : void 0;
|
|
1425
1513
|
if (page !== void 0) return page;
|
|
1426
1514
|
return Object.freeze({
|
|
1427
1515
|
kind: "select",
|
|
1428
1516
|
from: node.from,
|
|
1429
1517
|
joins: noJoins,
|
|
1430
1518
|
projections,
|
|
1431
|
-
filters
|
|
1432
|
-
orderBy: positions &&
|
|
1519
|
+
filters,
|
|
1520
|
+
orderBy: positions && orderBy.length > 0 ? sortByPositions(orderBy, projections) : orderBy,
|
|
1433
1521
|
limit: node.limit,
|
|
1434
1522
|
offset: node.offset,
|
|
1435
1523
|
rows: node.rows,
|
|
@@ -1438,17 +1526,38 @@ function lowerSelect(node, definitions, db, positions) {
|
|
|
1438
1526
|
});
|
|
1439
1527
|
}
|
|
1440
1528
|
/**
|
|
1441
|
-
*
|
|
1529
|
+
* 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.
|
|
1530
|
+
*
|
|
1531
|
+
* The query evaluates a field that it sorts by, of an aggregate or of `exists`, for every row anyway.
|
|
1532
|
+
*/
|
|
1533
|
+
function isPagedField(field, projection, orderBy) {
|
|
1534
|
+
if (!isNestedSelection(field)) return false;
|
|
1535
|
+
const { expression } = projection;
|
|
1536
|
+
return !orderBy.some((order) => order.expression === expression);
|
|
1537
|
+
}
|
|
1538
|
+
/** Rejects a query of a relation outside the definitions of `context`. */
|
|
1539
|
+
function checkRelation(node, context) {
|
|
1540
|
+
const { relation } = node;
|
|
1541
|
+
const { definitions } = context;
|
|
1542
|
+
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`);
|
|
1543
|
+
}
|
|
1544
|
+
/** Rejects a nested query bound to `db`, which the error names as `subject`, unless `db` is the `Db` of `context` or `undefined`. */
|
|
1545
|
+
function checkDb(db, context, subject) {
|
|
1546
|
+
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`);
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* 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.
|
|
1442
1550
|
* The derived source applies the filters, the order, the limit, and the offset, and replaces the occurrence of the table.
|
|
1443
1551
|
*
|
|
1444
|
-
* It returns `undefined` when a nested query holds a SQL fragment that refers to a column of the table outside the queries nested in the fragment.
|
|
1552
|
+
* It returns `undefined` when a nested query that it evaluates over the rows of the page holds a SQL fragment that refers to a column of the table outside the queries nested in the fragment.
|
|
1445
1553
|
* Such a fragment may hold an aggregate of the query, which the limit and the offset apply after.
|
|
1446
1554
|
*
|
|
1447
1555
|
* 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.
|
|
1556
|
+
* A field of an aggregate or of `exists` that the query sorts by is such a scalar field: the order needs its value for every row.
|
|
1448
1557
|
* A column that no field reads stays out of the source, which could not select it beside an aggregate of a SQL fragment, such as `count(*)`.
|
|
1449
1558
|
* The order of a derived source does not carry into the query that reads it, so the statement sorts the rows of the page again by the sort keys of the source, by positions when `positions` is set.
|
|
1450
1559
|
*/
|
|
1451
|
-
function lowerPage(node, projections, positions, guaranteedBy) {
|
|
1560
|
+
function lowerPage(node, projections, filters, orderBy, positions, guaranteedBy) {
|
|
1452
1561
|
const { from } = node;
|
|
1453
1562
|
const columnKeys = /* @__PURE__ */ new Set();
|
|
1454
1563
|
const fields = [];
|
|
@@ -1456,7 +1565,7 @@ function lowerPage(node, projections, positions, guaranteedBy) {
|
|
|
1456
1565
|
for (let index = 0; index < projections.length; index++) {
|
|
1457
1566
|
const { expression } = projections[index];
|
|
1458
1567
|
let key;
|
|
1459
|
-
if (
|
|
1568
|
+
if (isPagedField(node.projections[index], projections[index], orderBy)) {
|
|
1460
1569
|
if (holdsFragmentOfSource(expression, from)) return;
|
|
1461
1570
|
collectColumnKeys(expression, from, columnKeys);
|
|
1462
1571
|
} else if (expression.kind === "column" && expression.source === from) columnKeys.add(expression.key);
|
|
@@ -1486,8 +1595,8 @@ function lowerPage(node, projections, positions, guaranteedBy) {
|
|
|
1486
1595
|
from,
|
|
1487
1596
|
joins: noJoins,
|
|
1488
1597
|
projections: sourced,
|
|
1489
|
-
filters
|
|
1490
|
-
orderBy
|
|
1598
|
+
filters,
|
|
1599
|
+
orderBy,
|
|
1491
1600
|
limit: node.limit,
|
|
1492
1601
|
offset: node.offset,
|
|
1493
1602
|
rows: "many",
|
|
@@ -1523,13 +1632,14 @@ function lowerPage(node, projections, positions, guaranteedBy) {
|
|
|
1523
1632
|
});
|
|
1524
1633
|
}
|
|
1525
1634
|
/** Lowers an aggregate query to a statement of the value of its aggregate over the rows that its filters, limit, and offset choose. */
|
|
1526
|
-
function lowerAggregate(node) {
|
|
1635
|
+
function lowerAggregate(node, context) {
|
|
1527
1636
|
const field = node.projections[0];
|
|
1528
1637
|
const aggregate = field.expression;
|
|
1638
|
+
const orderBy = lowerOrder(node.orderBy, context);
|
|
1529
1639
|
let from = node.from;
|
|
1530
|
-
let filters = node.filters;
|
|
1531
|
-
let args = aggregate.arguments;
|
|
1532
|
-
let order = field.ordered ?
|
|
1640
|
+
let filters = lowerFilters(node.filters, context);
|
|
1641
|
+
let args = lowerEach(aggregate.arguments, context, lowerExpression);
|
|
1642
|
+
let order = field.ordered ? orderBy : noOrder;
|
|
1533
1643
|
const counted = aggregate === countProjection.expression;
|
|
1534
1644
|
if (node.limit !== void 0 || node.offset !== void 0 || !counted && !refersToSource(args[0], node.from)) {
|
|
1535
1645
|
const projections = [];
|
|
@@ -1543,8 +1653,8 @@ function lowerAggregate(node) {
|
|
|
1543
1653
|
from: node.from,
|
|
1544
1654
|
joins: noJoins,
|
|
1545
1655
|
projections,
|
|
1546
|
-
filters
|
|
1547
|
-
orderBy: counted ? noOrder :
|
|
1656
|
+
filters,
|
|
1657
|
+
orderBy: counted ? noOrder : orderBy,
|
|
1548
1658
|
limit: node.limit,
|
|
1549
1659
|
offset: node.offset,
|
|
1550
1660
|
rows: "many",
|
|
@@ -1571,7 +1681,7 @@ function lowerAggregate(node) {
|
|
|
1571
1681
|
arguments: args,
|
|
1572
1682
|
orderBy: order
|
|
1573
1683
|
});
|
|
1574
|
-
const
|
|
1684
|
+
const fallbacks = lowerEach(field.fallbacks, context, lowerExpression);
|
|
1575
1685
|
let value = call;
|
|
1576
1686
|
if (fallbacks.length > 0) {
|
|
1577
1687
|
const values = [call];
|
|
@@ -1607,12 +1717,15 @@ function refersToSource(node, source) {
|
|
|
1607
1717
|
case "param":
|
|
1608
1718
|
case "boolean":
|
|
1609
1719
|
case "values":
|
|
1720
|
+
case "nestedQuery":
|
|
1610
1721
|
case "tableName":
|
|
1611
1722
|
case "subquery":
|
|
1612
|
-
case "exists":
|
|
1723
|
+
case "exists":
|
|
1724
|
+
case "textSubquery": return false;
|
|
1613
1725
|
case "cast":
|
|
1614
1726
|
case "not":
|
|
1615
|
-
case "isNull":
|
|
1727
|
+
case "isNull":
|
|
1728
|
+
case "inQuery": return refersToSource(node.operand, source);
|
|
1616
1729
|
case "and":
|
|
1617
1730
|
case "or": return node.operands.some((operand) => refersToSource(operand, source));
|
|
1618
1731
|
case "comparison": return refersToSource(node.left, source) || refersToSource(node.right, source);
|
|
@@ -1622,13 +1735,57 @@ function refersToSource(node, source) {
|
|
|
1622
1735
|
default: return node;
|
|
1623
1736
|
}
|
|
1624
1737
|
}
|
|
1738
|
+
/** Lowers a query of `exists` to `EXISTS` of the rows that its filters, limit, and offset choose. */
|
|
1739
|
+
function lowerExists(node, context) {
|
|
1740
|
+
checkRelation(node, context);
|
|
1741
|
+
return createNode({
|
|
1742
|
+
kind: "exists",
|
|
1743
|
+
query: Object.freeze({
|
|
1744
|
+
kind: "select",
|
|
1745
|
+
from: node.from,
|
|
1746
|
+
joins: noJoins,
|
|
1747
|
+
projections: noProjections,
|
|
1748
|
+
filters: lowerFilters(node.filters, context),
|
|
1749
|
+
orderBy: noOrder,
|
|
1750
|
+
limit: node.limit,
|
|
1751
|
+
offset: node.offset,
|
|
1752
|
+
rows: "many",
|
|
1753
|
+
value: false,
|
|
1754
|
+
guaranteedBy: void 0
|
|
1755
|
+
})
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
/** Lowers a query of `exists` to a statement without a source of one row of `EXISTS` of its rows. */
|
|
1759
|
+
function selectExists(node, context) {
|
|
1760
|
+
return Object.freeze({
|
|
1761
|
+
kind: "select",
|
|
1762
|
+
from: void 0,
|
|
1763
|
+
joins: noJoins,
|
|
1764
|
+
projections: [projectExists(node.projections[0].key, node, context)],
|
|
1765
|
+
filters: noFilters,
|
|
1766
|
+
orderBy: noOrder,
|
|
1767
|
+
limit: void 0,
|
|
1768
|
+
offset: void 0,
|
|
1769
|
+
rows: node.rows,
|
|
1770
|
+
value: true,
|
|
1771
|
+
guaranteedBy: void 0
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
/** 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`. */
|
|
1775
|
+
function projectExists(key, node, context) {
|
|
1776
|
+
return Object.freeze({
|
|
1777
|
+
key,
|
|
1778
|
+
expression: lowerExists(node, context),
|
|
1779
|
+
type: node.projections[0].type
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1625
1782
|
/** Lowers a query of `$select` to a statement without a source, which returns one row of its fields. */
|
|
1626
|
-
function lowerRecord(node,
|
|
1783
|
+
function lowerRecord(node, context) {
|
|
1627
1784
|
return Object.freeze({
|
|
1628
1785
|
kind: "select",
|
|
1629
1786
|
from: void 0,
|
|
1630
1787
|
joins: noJoins,
|
|
1631
|
-
projections: lowerProjections(node.projections,
|
|
1788
|
+
projections: lowerProjections(node.projections, context),
|
|
1632
1789
|
filters: noFilters,
|
|
1633
1790
|
orderBy: noOrder,
|
|
1634
1791
|
limit: void 0,
|
|
@@ -1639,23 +1796,40 @@ function lowerRecord(node, definitions, db) {
|
|
|
1639
1796
|
});
|
|
1640
1797
|
}
|
|
1641
1798
|
/**
|
|
1642
|
-
* Lowers the fields of a query to projections, packing the rows of each nested query into the value of its field, or selecting the value of an aggregate query; checks them as `lowerQuery` does.
|
|
1799
|
+
* Lowers the fields of a query to projections, packing the rows of each nested query into the value of its field, or selecting the value of an aggregate query or `EXISTS` of a query of `exists`, and replacing the queries in the expressions of the other fields; checks them as `lowerQuery` does.
|
|
1643
1800
|
*
|
|
1644
|
-
* Without
|
|
1801
|
+
* Without queries, it returns `selected` itself.
|
|
1645
1802
|
*/
|
|
1646
|
-
function lowerProjections(selected,
|
|
1803
|
+
function lowerProjections(selected, context) {
|
|
1647
1804
|
let lowered;
|
|
1648
1805
|
for (let index = 0; index < selected.length; index++) {
|
|
1649
1806
|
const field = selected[index];
|
|
1807
|
+
let projection;
|
|
1650
1808
|
if (isNestedSelection(field)) {
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1809
|
+
checkDb(field.db, context, `Selected field "${field.key}"`);
|
|
1810
|
+
projection = projectQuery(field.key, field.query, context);
|
|
1811
|
+
} else {
|
|
1812
|
+
const expression = lowerExpression(field.expression, context);
|
|
1813
|
+
projection = expression === field.expression ? field : Object.freeze({
|
|
1814
|
+
key: field.key,
|
|
1815
|
+
expression,
|
|
1816
|
+
type: field.type
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
if (lowered !== void 0) lowered.push(projection);
|
|
1820
|
+
else if (projection !== field) {
|
|
1821
|
+
lowered = selected.slice(0, index);
|
|
1822
|
+
lowered.push(projection);
|
|
1823
|
+
}
|
|
1656
1824
|
}
|
|
1657
1825
|
return lowered ?? selected;
|
|
1658
1826
|
}
|
|
1827
|
+
/** 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. */
|
|
1828
|
+
function projectQuery(key, node, context) {
|
|
1829
|
+
if (isExistsQuery(node)) return projectExists(key, node, context);
|
|
1830
|
+
const query = lowerSelect(node, context, false);
|
|
1831
|
+
return isAggregateQuery(node) ? projectAggregateValue(key, query) : projectRows(key, query);
|
|
1832
|
+
}
|
|
1659
1833
|
/** Creates the projection of the value of `query`, the statement of an aggregate query, under `key`, selected as a scalar subquery and decoded by the type of the aggregate. */
|
|
1660
1834
|
function projectAggregateValue(key, query) {
|
|
1661
1835
|
return Object.freeze({
|
|
@@ -1667,6 +1841,176 @@ function projectAggregateValue(key, query) {
|
|
|
1667
1841
|
type: query.projections[0].type
|
|
1668
1842
|
});
|
|
1669
1843
|
}
|
|
1844
|
+
/** Lowers the filters of a query, replacing the queries in their expressions; without queries, it returns `filters` itself. */
|
|
1845
|
+
function lowerFilters(filters, context) {
|
|
1846
|
+
const { relation, scope, user } = filters;
|
|
1847
|
+
const loweredRelation = relation === void 0 ? void 0 : lowerExpression(relation, context);
|
|
1848
|
+
const loweredScope = scope === void 0 ? void 0 : lowerExpression(scope, context);
|
|
1849
|
+
const loweredUser = user === void 0 ? void 0 : lowerExpression(user, context);
|
|
1850
|
+
if (loweredRelation === relation && loweredScope === scope && loweredUser === user) return filters;
|
|
1851
|
+
const lowered = {};
|
|
1852
|
+
if (loweredRelation !== void 0) lowered.relation = loweredRelation;
|
|
1853
|
+
if (loweredScope !== void 0) lowered.scope = loweredScope;
|
|
1854
|
+
if (loweredUser !== void 0) lowered.user = loweredUser;
|
|
1855
|
+
return Object.freeze(lowered);
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Lowers the sort keys of a query, replacing the queries in their expressions; without queries, it returns `orderBy` itself.
|
|
1859
|
+
*
|
|
1860
|
+
* 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.
|
|
1861
|
+
* A field of an aggregate or of `exists` is identified by the node of its reference, which holds the field.
|
|
1862
|
+
*/
|
|
1863
|
+
function lowerOrder(orderBy, context, selected, projections) {
|
|
1864
|
+
let lowered;
|
|
1865
|
+
for (let index = 0; index < orderBy.length; index++) {
|
|
1866
|
+
const order = orderBy[index];
|
|
1867
|
+
const { expression } = order;
|
|
1868
|
+
const position = selected === void 0 || projections === void 0 || projections === selected ? -1 : selected.findIndex((field) => isFieldValue(field, expression));
|
|
1869
|
+
const sorted = position === -1 ? lowerExpression(expression, context) : projections[position].expression;
|
|
1870
|
+
if (lowered === void 0 && sorted === expression) continue;
|
|
1871
|
+
lowered ??= orderBy.slice(0, index);
|
|
1872
|
+
lowered.push(sorted === expression ? order : Object.freeze({
|
|
1873
|
+
expression: sorted,
|
|
1874
|
+
direction: order.direction,
|
|
1875
|
+
nulls: order.nulls
|
|
1876
|
+
}));
|
|
1877
|
+
}
|
|
1878
|
+
return lowered ?? orderBy;
|
|
1879
|
+
}
|
|
1880
|
+
/** 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`. */
|
|
1881
|
+
function isFieldValue(field, expression) {
|
|
1882
|
+
return isNestedSelection(field) ? expression.kind === "nestedQuery" && expression.field === field : field.expression === expression;
|
|
1883
|
+
}
|
|
1884
|
+
/**
|
|
1885
|
+
* Lowers an expression, replacing each query in it and checking the query as a nested query.
|
|
1886
|
+
* 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.
|
|
1887
|
+
*
|
|
1888
|
+
* It returns `node` itself when it holds no query, and creates only the nodes that hold one.
|
|
1889
|
+
*/
|
|
1890
|
+
function lowerExpression(node, context) {
|
|
1891
|
+
switch (node.kind) {
|
|
1892
|
+
case "column":
|
|
1893
|
+
case "param":
|
|
1894
|
+
case "boolean":
|
|
1895
|
+
case "values":
|
|
1896
|
+
case "tableName":
|
|
1897
|
+
case "subquery":
|
|
1898
|
+
case "exists":
|
|
1899
|
+
case "textSubquery": return node;
|
|
1900
|
+
case "nestedQuery": return lowerNestedQuery(node, context);
|
|
1901
|
+
case "cast": {
|
|
1902
|
+
const operand = lowerExpression(node.operand, context);
|
|
1903
|
+
return operand === node.operand ? node : createNode({
|
|
1904
|
+
kind: "cast",
|
|
1905
|
+
operand,
|
|
1906
|
+
type: node.type
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
case "not": {
|
|
1910
|
+
const operand = lowerExpression(node.operand, context);
|
|
1911
|
+
return operand === node.operand ? node : createNode({
|
|
1912
|
+
kind: "not",
|
|
1913
|
+
operand
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
case "isNull": {
|
|
1917
|
+
const operand = lowerExpression(node.operand, context);
|
|
1918
|
+
return operand === node.operand ? node : createNode({
|
|
1919
|
+
kind: "isNull",
|
|
1920
|
+
operand
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
case "and":
|
|
1924
|
+
case "or": {
|
|
1925
|
+
const operands = lowerEach(node.operands, context, lowerExpression);
|
|
1926
|
+
return operands === node.operands ? node : createNode({
|
|
1927
|
+
kind: node.kind,
|
|
1928
|
+
operands
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
case "comparison": {
|
|
1932
|
+
const left = lowerExpression(node.left, context);
|
|
1933
|
+
const right = lowerExpression(node.right, context);
|
|
1934
|
+
return left === node.left && right === node.right ? node : createNode({
|
|
1935
|
+
kind: "comparison",
|
|
1936
|
+
operator: node.operator,
|
|
1937
|
+
left,
|
|
1938
|
+
right
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
case "in": {
|
|
1942
|
+
const operand = lowerExpression(node.operand, context);
|
|
1943
|
+
const list = lowerEach(node.list, context, lowerExpression);
|
|
1944
|
+
return operand === node.operand && list === node.list ? node : createNode({
|
|
1945
|
+
kind: "in",
|
|
1946
|
+
operand,
|
|
1947
|
+
list
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
case "inQuery": {
|
|
1951
|
+
const operand = lowerExpression(node.operand, context);
|
|
1952
|
+
const query = node.query.kind === "nestedQuery" ? lowerQueryOfValues(node.query, context) : node.query;
|
|
1953
|
+
return operand === node.operand && query === node.query ? node : createNode({
|
|
1954
|
+
kind: "inQuery",
|
|
1955
|
+
operand,
|
|
1956
|
+
query
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
case "raw": {
|
|
1960
|
+
const values = lowerEach(node.values, context, lowerExpression);
|
|
1961
|
+
return values === node.values ? node : createNode({
|
|
1962
|
+
kind: "raw",
|
|
1963
|
+
fragments: node.fragments,
|
|
1964
|
+
values
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
case "aggregate": {
|
|
1968
|
+
const args = lowerEach(node.arguments, context, lowerExpression);
|
|
1969
|
+
const orderBy = lowerOrder(node.orderBy, context);
|
|
1970
|
+
return args === node.arguments && orderBy === node.orderBy ? node : createNode({
|
|
1971
|
+
kind: "aggregate",
|
|
1972
|
+
name: node.name,
|
|
1973
|
+
distinct: node.distinct,
|
|
1974
|
+
arguments: args,
|
|
1975
|
+
orderBy
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
default: return node;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* 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.
|
|
1983
|
+
*
|
|
1984
|
+
* 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.
|
|
1985
|
+
*/
|
|
1986
|
+
function lowerNestedQuery(node, context) {
|
|
1987
|
+
checkDb(node.db, context, "A query in an expression");
|
|
1988
|
+
const { query } = node;
|
|
1989
|
+
if (isExistsQuery(query)) return lowerExists(query, context);
|
|
1990
|
+
return createNode({
|
|
1991
|
+
kind: query.value && query.rows !== "many" ? "subquery" : "textSubquery",
|
|
1992
|
+
query: lowerSelect(query, context, false)
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
/** Lowers the query of the values of `IN` to its statement, whatever its rows. */
|
|
1996
|
+
function lowerQueryOfValues(node, context) {
|
|
1997
|
+
checkDb(node.db, context, "A query in an expression");
|
|
1998
|
+
return lowerSelect(node.query, context, false);
|
|
1999
|
+
}
|
|
2000
|
+
/** Lowers each of `items` with `lower`; when none changes, it returns `items` itself. */
|
|
2001
|
+
function lowerEach(items, context, lower) {
|
|
2002
|
+
let lowered;
|
|
2003
|
+
for (let index = 0; index < items.length; index++) {
|
|
2004
|
+
const item = items[index];
|
|
2005
|
+
const result = lower(item, context);
|
|
2006
|
+
if (lowered !== void 0) lowered.push(result);
|
|
2007
|
+
else if (result !== item) {
|
|
2008
|
+
lowered = items.slice(0, index);
|
|
2009
|
+
lowered.push(result);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return lowered ?? items;
|
|
2013
|
+
}
|
|
1670
2014
|
// src/query/kind.ts
|
|
1671
2015
|
/** `then` of every bound query, which executes the query on its `Db`. */
|
|
1672
2016
|
function executeOnAwait(onFulfilled, onRejected) {
|
|
@@ -1689,7 +2033,7 @@ const noContext = /* @__PURE__ */ Object.freeze({ relations: /* @__PURE__ */ Obj
|
|
|
1689
2033
|
function createColumnRefs(source) {
|
|
1690
2034
|
return addColumns({}, source);
|
|
1691
2035
|
}
|
|
1692
|
-
/** Creates the references of the
|
|
2036
|
+
/** Creates the references of the callbacks of `select`, `where`, and `orderBy` over `source`: its columns, and the relations of `context`. */
|
|
1693
2037
|
function createSelectionRefs(source, context) {
|
|
1694
2038
|
return Object.defineProperties(addColumns({}, source), context.relations);
|
|
1695
2039
|
}
|
|
@@ -1749,9 +2093,9 @@ function matchValue(source, column, type, value) {
|
|
|
1749
2093
|
return toNode(predicate);
|
|
1750
2094
|
}
|
|
1751
2095
|
// src/query/filters.ts
|
|
1752
|
-
/** Creates query filters with the filter of `where` over `source` combined with `AND` with the user filters of `filters`. */
|
|
1753
|
-
function addUserFilter(filters, filter, source) {
|
|
1754
|
-
const predicate = resolveValues(toConditionNode(typeof filter === "function" ? filter(
|
|
2096
|
+
/** Creates query filters with the filter of `where` over `source` with the relations of `context` combined with `AND` with the user filters of `filters`. */
|
|
2097
|
+
function addUserFilter(filters, filter, source, context) {
|
|
2098
|
+
const predicate = resolveValues(toConditionNode(typeof filter === "function" ? filter(createSelectionRefs(source, context)) : filter), source);
|
|
1755
2099
|
const { user } = filters;
|
|
1756
2100
|
return Object.freeze({
|
|
1757
2101
|
...filters,
|
|
@@ -1843,29 +2187,29 @@ var OrderingObject = class {
|
|
|
1843
2187
|
Object.freeze(this);
|
|
1844
2188
|
}
|
|
1845
2189
|
};
|
|
1846
|
-
/** Sorts by `expression
|
|
1847
|
-
function asc(
|
|
1848
|
-
return orderBy(
|
|
2190
|
+
/** Sorts by `value`, an expression or a query of one value, in ascending order, in the callback of `orderBy`. */
|
|
2191
|
+
function asc(value) {
|
|
2192
|
+
return orderBy(value, "asc");
|
|
1849
2193
|
}
|
|
1850
|
-
/** Sorts by `expression
|
|
1851
|
-
function desc(
|
|
1852
|
-
return orderBy(
|
|
2194
|
+
/** Sorts by `value`, an expression or a query of one value, in descending order, in the callback of `orderBy`. */
|
|
2195
|
+
function desc(value) {
|
|
2196
|
+
return orderBy(value, "desc");
|
|
1853
2197
|
}
|
|
1854
|
-
/** Sorts by `expression
|
|
1855
|
-
function orderBy(
|
|
1856
|
-
if (!
|
|
1857
|
-
return new OrderingObject(createOrderNode(
|
|
2198
|
+
/** Sorts by `value`, an expression or a query of one value, in `direction`, such as `"desc nulls last"`, in the callback of `orderBy`. */
|
|
2199
|
+
function orderBy(value, direction) {
|
|
2200
|
+
if (!isOperandSource(value)) throw new TypeError("A sort key must be an expression or a query of one value");
|
|
2201
|
+
return new OrderingObject(createOrderNode(value[operandNode](), direction));
|
|
1858
2202
|
}
|
|
1859
2203
|
/**
|
|
1860
2204
|
* Creates the order of `node` with the sort keys of the arguments of `orderBy` after its own.
|
|
1861
2205
|
*
|
|
1862
|
-
* `args` are column keys and objects of columns with directions, or a callback that receives the columns and the selected fields and returns one sort key or an array of them.
|
|
2206
|
+
* `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.
|
|
1863
2207
|
*/
|
|
1864
|
-
function addOrder(node, args) {
|
|
2208
|
+
function addOrder(node, context, args) {
|
|
1865
2209
|
const order = node.orderBy.slice();
|
|
1866
2210
|
const first = args[0];
|
|
1867
2211
|
if (typeof first === "function") {
|
|
1868
|
-
const terms = first(
|
|
2212
|
+
const terms = first(createSelectionRefs(node.from, context), createSelectedRefs(node));
|
|
1869
2213
|
if (Array.isArray(terms)) for (const term of terms) order.push(readTerm(term));
|
|
1870
2214
|
else order.push(readTerm(terms));
|
|
1871
2215
|
return order;
|
|
@@ -1877,11 +2221,15 @@ function addOrder(node, args) {
|
|
|
1877
2221
|
} else throw new TypeError("orderBy takes column keys and objects of columns with directions, or a callback");
|
|
1878
2222
|
return order;
|
|
1879
2223
|
}
|
|
1880
|
-
/**
|
|
2224
|
+
/**
|
|
2225
|
+
* Creates the sort key node of a sort key returned by the callback of `orderBy`.
|
|
2226
|
+
*
|
|
2227
|
+
* A query is converted by the method of `operandNode`, which rejects a query other than of one value.
|
|
2228
|
+
*/
|
|
1881
2229
|
function readTerm(term) {
|
|
1882
2230
|
if (term instanceof OrderingObject) return term[orderingNode];
|
|
1883
|
-
if (
|
|
1884
|
-
throw new TypeError("The callback of orderBy must return expressions, or sort keys of asc, desc, and orderBy");
|
|
2231
|
+
if (isOperandSource(term)) return createOrderNode(term[operandNode](), "asc");
|
|
2232
|
+
throw new TypeError("The callback of orderBy must return expressions, queries of one value, or sort keys of asc, desc, and orderBy");
|
|
1885
2233
|
}
|
|
1886
2234
|
/** Creates the sort key node of `expression` in `direction`, an `OrderDirection` checked at runtime. */
|
|
1887
2235
|
function createOrderNode(expression, direction) {
|
|
@@ -1906,6 +2254,7 @@ function createColumnNode(source, key) {
|
|
|
1906
2254
|
* Creates the references of the callback of `orderBy` to the fields selected by `node`.
|
|
1907
2255
|
*
|
|
1908
2256
|
* Each is an expression over the node of its projection, so that a sort key of it is the projection itself.
|
|
2257
|
+
* 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.
|
|
1909
2258
|
*/
|
|
1910
2259
|
function createSelectedRefs(node) {
|
|
1911
2260
|
const { projections } = node;
|
|
@@ -1913,13 +2262,15 @@ function createSelectedRefs(node) {
|
|
|
1913
2262
|
const refs = {};
|
|
1914
2263
|
for (const projection of projections) {
|
|
1915
2264
|
const { key } = projection;
|
|
1916
|
-
if (isNestedSelection(projection))
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
2265
|
+
if (isNestedSelection(projection)) {
|
|
2266
|
+
if (isSelectedAsValue(projection.query)) refs[key] = toQueryExpression(projection);
|
|
2267
|
+
else Object.defineProperty(refs, key, {
|
|
2268
|
+
get() {
|
|
2269
|
+
throw new Error(`Field "${key}" holds nested rows and cannot be sorted by`);
|
|
2270
|
+
},
|
|
2271
|
+
enumerable: true
|
|
2272
|
+
});
|
|
2273
|
+
} else refs[key] = toSelectedExpression(projection);
|
|
1923
2274
|
}
|
|
1924
2275
|
return refs;
|
|
1925
2276
|
}
|
|
@@ -1927,6 +2278,16 @@ function createSelectedRefs(node) {
|
|
|
1927
2278
|
function toSelectedExpression(projection) {
|
|
1928
2279
|
return toExpression(projection.expression, projection.type);
|
|
1929
2280
|
}
|
|
2281
|
+
/** Creates an expression of the value of `field`, a selected aggregate query or query of `exists`, with the type of its value. */
|
|
2282
|
+
function toQueryExpression(field) {
|
|
2283
|
+
const { query } = field;
|
|
2284
|
+
return toExpression(createNode({
|
|
2285
|
+
kind: "nestedQuery",
|
|
2286
|
+
query,
|
|
2287
|
+
db: field.db,
|
|
2288
|
+
field
|
|
2289
|
+
}), query.projections[0].type);
|
|
2290
|
+
}
|
|
1930
2291
|
// src/query/pagination.ts
|
|
1931
2292
|
/** Creates the parameter of the number of rows of `limit` or `offset`, named by `method`, of query `node`, rejecting a query of one row and a count that is not a non-negative safe integer. */
|
|
1932
2293
|
function createPageCount(node, method, count) {
|
|
@@ -1941,7 +2302,9 @@ function createPageCount(node, method, count) {
|
|
|
1941
2302
|
/** Creates the hint of the error of `limit` or `offset` of query `node` of one row: the method to call them before, if any. */
|
|
1942
2303
|
function createPaginationHint(node) {
|
|
1943
2304
|
if (node.relation?.kind === "one") return "";
|
|
1944
|
-
|
|
2305
|
+
if (isAggregateQuery(node)) return "; call it before count or another aggregate to aggregate the rows of a page";
|
|
2306
|
+
if (isExistsQuery(node)) return "; call it before exists to check the rows of a page";
|
|
2307
|
+
return "; call it before take or get";
|
|
1945
2308
|
}
|
|
1946
2309
|
// src/query/select.ts
|
|
1947
2310
|
/**
|
|
@@ -2032,13 +2395,21 @@ var SelectQuery = class SelectQuery {
|
|
|
2032
2395
|
stringAgg(arg, delimiter) {
|
|
2033
2396
|
return this.aggregateBy("stringAgg", arg, delimiter);
|
|
2034
2397
|
}
|
|
2398
|
+
exists() {
|
|
2399
|
+
this.rejectAggregate("exists");
|
|
2400
|
+
return this.deriveAggregate(getExistsProjection());
|
|
2401
|
+
}
|
|
2035
2402
|
coalesce(fallback) {
|
|
2036
2403
|
const { node } = this;
|
|
2037
2404
|
if (!node.value) throw new Error("coalesce applies only to a query of values, such as one made by get, pluck, or an aggregate");
|
|
2038
2405
|
return this.derive({ projections: [coalesceValue(node.projections[0], fallback)] });
|
|
2039
2406
|
}
|
|
2407
|
+
expr() {
|
|
2408
|
+
const value = this[operandNode]();
|
|
2409
|
+
return toExpression(value, this.node.projections[0].type);
|
|
2410
|
+
}
|
|
2040
2411
|
orderBy(...args) {
|
|
2041
|
-
return this.derive({ orderBy: addOrder(this.node, args) });
|
|
2412
|
+
return this.derive({ orderBy: addOrder(this.node, this.context, args) });
|
|
2042
2413
|
}
|
|
2043
2414
|
limit(count) {
|
|
2044
2415
|
return this.derive({ limit: createPageCount(this.node, "limit", count) });
|
|
@@ -2079,6 +2450,34 @@ var SelectQuery = class SelectQuery {
|
|
|
2079
2450
|
toSQL(options) {
|
|
2080
2451
|
return compileToSQL(this, options);
|
|
2081
2452
|
}
|
|
2453
|
+
/**
|
|
2454
|
+
* 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`.
|
|
2455
|
+
*
|
|
2456
|
+
* It rejects a query of records or of rows `many`, which `ScalarQuery` rejects in types.
|
|
2457
|
+
*/
|
|
2458
|
+
[operandNode]() {
|
|
2459
|
+
const { node } = this;
|
|
2460
|
+
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");
|
|
2461
|
+
return createNode({
|
|
2462
|
+
kind: "nestedQuery",
|
|
2463
|
+
query: node,
|
|
2464
|
+
db: this.db
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* Returns the node of the query as the values of `in`, which lowering replaces with its statement.
|
|
2469
|
+
*
|
|
2470
|
+
* It rejects a query of records, which `ValuesQuery` rejects in types.
|
|
2471
|
+
*/
|
|
2472
|
+
[valuesQueryNode]() {
|
|
2473
|
+
const { node } = this;
|
|
2474
|
+
if (!node.value) throw new TypeError("in takes a query of values, such as of pluck, not a query of records");
|
|
2475
|
+
return createNode({
|
|
2476
|
+
kind: "nestedQuery",
|
|
2477
|
+
query: node,
|
|
2478
|
+
db: this.db
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2082
2481
|
/** Creates a query of the same kind and `Db` with the node changes applied. */
|
|
2083
2482
|
derive(changes) {
|
|
2084
2483
|
return new SelectQuery(this.table, Object.freeze({
|
|
@@ -2098,7 +2497,7 @@ var SelectQuery = class SelectQuery {
|
|
|
2098
2497
|
}
|
|
2099
2498
|
return this.derive(changes);
|
|
2100
2499
|
}
|
|
2101
|
-
/** Creates the query of the value of `projection`, an aggregate over the rows of this query, which always returns one row. */
|
|
2500
|
+
/** 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. */
|
|
2102
2501
|
deriveAggregate(projection) {
|
|
2103
2502
|
return this.derive({
|
|
2104
2503
|
projections: [projection],
|
|
@@ -2116,7 +2515,7 @@ var SelectQuery = class SelectQuery {
|
|
|
2116
2515
|
addFilter(filter) {
|
|
2117
2516
|
const { node } = this;
|
|
2118
2517
|
return {
|
|
2119
|
-
filters: addUserFilter(node.filters, filter, node.from),
|
|
2518
|
+
filters: addUserFilter(node.filters, filter, node.from, this.context),
|
|
2120
2519
|
rows: toFilteredRows(node.rows, node.value, node.guaranteed),
|
|
2121
2520
|
guaranteed: false
|
|
2122
2521
|
};
|
|
@@ -2141,9 +2540,11 @@ var SelectQuery = class SelectQuery {
|
|
|
2141
2540
|
this.rejectAggregate(method);
|
|
2142
2541
|
throw new Error(`${method} cannot be used after get or pluck; call get or pluck to replace the value`);
|
|
2143
2542
|
}
|
|
2144
|
-
/** Rejects `method`, which replaces the value or the selection, on a query made by an aggregate method
|
|
2543
|
+
/** Rejects `method`, which replaces the value or the selection, on a query made by an aggregate method or `exists`. */
|
|
2145
2544
|
rejectAggregate(method) {
|
|
2146
|
-
|
|
2545
|
+
const { node } = this;
|
|
2546
|
+
if (isAggregateQuery(node)) throw new Error(`${method} cannot be used after an aggregate`);
|
|
2547
|
+
if (isExistsQuery(node)) throw new Error(`${method} cannot be used after exists`);
|
|
2147
2548
|
}
|
|
2148
2549
|
};
|
|
2149
2550
|
/**
|
|
@@ -2230,6 +2631,10 @@ var SqlQuery = class SqlQuery {
|
|
|
2230
2631
|
toSQL() {
|
|
2231
2632
|
return this.db.$compile(this);
|
|
2232
2633
|
}
|
|
2634
|
+
/** Rejects the query as an operand of an expression, which takes a query of one value of a table. */
|
|
2635
|
+
[operandNode]() {
|
|
2636
|
+
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");
|
|
2637
|
+
}
|
|
2233
2638
|
/** Creates a query of the same `Db` with the node changes applied. */
|
|
2234
2639
|
derive(changes) {
|
|
2235
2640
|
return new SqlQuery(Object.freeze({
|
|
@@ -2374,6 +2779,10 @@ var MutationQuery = class MutationQuery {
|
|
|
2374
2779
|
toSQL(options) {
|
|
2375
2780
|
return compileToSQL(this, options);
|
|
2376
2781
|
}
|
|
2782
|
+
/** Rejects the query as an operand of an expression, which takes a query of one value of a table. */
|
|
2783
|
+
[operandNode]() {
|
|
2784
|
+
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");
|
|
2785
|
+
}
|
|
2377
2786
|
/** Creates a mutation of the same kind and `Db` with the node changes applied. */
|
|
2378
2787
|
derive(changes) {
|
|
2379
2788
|
return new MutationQuery(this.table, Object.freeze({
|
|
@@ -2486,12 +2895,14 @@ function projectValue(source, arg, method) {
|
|
|
2486
2895
|
return projectExpression("value", value, type);
|
|
2487
2896
|
}
|
|
2488
2897
|
/**
|
|
2489
|
-
* 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, or
|
|
2898
|
+
* 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.
|
|
2490
2899
|
*
|
|
2491
2900
|
* The projection of an aggregate keeps its call, which lowering builds, and adds the fallback after its earlier ones.
|
|
2901
|
+
* The projection of `exists` stays as it is, since `EXISTS` is never `NULL`.
|
|
2492
2902
|
*/
|
|
2493
2903
|
function coalesceValue(field, fallback) {
|
|
2494
2904
|
if (fallback === null || fallback === void 0) throw new TypeError(`coalesce takes a value other than ${String(fallback)}`);
|
|
2905
|
+
if (isExistsProjection(field)) return field;
|
|
2495
2906
|
const value = toValueNode(fallback, field.type);
|
|
2496
2907
|
if (isAggregateProjection(field)) return addFallback(field, value);
|
|
2497
2908
|
return Object.freeze({
|
|
@@ -2568,6 +2979,10 @@ var RecordQuery = class {
|
|
|
2568
2979
|
toSQL(options) {
|
|
2569
2980
|
return compileToSQL(this, options);
|
|
2570
2981
|
}
|
|
2982
|
+
/** Rejects the query as an operand of an expression, which takes a query of one value of a table. */
|
|
2983
|
+
[operandNode]() {
|
|
2984
|
+
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");
|
|
2985
|
+
}
|
|
2571
2986
|
};
|
|
2572
2987
|
/**
|
|
2573
2988
|
* Starts the query of `$select` of `selection`, an object of expressions and queries by key, bound to `db` when given.
|
|
@@ -2699,6 +3114,26 @@ function sql(first, ...values) {
|
|
|
2699
3114
|
if (!isType(first)) throw new TypeError("sql() accepts a type, not a column");
|
|
2700
3115
|
return (strings, ...values) => createFragment(strings, values, first);
|
|
2701
3116
|
}
|
|
3117
|
+
/**
|
|
3118
|
+
* 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`.
|
|
3119
|
+
*
|
|
3120
|
+
* Several names make a qualified name, such as `identifier(schema, table)` for `"schema"."table"`.
|
|
3121
|
+
* The fragment has no parameters, so a script takes it.
|
|
3122
|
+
* The name is not qualified by the PostgreSQL schema of the statement.
|
|
3123
|
+
*/
|
|
3124
|
+
function identifier(...names) {
|
|
3125
|
+
let text = "";
|
|
3126
|
+
for (const name of names) {
|
|
3127
|
+
if (typeof name !== "string" || name === "") throw new TypeError("identifier() takes names as non-empty strings");
|
|
3128
|
+
text += (text === "" ? "" : ".") + quoteIdentifier(name);
|
|
3129
|
+
}
|
|
3130
|
+
if (text === "") throw new TypeError("identifier() takes at least one name");
|
|
3131
|
+
return toExpression(createNode({
|
|
3132
|
+
kind: "raw",
|
|
3133
|
+
fragments: [text],
|
|
3134
|
+
values: []
|
|
3135
|
+
}));
|
|
3136
|
+
}
|
|
2702
3137
|
function param(type, value) {
|
|
2703
3138
|
if (!isType(type) && !isExpression(type)) throw new TypeError("param() accepts a type or an expression, not a column");
|
|
2704
3139
|
if (isExpression(type)) {
|
|
@@ -2720,9 +3155,12 @@ function createFragment(strings, values, type) {
|
|
|
2720
3155
|
return toExpression(toRawSqlNode(strings, values), type);
|
|
2721
3156
|
}
|
|
2722
3157
|
/**
|
|
2723
|
-
* Creates the node of the SQL of a template
|
|
3158
|
+
* Creates the node of the SQL of a template, whose interpolations stay structured nodes.
|
|
2724
3159
|
*
|
|
2725
|
-
*
|
|
3160
|
+
* 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.
|
|
3161
|
+
* Another query of a table becomes a query in an expression, which lowering replaces with its SQL.
|
|
3162
|
+
* Other values become query parameters.
|
|
3163
|
+
* It rejects the other queries, which a parameter would encode as text.
|
|
2726
3164
|
*/
|
|
2727
3165
|
function toRawSqlNode(strings, values) {
|
|
2728
3166
|
const nodes = [];
|
|
@@ -2736,14 +3174,18 @@ function toRawSqlNode(strings, values) {
|
|
|
2736
3174
|
/** Returns the node of a value interpolated into SQL, as `toRawSqlNode` takes it. */
|
|
2737
3175
|
function toInterpolatedNode(value) {
|
|
2738
3176
|
if (value instanceof SelectQuery) {
|
|
2739
|
-
|
|
2740
|
-
return createNode({
|
|
3177
|
+
const { node } = value;
|
|
3178
|
+
return isTableQuery(node) ? createNode({
|
|
2741
3179
|
kind: "tableName",
|
|
2742
3180
|
table: value.table,
|
|
2743
3181
|
db: value.db
|
|
3182
|
+
}) : createNode({
|
|
3183
|
+
kind: "nestedQuery",
|
|
3184
|
+
query: node,
|
|
3185
|
+
db: value.db
|
|
2744
3186
|
});
|
|
2745
3187
|
}
|
|
2746
|
-
if (value instanceof RecordQuery || value instanceof MutationQuery || value instanceof SqlQuery) throw new TypeError("sql takes a query of a table
|
|
3188
|
+
if (value instanceof RecordQuery || value instanceof MutationQuery || value instanceof SqlQuery) throw new TypeError("sql takes a query of a table, such as db.form or a query of its records, not a query of $select, a mutation, or a query of SQL text");
|
|
2747
3189
|
return toValueNode(value);
|
|
2748
3190
|
}
|
|
2749
3191
|
// src/execution/db.ts
|
|
@@ -2777,10 +3219,13 @@ function bindSchema(schema, database, options = {}) {
|
|
|
2777
3219
|
async $script(script, ...values) {
|
|
2778
3220
|
if (typeof script === "string") return database.script(script);
|
|
2779
3221
|
const { text, parameters } = compile(startSqlQuery(toRawSqlNode(script, values), db));
|
|
2780
|
-
if (parameters.length > 0) throw new Error("A script
|
|
3222
|
+
if (parameters.length > 0) throw new Error("A script has no parameters, so it takes no values, also in the queries and sql fragments that it interpolates");
|
|
2781
3223
|
return database.script(text);
|
|
2782
3224
|
},
|
|
2783
|
-
$transaction: (callback, options) => database.transaction(callback, options)
|
|
3225
|
+
$transaction: (callback, options) => database.transaction(callback, options),
|
|
3226
|
+
$ensureTransaction: (callback) => database.ensureTransaction(callback),
|
|
3227
|
+
$isInTransaction: () => database.isInTransaction(),
|
|
3228
|
+
$lock: (key) => database.lock(key)
|
|
2784
3229
|
};
|
|
2785
3230
|
const contexts = createQueryContexts(Object.keys(schema.$tables), relations);
|
|
2786
3231
|
const db = defineEntries(methods, schema.$tables, (key) => bindQuery(startQuery(schema.$tables[key], contexts[key]), db));
|
|
@@ -2851,6 +3296,27 @@ var TransactionCoordinator = class {
|
|
|
2851
3296
|
return this.test ? Promise.reject(new Error(describeOutsideTest("start a transaction"))) : this.#runRoot(callback, true, void 0, settings);
|
|
2852
3297
|
}
|
|
2853
3298
|
/**
|
|
3299
|
+
* Runs `callback` in the current transaction, or in a new transaction outside of one.
|
|
3300
|
+
*
|
|
3301
|
+
* Joining starts no savepoint, so a rejection of `callback` does not undo its changes.
|
|
3302
|
+
* It rejects without calling `callback` when the current level accepts no statements, as a nested transaction would.
|
|
3303
|
+
*/
|
|
3304
|
+
async ensureTransaction(callback) {
|
|
3305
|
+
const level = this.#storage.getStore();
|
|
3306
|
+
if (!level?.inTransaction) return this.transaction(callback);
|
|
3307
|
+
const unavailable = createUnavailableError(level, "join the transaction");
|
|
3308
|
+
if (unavailable) throw unavailable;
|
|
3309
|
+
return callback();
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* Tells whether the current async context runs in a transaction of `transaction`.
|
|
3313
|
+
*
|
|
3314
|
+
* 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.
|
|
3315
|
+
*/
|
|
3316
|
+
isInTransaction() {
|
|
3317
|
+
return this.#storage.getStore()?.inTransaction ?? false;
|
|
3318
|
+
}
|
|
3319
|
+
/**
|
|
2854
3320
|
* Runs `callback` in a transaction that always rolls back, on a coordinator created for tests.
|
|
2855
3321
|
*
|
|
2856
3322
|
* Inside a transaction, it runs `callback` in a savepoint nested in it that always rolls back.
|
|
@@ -3017,7 +3483,7 @@ var TransactionCoordinator = class {
|
|
|
3017
3483
|
*/
|
|
3018
3484
|
async #seedFixture(fixture, seed) {
|
|
3019
3485
|
try {
|
|
3020
|
-
return await this.#run(await this.#startNested(fixture), seed, true);
|
|
3486
|
+
return await this.#run(await this.#startNested(fixture, false), seed, true);
|
|
3021
3487
|
} catch (error) {
|
|
3022
3488
|
return { rejection: { error } };
|
|
3023
3489
|
}
|
|
@@ -3069,7 +3535,7 @@ var TransactionCoordinator = class {
|
|
|
3069
3535
|
} catch (error) {
|
|
3070
3536
|
throw (await releaseConnection(connection, { error }, { error })).error;
|
|
3071
3537
|
}
|
|
3072
|
-
return createLevel(connection, void 0);
|
|
3538
|
+
return createLevel(connection, void 0, false);
|
|
3073
3539
|
}
|
|
3074
3540
|
/**
|
|
3075
3541
|
* Starts a nested level of a test fixture, preempting its active one.
|
|
@@ -3085,12 +3551,12 @@ var TransactionCoordinator = class {
|
|
|
3085
3551
|
const unavailable = createUnavailableError(fixture, "start a level of the test fixture");
|
|
3086
3552
|
if (unavailable) throw suppressAbortReason(unavailable, signal);
|
|
3087
3553
|
signal?.throwIfAborted();
|
|
3088
|
-
return this.#startNested(fixture, signal);
|
|
3554
|
+
return this.#startNested(fixture, false, signal);
|
|
3089
3555
|
}
|
|
3090
3556
|
async #runRoot(callback, commits, signal, settings) {
|
|
3091
3557
|
const connection = await (signal ? acquireUnlessAborted(this.driver, signal) : this.driver.acquire());
|
|
3092
3558
|
if (signal?.aborted) throw (await releaseConnection(connection, void 0, { error: signal.reason })).error;
|
|
3093
|
-
const level = createLevel(connection, void 0);
|
|
3559
|
+
const level = createLevel(connection, void 0, commits);
|
|
3094
3560
|
let outcome;
|
|
3095
3561
|
try {
|
|
3096
3562
|
await connection.execute(beginStatement);
|
|
@@ -3114,16 +3580,17 @@ var TransactionCoordinator = class {
|
|
|
3114
3580
|
async #runNested(parent, callback, commits, signal, settings) {
|
|
3115
3581
|
const unavailable = createUnavailableError(parent, "start a nested transaction");
|
|
3116
3582
|
if (unavailable) throw unavailable;
|
|
3117
|
-
return this.#runStarted(await this.#startNested(parent, signal, settings), callback, commits, signal);
|
|
3583
|
+
return this.#runStarted(await this.#startNested(parent, commits, signal, settings), callback, commits, signal);
|
|
3118
3584
|
}
|
|
3119
3585
|
/**
|
|
3120
3586
|
* Reserves a nested level of `parent`, sends its `SAVEPOINT`, and applies its `settings`, rejecting when one of them fails.
|
|
3121
3587
|
*
|
|
3588
|
+
* `transaction` tells whether the level is a transaction of `transaction` rather than a level of the test helpers.
|
|
3122
3589
|
* A level whose settings fail rolls back to its savepoint first.
|
|
3123
3590
|
* The failure suppresses the reason of `signal` when the signal aborted before it.
|
|
3124
3591
|
*/
|
|
3125
|
-
async #startNested(parent, signal, settings) {
|
|
3126
|
-
const level = createLevel(parent.connection, parent);
|
|
3592
|
+
async #startNested(parent, transaction, signal, settings) {
|
|
3593
|
+
const level = createLevel(parent.connection, parent, transaction);
|
|
3127
3594
|
parent.child = level;
|
|
3128
3595
|
try {
|
|
3129
3596
|
await level.connection.execute(createStatement(`SAVEPOINT ${getSavepointName(level)}`));
|
|
@@ -3210,11 +3677,12 @@ var TransactionCoordinator = class {
|
|
|
3210
3677
|
const beginStatement = createStatement("BEGIN");
|
|
3211
3678
|
const commitStatement = createStatement("COMMIT");
|
|
3212
3679
|
const rollbackStatement = createStatement("ROLLBACK");
|
|
3213
|
-
function createLevel(connection, parent) {
|
|
3680
|
+
function createLevel(connection, parent, transaction) {
|
|
3214
3681
|
return {
|
|
3215
3682
|
connection,
|
|
3216
3683
|
parent,
|
|
3217
3684
|
depth: parent ? parent.depth + 1 : 0,
|
|
3685
|
+
inTransaction: transaction || (parent?.inTransaction ?? false),
|
|
3218
3686
|
state: "starting",
|
|
3219
3687
|
child: void 0,
|
|
3220
3688
|
rollbackOnly: void 0,
|
|
@@ -3410,13 +3878,23 @@ function createDatabase({ driver, test }) {
|
|
|
3410
3878
|
script: (script, ...values) => typeof script === "string" ? coordinator.executeScript(script) : getTablelessDb().$script(script, ...values),
|
|
3411
3879
|
execute: (statement) => coordinator.execute(statement),
|
|
3412
3880
|
transaction: (callback, options) => coordinator.transaction(callback, options),
|
|
3881
|
+
ensureTransaction: (callback) => coordinator.ensureTransaction(callback),
|
|
3882
|
+
isInTransaction: () => coordinator.isInTransaction(),
|
|
3883
|
+
async lock(key) {
|
|
3884
|
+
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");
|
|
3885
|
+
await coordinator.execute(createLockStatement(key));
|
|
3886
|
+
},
|
|
3413
3887
|
close: () => coordinator.close(),
|
|
3414
3888
|
[Symbol.asyncDispose]: () => coordinator.close()
|
|
3415
3889
|
};
|
|
3416
3890
|
return database;
|
|
3417
3891
|
}
|
|
3892
|
+
/** Creates the statement that takes a transaction-level advisory lock on a 64-bit hash of `key`. */
|
|
3893
|
+
function createLockStatement(key) {
|
|
3894
|
+
return createStatement("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [key]);
|
|
3895
|
+
}
|
|
3418
3896
|
// src/query/helper.ts
|
|
3419
3897
|
function defineHelper(_table, helper) {
|
|
3420
3898
|
return (query, ...args) => requireDerived(query, helper(query, ...args), "The callback of a helper");
|
|
3421
3899
|
}
|
|
3422
|
-
export { NotFoundError, RequiredRelationError, ResultDecodeError, and, asc, compare, createDatabase, createStatement, defineFilter, defineGenericOperator, defineHelper, defineSchema, defineTable, defineType, desc, equalityOperators, from, many, not, one, or, orderBy, orderingOperators, param, sql, textOperators };
|
|
3900
|
+
export { NotFoundError, RequiredRelationError, ResultDecodeError, and, asc, compare, createDatabase, createStatement, defineFilter, defineGenericOperator, defineHelper, defineSchema, defineTable, defineType, desc, equalityOperators, from, identifier, many, not, one, or, orderBy, orderingOperators, param, sql, textOperators };
|