kern-orm 0.1.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/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as falseNode, C as equalityOperators, D as createList, E as defineGenericOperator, M as oneNode, N as trueNode, O as createNode, S as Column, T as isEqualityCompatible, _ as toNode, a as stringifyJson, b as defineType, c as textOperators, d as compareNodes, f as createIsNullNode, g as toExpression, h as toBooleanExpression, j as noFilters, k as createSelectNode, l as orderingOperators, m as isExpression, n as text, p as getType, s as encodeBytea, u as compare, v as toValueNode, x as isType } from "./text-4Xs09pIf.mjs";
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; an object of column values stays unresolved until `where`. */
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
- return isExpression(operand) ? toNode(operand) : createNode({
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": return;
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, definitions, db, true);
1376
- case "record": return lowerRecord(node, definitions, db);
1377
- case "text": return node;
1378
- default: return lowerMutation(node, definitions, db);
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 mutation to its statement, 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. */
1382
- function lowerMutation(node, definitions, db) {
1383
- const { returning } = node;
1384
- const lowered = returning === void 0 ? void 0 : lowerProjections(returning, definitions, db);
1385
- if (lowered === returning) return node;
1386
- const { target, rows, value } = node;
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": return Object.freeze({
1389
- kind: "insert",
1390
- target,
1391
- columns: node.columns,
1392
- values: node.values,
1393
- returning: lowered,
1394
- rows,
1395
- value
1396
- });
1397
- case "update": return Object.freeze({
1398
- kind: "update",
1399
- target,
1400
- set: node.set,
1401
- filters: node.filters,
1402
- returning: lowered,
1403
- rows,
1404
- value
1405
- });
1406
- case "delete": return Object.freeze({
1407
- kind: "delete",
1408
- target,
1409
- filters: node.filters,
1410
- returning: lowered,
1411
- rows,
1412
- value
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, definitions, db, positions) {
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 && projections !== node.projections ? lowerPage(node, projections, positions, guaranteedBy) : 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: node.filters,
1432
- orderBy: positions && node.orderBy.length > 0 ? sortByPositions(node.orderBy, projections) : node.orderBy,
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
- * Lowers `node` with `projections` to a statement that evaluates its nested queries over the rows of a derived page.
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 (isNestedSelection(node.projections[index])) {
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: node.filters,
1490
- orderBy: node.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 ? node.orderBy : noOrder;
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: node.filters,
1547
- orderBy: counted ? noOrder : node.orderBy,
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 { fallbacks } = field;
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": return false;
1723
+ case "exists":
1724
+ case "textSubquery": return false;
1613
1725
  case "cast":
1614
1726
  case "not":
1615
- case "isNull": return refersToSource(node.operand, source);
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, definitions, db) {
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, definitions, db),
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 nested queries, it returns `selected` itself.
1801
+ * Without queries, it returns `selected` itself.
1645
1802
  */
1646
- function lowerProjections(selected, definitions, db) {
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
- if (field.db !== void 0 && field.db !== db) throw new Error(db === void 0 ? `Selected field "${field.key}" is bound to a Db, so only that Db can compile the query` : `Selected field "${field.key}" is bound to another Db`);
1652
- lowered ??= selected.slice(0, index);
1653
- const query = lowerSelect(field.query, definitions, db, false);
1654
- lowered.push(isAggregateQuery(field.query) ? projectAggregateValue(field.key, query) : projectRows(field.key, query));
1655
- } else if (lowered !== void 0) lowered.push(field);
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 callback of `select` over `source`: its columns, and the relations of `context`. */
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(createColumnRefs(source)) : filter), source);
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` in ascending order, in the callback of `orderBy`. */
1847
- function asc(expression) {
1848
- return orderBy(expression, "asc");
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` in descending order, in the callback of `orderBy`. */
1851
- function desc(expression) {
1852
- return orderBy(expression, "desc");
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` in `direction`, such as `"desc nulls last"`, in the callback of `orderBy`. */
1855
- function orderBy(expression, direction) {
1856
- if (!isExpression(expression)) throw new TypeError("A sort key must be an expression");
1857
- return new OrderingObject(createOrderNode(toNode(expression), direction));
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(createColumnRefs(node.from), createSelectedRefs(node));
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
- /** Creates the sort key node of a sort key returned by the callback of `orderBy`. */
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 (isExpression(term)) return createOrderNode(toNode(term), "asc");
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)) Object.defineProperty(refs, key, {
1917
- get() {
1918
- throw new Error(`Field "${key}" holds a nested query and cannot be sorted by`);
1919
- },
1920
- enumerable: true
1921
- });
1922
- else refs[key] = toSelectedExpression(projection);
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
- return isAggregateQuery(node) ? "; call it before count or another aggregate to aggregate the rows of a page" : "; call it before take or get";
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
- if (isAggregateQuery(this.node)) throw new Error(`${method} cannot be used after an aggregate`);
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 an expression.
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: interpolated expressions keep their nodes, a query of a table without clauses becomes the name of the table, qualified by the PostgreSQL schema of the statement, and other values become query parameters.
3158
+ * Creates the node of the SQL of a template, whose interpolations stay structured nodes.
2724
3159
  *
2725
- * It rejects other queries, which a parameter would encode as text.
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
- if (!isTableQuery(value.node)) throw new TypeError("sql takes a query of a table without clauses, such as db.form, as the name of the table, not a query with clauses");
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 without clauses, such as db.form, as the name of the table, not another query");
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
@@ -2753,12 +3195,17 @@ function bindSchema(schema, database, options = {}) {
2753
3195
  const relations = schema[schemaRelations];
2754
3196
  const definitions = collectDefinitions(relations);
2755
3197
  const { schemaName } = options;
3198
+ const resolveSchemaName = () => typeof schemaName === "function" ? schemaName() : schemaName;
2756
3199
  function compile(query) {
2757
3200
  if (query.db !== void 0 && query.db !== db) throw new Error("Cannot run a query bound to another Db");
2758
- return compileStatement(lowerStatement(query.node, definitions, db), { schemaName: typeof schemaName === "function" ? schemaName() : schemaName }, tables, db);
3201
+ return compileStatement(lowerStatement(query.node, definitions, db), { schemaName: resolveSchemaName() }, tables, db);
2759
3202
  }
2760
3203
  const methods = {
2761
3204
  $schema: schema,
3205
+ $database: database,
3206
+ get $schemaName() {
3207
+ return resolveSchemaName();
3208
+ },
2762
3209
  $compile(query) {
2763
3210
  const { text, parameters } = compile(query);
2764
3211
  return createStatement(text, parameters);
@@ -2772,10 +3219,13 @@ function bindSchema(schema, database, options = {}) {
2772
3219
  async $script(script, ...values) {
2773
3220
  if (typeof script === "string") return database.script(script);
2774
3221
  const { text, parameters } = compile(startSqlQuery(toRawSqlNode(script, values), db));
2775
- if (parameters.length > 0) throw new Error("A script takes no values, since it has no parameters; interpolate only tables and sql fragments without values");
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");
2776
3223
  return database.script(text);
2777
3224
  },
2778
- $transaction: (callback) => database.transaction(callback)
3225
+ $transaction: (callback, options) => database.transaction(callback, options),
3226
+ $ensureTransaction: (callback) => database.ensureTransaction(callback),
3227
+ $isInTransaction: () => database.isInTransaction(),
3228
+ $lock: (key) => database.lock(key)
2779
3229
  };
2780
3230
  const contexts = createQueryContexts(Object.keys(schema.$tables), relations);
2781
3231
  const db = defineEntries(methods, schema.$tables, (key) => bindQuery(startQuery(schema.$tables[key], contexts[key]), db));
@@ -2833,16 +3283,38 @@ var TransactionCoordinator = class {
2833
3283
  return createUnavailableError(level, operation) ?? level.connection;
2834
3284
  }
2835
3285
  /**
2836
- * Runs `callback` in a transaction that commits when it resolves and rolls back when it rejects.
3286
+ * Runs `callback` in a transaction that commits when it resolves and rolls back when it rejects, with the settings of `options`.
2837
3287
  *
2838
- * Inside a transaction, it runs `callback` in a savepoint nested in it.
3288
+ * Inside a transaction, it runs `callback` in a savepoint nested in it, which restores the settings it changed when it ends.
2839
3289
  * A failed cleanup after an error rejects with a `SuppressedError` that keeps the original error in its `suppressed` chain.
2840
3290
  */
2841
- transaction(callback) {
3291
+ transaction(callback, options) {
3292
+ const settings = getLevelSettings(options);
2842
3293
  const parent = this.#storage.getStore();
2843
- if (parent) return this.#runNested(parent, callback, true);
3294
+ if (parent) return this.#runNested(parent, callback, true, void 0, settings);
2844
3295
  if (this.#closed) return Promise.reject(createDatabaseClosedError("start a transaction"));
2845
- return this.test ? Promise.reject(new Error(describeOutsideTest("start a transaction"))) : this.#runRoot(callback, true);
3296
+ return this.test ? Promise.reject(new Error(describeOutsideTest("start a transaction"))) : this.#runRoot(callback, true, void 0, settings);
3297
+ }
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;
2846
3318
  }
2847
3319
  /**
2848
3320
  * Runs `callback` in a transaction that always rolls back, on a coordinator created for tests.
@@ -3011,7 +3483,7 @@ var TransactionCoordinator = class {
3011
3483
  */
3012
3484
  async #seedFixture(fixture, seed) {
3013
3485
  try {
3014
- return await this.#run(await this.#startNested(fixture), seed, true);
3486
+ return await this.#run(await this.#startNested(fixture, false), seed, true);
3015
3487
  } catch (error) {
3016
3488
  return { rejection: { error } };
3017
3489
  }
@@ -3063,7 +3535,7 @@ var TransactionCoordinator = class {
3063
3535
  } catch (error) {
3064
3536
  throw (await releaseConnection(connection, { error }, { error })).error;
3065
3537
  }
3066
- return createLevel(connection, void 0);
3538
+ return createLevel(connection, void 0, false);
3067
3539
  }
3068
3540
  /**
3069
3541
  * Starts a nested level of a test fixture, preempting its active one.
@@ -3079,12 +3551,12 @@ var TransactionCoordinator = class {
3079
3551
  const unavailable = createUnavailableError(fixture, "start a level of the test fixture");
3080
3552
  if (unavailable) throw suppressAbortReason(unavailable, signal);
3081
3553
  signal?.throwIfAborted();
3082
- return this.#startNested(fixture, signal);
3554
+ return this.#startNested(fixture, false, signal);
3083
3555
  }
3084
- async #runRoot(callback, commits, signal) {
3556
+ async #runRoot(callback, commits, signal, settings) {
3085
3557
  const connection = await (signal ? acquireUnlessAborted(this.driver, signal) : this.driver.acquire());
3086
3558
  if (signal?.aborted) throw (await releaseConnection(connection, void 0, { error: signal.reason })).error;
3087
- const level = createLevel(connection, void 0);
3559
+ const level = createLevel(connection, void 0, commits);
3088
3560
  let outcome;
3089
3561
  try {
3090
3562
  await connection.execute(beginStatement);
@@ -3095,23 +3567,30 @@ var TransactionCoordinator = class {
3095
3567
  failure: { error }
3096
3568
  };
3097
3569
  }
3570
+ if (!outcome && settings) try {
3571
+ await applySettings(level, settings);
3572
+ } catch (error) {
3573
+ outcome = await getEndedOutcome(this.#end(level, false), error);
3574
+ }
3098
3575
  outcome ??= await this.#run(level, callback, commits, signal);
3099
3576
  const rejection = await releaseConnection(connection, outcome.failure, outcome.rejection);
3100
3577
  if (rejection) throw rejection.error;
3101
3578
  return outcome.result;
3102
3579
  }
3103
- async #runNested(parent, callback, commits, signal) {
3580
+ async #runNested(parent, callback, commits, signal, settings) {
3104
3581
  const unavailable = createUnavailableError(parent, "start a nested transaction");
3105
3582
  if (unavailable) throw unavailable;
3106
- return this.#runStarted(await this.#startNested(parent, signal), callback, commits, signal);
3583
+ return this.#runStarted(await this.#startNested(parent, commits, signal, settings), callback, commits, signal);
3107
3584
  }
3108
3585
  /**
3109
- * Reserves a nested level of `parent` and sends its `SAVEPOINT`, rejecting when it fails.
3586
+ * Reserves a nested level of `parent`, sends its `SAVEPOINT`, and applies its `settings`, rejecting when one of them fails.
3110
3587
  *
3588
+ * `transaction` tells whether the level is a transaction of `transaction` rather than a level of the test helpers.
3589
+ * A level whose settings fail rolls back to its savepoint first.
3111
3590
  * The failure suppresses the reason of `signal` when the signal aborted before it.
3112
3591
  */
3113
- async #startNested(parent, signal) {
3114
- const level = createLevel(parent.connection, parent);
3592
+ async #startNested(parent, transaction, signal, settings) {
3593
+ const level = createLevel(parent.connection, parent, transaction);
3115
3594
  parent.child = level;
3116
3595
  try {
3117
3596
  await level.connection.execute(createStatement(`SAVEPOINT ${getSavepointName(level)}`));
@@ -3122,6 +3601,11 @@ var TransactionCoordinator = class {
3122
3601
  }
3123
3602
  throw suppressAbortReason(combineRejections({ error }, await level.cleanup).error, signal);
3124
3603
  }
3604
+ if (settings) try {
3605
+ await applySettings(level, settings);
3606
+ } catch (error) {
3607
+ throw suppressAbortReason(combineRejections({ error }, await this.#end(level, false)).error, signal);
3608
+ }
3125
3609
  return level;
3126
3610
  }
3127
3611
  /** Runs `callback` in a started level, returning its result or throwing its rejection. */
@@ -3193,18 +3677,60 @@ var TransactionCoordinator = class {
3193
3677
  const beginStatement = createStatement("BEGIN");
3194
3678
  const commitStatement = createStatement("COMMIT");
3195
3679
  const rollbackStatement = createStatement("ROLLBACK");
3196
- function createLevel(connection, parent) {
3680
+ function createLevel(connection, parent, transaction) {
3197
3681
  return {
3198
3682
  connection,
3199
3683
  parent,
3200
3684
  depth: parent ? parent.depth + 1 : 0,
3685
+ inTransaction: transaction || (parent?.inTransaction ?? false),
3201
3686
  state: "starting",
3202
3687
  child: void 0,
3203
3688
  rollbackOnly: void 0,
3204
3689
  cleanup: void 0,
3205
- endedBy: void 0
3690
+ endedBy: void 0,
3691
+ restore: void 0
3206
3692
  };
3207
3693
  }
3694
+ /** Settings of a level of `transaction` with `options`, or `undefined` when it has none. */
3695
+ function getLevelSettings(options) {
3696
+ return options?.searchPath ? [{
3697
+ name: "search_path",
3698
+ value: options.searchPath.map(quoteIdentifier).join(", ")
3699
+ }] : void 0;
3700
+ }
3701
+ /**
3702
+ * Applies `settings` to a level whose `BEGIN` or `SAVEPOINT` completed, unless its cleanup has started.
3703
+ *
3704
+ * 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.
3705
+ */
3706
+ async function applySettings(level, settings) {
3707
+ if (level.cleanup) return;
3708
+ if (level.parent) {
3709
+ let text = "SELECT";
3710
+ const names = [];
3711
+ for (const { name } of settings) {
3712
+ names.push(name);
3713
+ text += `${names.length > 1 ? "," : ""} current_setting($${names.length})`;
3714
+ }
3715
+ const values = (await level.connection.execute(createStatement(text, names))).rows[0];
3716
+ level.restore = createSettingStatement(settings.map(({ name }, index) => ({
3717
+ name,
3718
+ value: values[index]
3719
+ })));
3720
+ if (level.cleanup) return;
3721
+ }
3722
+ await level.connection.execute(createSettingStatement(settings));
3723
+ }
3724
+ /** Creates the statement that sets `settings` until the transaction ends or rolls back to an earlier savepoint, as `SET LOCAL` does. */
3725
+ function createSettingStatement(settings) {
3726
+ let text = "SELECT";
3727
+ const parameters = [];
3728
+ for (const { name, value } of settings) {
3729
+ parameters.push(name, value);
3730
+ text += `${parameters.length > 2 ? "," : ""} set_config($${parameters.length - 1}, $${parameters.length}, true)`;
3731
+ }
3732
+ return createStatement(text, parameters);
3733
+ }
3208
3734
  /** Name of the savepoint of a nested level; a level has at most one nested level, so the depth makes it unique. */
3209
3735
  function getSavepointName(level) {
3210
3736
  return `kern_${level.depth}`;
@@ -3315,11 +3841,16 @@ async function endTransaction(connection, commit) {
3315
3841
  };
3316
3842
  }
3317
3843
  }
3318
- /** Ends the savepoint of a nested level, releasing it on commit and rolling back to it otherwise. */
3844
+ /**
3845
+ * Ends the savepoint of a nested level, releasing it on commit and rolling back to it otherwise.
3846
+ *
3847
+ * Before a release, it restores the settings that the level changed; a rollback restores them itself.
3848
+ */
3319
3849
  async function endSavepoint(level, commit) {
3320
3850
  const name = getSavepointName(level);
3321
3851
  try {
3322
3852
  if (!commit) await level.connection.execute(createStatement(`ROLLBACK TO SAVEPOINT ${name}`));
3853
+ else if (level.restore) await level.connection.execute(level.restore);
3323
3854
  await level.connection.execute(createStatement(`RELEASE SAVEPOINT ${name}`));
3324
3855
  return {};
3325
3856
  } catch (error) {
@@ -3346,14 +3877,24 @@ function createDatabase({ driver, test }) {
3346
3877
  query: (strings, ...values) => getTablelessDb().$query(strings, ...values),
3347
3878
  script: (script, ...values) => typeof script === "string" ? coordinator.executeScript(script) : getTablelessDb().$script(script, ...values),
3348
3879
  execute: (statement) => coordinator.execute(statement),
3349
- transaction: (callback) => coordinator.transaction(callback),
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
+ },
3350
3887
  close: () => coordinator.close(),
3351
3888
  [Symbol.asyncDispose]: () => coordinator.close()
3352
3889
  };
3353
3890
  return database;
3354
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
+ }
3355
3896
  // src/query/helper.ts
3356
3897
  function defineHelper(_table, helper) {
3357
3898
  return (query, ...args) => requireDerived(query, helper(query, ...args), "The callback of a helper");
3358
3899
  }
3359
- 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 };