linkgress-orm 0.4.76 → 0.4.79
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/config/linkgress-config.d.ts +23 -0
- package/dist/config/linkgress-config.d.ts.map +1 -1
- package/dist/config/linkgress-config.js +29 -0
- package/dist/config/linkgress-config.js.map +1 -1
- package/dist/entity/db-context.d.ts +45 -0
- package/dist/entity/db-context.d.ts.map +1 -1
- package/dist/entity/db-context.js +59 -1
- package/dist/entity/db-context.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/query/collection-strategy.interface.d.ts +14 -0
- package/dist/query/collection-strategy.interface.d.ts.map +1 -1
- package/dist/query/conditions.d.ts +36 -0
- package/dist/query/conditions.d.ts.map +1 -1
- package/dist/query/conditions.js +78 -10
- package/dist/query/conditions.js.map +1 -1
- package/dist/query/lateral-sql-cache.d.ts +53 -0
- package/dist/query/lateral-sql-cache.d.ts.map +1 -0
- package/dist/query/lateral-sql-cache.js +63 -0
- package/dist/query/lateral-sql-cache.js.map +1 -0
- package/dist/query/mutation-batch.d.ts +22 -0
- package/dist/query/mutation-batch.d.ts.map +1 -1
- package/dist/query/mutation-batch.js +30 -1
- package/dist/query/mutation-batch.js.map +1 -1
- package/dist/query/query-builder.d.ts +18 -1
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +203 -59
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/sql-utils.d.ts +12 -0
- package/dist/query/sql-utils.d.ts.map +1 -1
- package/dist/query/sql-utils.js +22 -2
- package/dist/query/sql-utils.js.map +1 -1
- package/dist/query/strategies/lateral-collection-strategy.d.ts +28 -3
- package/dist/query/strategies/lateral-collection-strategy.d.ts.map +1 -1
- package/dist/query/strategies/lateral-collection-strategy.js +161 -180
- package/dist/query/strategies/lateral-collection-strategy.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { TableSchema } from '../schema/table-builder';
|
|
2
2
|
export declare const renumberPlaceholders: (sqlText: string, offset: number) => string;
|
|
3
|
+
/**
|
|
4
|
+
* True when the SQL fragment contains a bare `$N` placeholder OUTSIDE quoted
|
|
5
|
+
* segments and comments — i.e. one {@link renumberPlaceholders} would rebind.
|
|
6
|
+
* Same quote/comment-aware scan, so `'literal $1 inside quotes'` and a `$1`
|
|
7
|
+
* sitting in a `--` comment stay invisible.
|
|
8
|
+
*
|
|
9
|
+
* Reads the regex's NAMED `digits` group rather than a positional index: the
|
|
10
|
+
* token classes of QUOTED_OR_PLACEHOLDER have grown before (comments were
|
|
11
|
+
* added after the literals), and a positional check silently points at the
|
|
12
|
+
* wrong group when that happens.
|
|
13
|
+
*/
|
|
14
|
+
export declare const hasBarePlaceholder: (sqlText: string) => boolean;
|
|
3
15
|
/**
|
|
4
16
|
* Column configuration extracted from schema
|
|
5
17
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-utils.d.ts","sourceRoot":"","sources":["../../src/query/sql-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA0B3D,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,EAAE,QAAQ,MAAM,KAAG,MAGpE,CAAC;AAEJ;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC;QAC9B,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC;KACjC,CAAC;IACF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,GAAG,EAAE,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAIjE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAIpE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAMxF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,GAAG,GAAG,CAKzE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,GAAG,GAAG,CAI3E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,EAAE,CAS/D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAW3F;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AAEzC;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,EACnB,eAAe,CAAC,EAAE,MAAM,GACvB,MAAM,CAMR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,oBAAoB,GAAE,OAAe,GACpC,YAAY,EAAE,CAoBhB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAChC,MAAM,EAAE,WAAW,EACnB,oBAAoB,GAAE,OAAe,GACpC,MAAM,EAAE,CAoCV;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,EAChC,aAAa,EAAE,YAAY,EAAE,EAC7B,eAAe,GAAE,MAAU,GAC1B,kBAAkB,CAuBpB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,MAAM,EAAE,EACzB,aAAa,EAAE,MAAM,EAAE,EACvB,MAAM,EAAE,WAAW,EACnB,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAoCR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAO3E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO7E"}
|
|
1
|
+
{"version":3,"file":"sql-utils.d.ts","sourceRoot":"","sources":["../../src/query/sql-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/query/sql-utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.POSTGRES_MAX_PARAMS = exports.renumberPlaceholders = void 0;
|
|
3
|
+
exports.POSTGRES_MAX_PARAMS = exports.hasBarePlaceholder = exports.renumberPlaceholders = void 0;
|
|
4
4
|
exports.getQualifiedTableName = getQualifiedTableName;
|
|
5
5
|
exports.buildReturningColumnList = buildReturningColumnList;
|
|
6
6
|
exports.buildColumnNamesList = buildColumnNamesList;
|
|
@@ -37,9 +37,29 @@ exports.getDbColumnName = getDbColumnName;
|
|
|
37
37
|
* Shared by QueryBatch, MutationBatch and insertWithChildren for cross-leg
|
|
38
38
|
* parameter renumbering.
|
|
39
39
|
*/
|
|
40
|
-
const QUOTED_OR_PLACEHOLDER = /('(?:[^']|'')*')|("(?:[^"]|"")*")|(--[^\n]*)|(\/\*[\s\S]*?\*\/)|\$(
|
|
40
|
+
const QUOTED_OR_PLACEHOLDER = /('(?:[^']|'')*')|("(?:[^"]|"")*")|(--[^\n]*)|(\/\*[\s\S]*?\*\/)|\$(?<digits>\d+)/g;
|
|
41
41
|
const renumberPlaceholders = (sqlText, offset) => sqlText.replace(QUOTED_OR_PLACEHOLDER, (match, _single, _dbl, _lineComment, _blockComment, digits) => digits !== undefined ? `$${Number(digits) + offset}` : match);
|
|
42
42
|
exports.renumberPlaceholders = renumberPlaceholders;
|
|
43
|
+
/**
|
|
44
|
+
* True when the SQL fragment contains a bare `$N` placeholder OUTSIDE quoted
|
|
45
|
+
* segments and comments — i.e. one {@link renumberPlaceholders} would rebind.
|
|
46
|
+
* Same quote/comment-aware scan, so `'literal $1 inside quotes'` and a `$1`
|
|
47
|
+
* sitting in a `--` comment stay invisible.
|
|
48
|
+
*
|
|
49
|
+
* Reads the regex's NAMED `digits` group rather than a positional index: the
|
|
50
|
+
* token classes of QUOTED_OR_PLACEHOLDER have grown before (comments were
|
|
51
|
+
* added after the literals), and a positional check silently points at the
|
|
52
|
+
* wrong group when that happens.
|
|
53
|
+
*/
|
|
54
|
+
const hasBarePlaceholder = (sqlText) => {
|
|
55
|
+
for (const match of sqlText.matchAll(QUOTED_OR_PLACEHOLDER)) {
|
|
56
|
+
if (match.groups?.digits !== undefined) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
};
|
|
62
|
+
exports.hasBarePlaceholder = hasBarePlaceholder;
|
|
43
63
|
/**
|
|
44
64
|
* Get qualified table name with schema prefix if specified
|
|
45
65
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-utils.js","sourceRoot":"","sources":["../../src/query/sql-utils.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"sql-utils.js","sourceRoot":"","sources":["../../src/query/sql-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { ICollectionStrategy, CollectionStrategyType, CollectionAggregationConfig, CollectionAggregationResult } from '../collection-strategy.interface';
|
|
2
2
|
import { QueryContext } from '../query-builder';
|
|
3
|
+
/**
|
|
4
|
+
* The LateralSqlCache key of an aggregation: every input `LateralCollectionStrategy.render` and
|
|
5
|
+
* its helpers read, in order — the alias inputs (counter, relation, tables), the correlation
|
|
6
|
+
* columns, the clause TEXT (which already carries the `$n` placeholder numbering), the scalar
|
|
7
|
+
* flags and limits, both navigation-join lists with their alias-map resolution, and the
|
|
8
|
+
* projection. Parameter VALUES never enter the strategy and never enter the key. Built by plain
|
|
9
|
+
* concatenation (a rope V8 flattens once, on the lookup) — cheaper than an array join.
|
|
10
|
+
*/
|
|
11
|
+
export declare const lateralShapeKey: (config: CollectionAggregationConfig, context: QueryContext) => string;
|
|
3
12
|
/**
|
|
4
13
|
* LATERAL JOIN-based collection strategy
|
|
5
14
|
*
|
|
@@ -57,6 +66,8 @@ export declare class LateralCollectionStrategy implements ICollectionStrategy {
|
|
|
57
66
|
*/
|
|
58
67
|
private buildParentCorrelation;
|
|
59
68
|
buildAggregation(config: CollectionAggregationConfig, context: QueryContext): CollectionAggregationResult;
|
|
69
|
+
/** The uncached rendering behind {@link buildAggregation}. */
|
|
70
|
+
private render;
|
|
60
71
|
/**
|
|
61
72
|
* Check if we can use a correlated subquery instead of LATERAL
|
|
62
73
|
* This is more efficient for simple cases without LIMIT/OFFSET/ORDER BY
|
|
@@ -69,10 +80,24 @@ export declare class LateralCollectionStrategy implements ICollectionStrategy {
|
|
|
69
80
|
*/
|
|
70
81
|
private buildCorrelatedSubqueryAggregation;
|
|
71
82
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
83
|
+
* Rewrites the collection marker alias (`"__collection_<table>__".`) to the lateral's inner
|
|
84
|
+
* alias. The regex pass runs only when the marker is present at all.
|
|
85
|
+
*/
|
|
86
|
+
private rewriteMarker;
|
|
87
|
+
/**
|
|
88
|
+
* One select-list entry for a leaf field. A bare quoted column (`"col"`, no dot, no inner
|
|
89
|
+
* quote) is qualified with the inner alias; anything else has the marker rewritten and is
|
|
90
|
+
* aliased unless it already renders as exactly `"<alias>"`.
|
|
91
|
+
*/
|
|
92
|
+
private renderLeafSelect;
|
|
93
|
+
/**
|
|
94
|
+
* Renders the selected fields in ONE recursive pass: returns the `json_build_object(...)` text
|
|
95
|
+
* of this level and appends the flattened inner select list and the nested lateral joins to
|
|
96
|
+
* `out` (same order as the three separate walks this replaces — depth first, own join before
|
|
97
|
+
* the nested ones). No intermediate arrays: the select list of a wide collection projection was
|
|
98
|
+
* three allocations per field and three joins per statement.
|
|
74
99
|
*/
|
|
75
|
-
private
|
|
100
|
+
private renderFields;
|
|
76
101
|
/**
|
|
77
102
|
* Build navigation JOINs SQL for multi-level navigation in collection queries
|
|
78
103
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lateral-collection-strategy.d.ts","sourceRoot":"","sources":["../../../src/query/strategies/lateral-collection-strategy.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAG5B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"lateral-collection-strategy.d.ts","sourceRoot":"","sources":["../../../src/query/strategies/lateral-collection-strategy.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAG5B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA4ChD;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,2BAA2B,EAAE,SAAS,YAAY,KAAG,MAc5F,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,yBAA0B,YAAW,mBAAmB;IACnE,OAAO,IAAI,sBAAsB;IAIjC,iBAAiB,IAAI,OAAO;IAK5B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,sBAAsB;IAa9B,gBAAgB,CACd,MAAM,EAAE,2BAA2B,EACnC,OAAO,EAAE,YAAY,GACpB,2BAA2B;IAqC9B,8DAA8D;IAC9D,OAAO,CAAC,MAAM;IAmEd;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAsBhC;;;;OAIG;IACH,OAAO,CAAC,kCAAkC;IA4H1C;;;OAGG;IACH,OAAO,CAAC,aAAa;IAOrB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAqBpB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA6B5B;;;;;;;OAOG;IACH,OAAO,CAAC,6BAA6B;IA0DrC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA6D7B;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IA+ClC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAwF7B;;OAEG;IACH,OAAO,CAAC,sBAAsB;CAuE/B"}
|
|
@@ -1,8 +1,61 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LateralCollectionStrategy = void 0;
|
|
3
|
+
exports.LateralCollectionStrategy = exports.lateralShapeKey = void 0;
|
|
4
4
|
const query_utils_1 = require("../query-utils");
|
|
5
5
|
const join_utils_1 = require("../join-utils");
|
|
6
|
+
const lateral_sql_cache_1 = require("../lateral-sql-cache");
|
|
7
|
+
/** Key-part separator: a control character no alias, column name or SQL text contains. */
|
|
8
|
+
const KEY_SEP = String.fromCharCode(1);
|
|
9
|
+
/** A column list as one key part; the one-column case (nearly every FK) needs no join allocation. */
|
|
10
|
+
const listKey = (list) => (list === undefined ? '' : list.length === 1 ? list[0] : list.join(','));
|
|
11
|
+
/** Appends what `buildNavigationJoinsWithAlias` reads from each join, including the alias-map resolution of its source. */
|
|
12
|
+
const appendNavigationJoinsKey = (key, joins, aliasMap) => {
|
|
13
|
+
if (!joins) {
|
|
14
|
+
return key + '-' + KEY_SEP;
|
|
15
|
+
}
|
|
16
|
+
key += joins.length + KEY_SEP;
|
|
17
|
+
for (const join of joins) {
|
|
18
|
+
key += join.alias + KEY_SEP + join.targetTable + KEY_SEP + (join.targetSchema ?? '') + KEY_SEP
|
|
19
|
+
+ listKey(join.foreignKeys) + KEY_SEP + listKey(join.matches) + KEY_SEP + (join.isMandatory ? 'I' : 'L') + KEY_SEP
|
|
20
|
+
+ join.sourceAlias + KEY_SEP + (aliasMap?.get(join.sourceAlias) ?? '') + KEY_SEP;
|
|
21
|
+
}
|
|
22
|
+
return key;
|
|
23
|
+
};
|
|
24
|
+
/** Appends the projection: aliases, expressions, nesting, and nested laterals by memo id (or by text when not memoised). */
|
|
25
|
+
const appendFieldsKey = (key, fields) => {
|
|
26
|
+
key += '[' + fields.length + KEY_SEP;
|
|
27
|
+
for (const field of fields) {
|
|
28
|
+
key += field.alias + KEY_SEP + (field.expression ?? '') + KEY_SEP + (field.isColumn === true ? 'c' : 'e') + KEY_SEP;
|
|
29
|
+
if (field.nestedCteJoin) {
|
|
30
|
+
key += field.nestedCteJoin.cteName + KEY_SEP + (field.nestedCteJoin.memoId ? '#' + field.nestedCteJoin.memoId : field.nestedCteJoin.joinClause) + KEY_SEP;
|
|
31
|
+
}
|
|
32
|
+
key = field.nested ? appendFieldsKey(key, field.nested) : key + '-' + KEY_SEP;
|
|
33
|
+
}
|
|
34
|
+
return key + ']' + KEY_SEP;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The LateralSqlCache key of an aggregation: every input `LateralCollectionStrategy.render` and
|
|
38
|
+
* its helpers read, in order — the alias inputs (counter, relation, tables), the correlation
|
|
39
|
+
* columns, the clause TEXT (which already carries the `$n` placeholder numbering), the scalar
|
|
40
|
+
* flags and limits, both navigation-join lists with their alias-map resolution, and the
|
|
41
|
+
* projection. Parameter VALUES never enter the strategy and never enter the key. Built by plain
|
|
42
|
+
* concatenation (a rope V8 flattens once, on the lookup) — cheaper than an array join.
|
|
43
|
+
*/
|
|
44
|
+
const lateralShapeKey = (config, context) => {
|
|
45
|
+
const aliasMap = context.lateralTableAliasMap;
|
|
46
|
+
let key = config.counter + KEY_SEP + config.relationName + KEY_SEP + config.targetTable + KEY_SEP + config.foreignKey + KEY_SEP
|
|
47
|
+
+ listKey(config.foreignKeys) + KEY_SEP + listKey(config.matches) + KEY_SEP + (config.foreignKeyTableAlias ?? '') + KEY_SEP
|
|
48
|
+
+ config.sourceTable + KEY_SEP + (aliasMap?.get(config.sourceTable) ?? '') + KEY_SEP
|
|
49
|
+
+ (config.whereClause ?? '') + KEY_SEP + (config.orderByClause ?? '') + KEY_SEP + (config.orderByClauseAlias ?? '') + KEY_SEP
|
|
50
|
+
+ (config.limitValue ?? '') + KEY_SEP + (config.offsetValue ?? '') + KEY_SEP
|
|
51
|
+
+ (config.isDistinct === true ? 'D' : '') + (config.isSingleResult === true ? 'S' : '') + (config.useJsonArrayAggregation === true ? 'J' : '') + KEY_SEP
|
|
52
|
+
+ config.aggregationType + KEY_SEP + (config.aggregateField ?? '') + KEY_SEP + (config.aggregateExpression ?? '') + KEY_SEP
|
|
53
|
+
+ (config.arrayField ?? '') + KEY_SEP + config.defaultValue + KEY_SEP;
|
|
54
|
+
key = appendNavigationJoinsKey(key, config.navigationJoins, aliasMap);
|
|
55
|
+
key = appendNavigationJoinsKey(key, config.selectorNavigationJoins, aliasMap);
|
|
56
|
+
return appendFieldsKey(key, config.selectedFields);
|
|
57
|
+
};
|
|
58
|
+
exports.lateralShapeKey = lateralShapeKey;
|
|
6
59
|
/**
|
|
7
60
|
* LATERAL JOIN-based collection strategy
|
|
8
61
|
*
|
|
@@ -71,6 +124,38 @@ class LateralCollectionStrategy {
|
|
|
71
124
|
return `"${fkTableAlias}"."${foreignKey}" = "${sourceAlias}"."id"`;
|
|
72
125
|
}
|
|
73
126
|
buildAggregation(config, context) {
|
|
127
|
+
// Memoised per shape (see LateralSqlCache): the rendering below reads nothing but the
|
|
128
|
+
// config and the enclosing alias map, and mutates neither, so a hit is a pure lookup.
|
|
129
|
+
if (!lateral_sql_cache_1.LateralSqlCache.isEnabled()) {
|
|
130
|
+
return this.render(config, context);
|
|
131
|
+
}
|
|
132
|
+
const key = (0, exports.lateralShapeKey)(config, context);
|
|
133
|
+
const hit = lateral_sql_cache_1.LateralSqlCache.get(key);
|
|
134
|
+
if (hit) {
|
|
135
|
+
return {
|
|
136
|
+
sql: hit.sql,
|
|
137
|
+
params: context.allParams,
|
|
138
|
+
tableName: hit.tableName,
|
|
139
|
+
joinClause: hit.joinClause,
|
|
140
|
+
selectExpression: hit.selectExpression,
|
|
141
|
+
isCTE: false,
|
|
142
|
+
memoId: hit.id,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const rendered = this.render(config, context);
|
|
146
|
+
const entry = lateral_sql_cache_1.LateralSqlCache.store(key, {
|
|
147
|
+
sql: rendered.sql,
|
|
148
|
+
joinClause: rendered.joinClause,
|
|
149
|
+
selectExpression: rendered.selectExpression,
|
|
150
|
+
tableName: rendered.tableName,
|
|
151
|
+
});
|
|
152
|
+
if (entry.id > 0) {
|
|
153
|
+
rendered.memoId = entry.id;
|
|
154
|
+
}
|
|
155
|
+
return rendered;
|
|
156
|
+
}
|
|
157
|
+
/** The uncached rendering behind {@link buildAggregation}. */
|
|
158
|
+
render(config, context) {
|
|
74
159
|
const lateralAlias = `lateral_${config.counter}`;
|
|
75
160
|
// Optimization: For simple aggregations without LIMIT/OFFSET/ORDER BY,
|
|
76
161
|
// use a correlated subquery in SELECT instead of LATERAL JOIN.
|
|
@@ -264,20 +349,60 @@ WHERE ${whereSQL})`;
|
|
|
264
349
|
};
|
|
265
350
|
}
|
|
266
351
|
/**
|
|
267
|
-
*
|
|
268
|
-
*
|
|
352
|
+
* Rewrites the collection marker alias (`"__collection_<table>__".`) to the lateral's inner
|
|
353
|
+
* alias. The regex pass runs only when the marker is present at all.
|
|
269
354
|
*/
|
|
270
|
-
|
|
271
|
-
|
|
355
|
+
rewriteMarker(expression, targetTable, innerTableAlias) {
|
|
356
|
+
if (!expression.includes('"__collection_')) {
|
|
357
|
+
return expression;
|
|
358
|
+
}
|
|
359
|
+
return expression.replace((0, query_utils_1.collectionMarkerPattern)(targetTable, true), '"' + innerTableAlias + '".');
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* One select-list entry for a leaf field. A bare quoted column (`"col"`, no dot, no inner
|
|
363
|
+
* quote) is qualified with the inner alias; anything else has the marker rewritten and is
|
|
364
|
+
* aliased unless it already renders as exactly `"<alias>"`.
|
|
365
|
+
*/
|
|
366
|
+
renderLeafSelect(expression, fullAlias, targetTable, innerTableAlias) {
|
|
367
|
+
const length = expression.length;
|
|
368
|
+
const isSimpleColumn = length > 2
|
|
369
|
+
&& expression.charCodeAt(0) === 34
|
|
370
|
+
&& expression.charCodeAt(length - 1) === 34
|
|
371
|
+
&& expression.indexOf('"', 1) === length - 1
|
|
372
|
+
&& expression.indexOf('.') === -1;
|
|
373
|
+
if (isSimpleColumn) {
|
|
374
|
+
return '"' + innerTableAlias + '".' + expression + ' as "' + fullAlias + '"';
|
|
375
|
+
}
|
|
376
|
+
const rewritten = this.rewriteMarker(expression, targetTable, innerTableAlias);
|
|
377
|
+
return rewritten === '"' + fullAlias + '"' ? rewritten : rewritten + ' as "' + fullAlias + '"';
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Renders the selected fields in ONE recursive pass: returns the `json_build_object(...)` text
|
|
381
|
+
* of this level and appends the flattened inner select list and the nested lateral joins to
|
|
382
|
+
* `out` (same order as the three separate walks this replaces — depth first, own join before
|
|
383
|
+
* the nested ones). No intermediate arrays: the select list of a wide collection projection was
|
|
384
|
+
* three allocations per field and three joins per statement.
|
|
385
|
+
*/
|
|
386
|
+
renderFields(fields, prefix, targetTable, innerTableAlias, out) {
|
|
387
|
+
let json = '';
|
|
272
388
|
for (const field of fields) {
|
|
389
|
+
const alias = field.alias;
|
|
390
|
+
const fullAlias = prefix ? prefix + '__' + alias : alias;
|
|
273
391
|
if (field.nestedCteJoin) {
|
|
274
|
-
joins.
|
|
392
|
+
out.joins += (out.joins ? '\n ' : '') + field.nestedCteJoin.joinClause;
|
|
275
393
|
}
|
|
276
394
|
if (field.nested) {
|
|
277
|
-
|
|
395
|
+
const nestedJson = this.renderFields(field.nested, fullAlias, targetTable, innerTableAlias, out);
|
|
396
|
+
json += (json ? ', ' : '') + "'" + alias + "', " + nestedJson;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
json += (json ? ', ' : '') + "'" + alias + "', \"" + fullAlias + '"';
|
|
400
|
+
if (field.expression) {
|
|
401
|
+
out.select += (out.select ? ', ' : '') + this.renderLeafSelect(field.expression, fullAlias, targetTable, innerTableAlias);
|
|
402
|
+
}
|
|
278
403
|
}
|
|
279
404
|
}
|
|
280
|
-
return
|
|
405
|
+
return 'json_build_object(' + json + ')';
|
|
281
406
|
}
|
|
282
407
|
/**
|
|
283
408
|
* Build navigation JOINs SQL for multi-level navigation in collection queries
|
|
@@ -313,14 +438,15 @@ WHERE ${whereSQL})`;
|
|
|
313
438
|
* @param targetTable - Optional: the original target table name (e.g., "posts") to map to innerTableAlias
|
|
314
439
|
* @param context - Optional: QueryContext containing lateralTableAliasMap for nested lateral references
|
|
315
440
|
*/
|
|
316
|
-
buildNavigationJoinsWithAlias(navigationJoins, innerTableAlias, targetTable, context) {
|
|
441
|
+
buildNavigationJoinsWithAlias(navigationJoins, innerTableAlias, targetTable, context, relationName) {
|
|
317
442
|
if (!navigationJoins || navigationJoins.length === 0) {
|
|
318
443
|
return '';
|
|
319
444
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
445
|
+
if (relationName === undefined) {
|
|
446
|
+
// `lateral_<n>_<relation>` — the relation name is everything after the second `_`
|
|
447
|
+
const parts = innerTableAlias.split('_');
|
|
448
|
+
relationName = parts.length >= 3 ? parts.slice(2).join('_') : innerTableAlias;
|
|
449
|
+
}
|
|
324
450
|
// Get the lateral table alias map from context (for nested lateral references)
|
|
325
451
|
// This is used when a nested collection's selector navigation references a parent collection's table
|
|
326
452
|
const lateralAliasMap = context?.lateralTableAliasMap;
|
|
@@ -363,101 +489,24 @@ WHERE ${whereSQL})`;
|
|
|
363
489
|
*/
|
|
364
490
|
buildJsonbAggregation(config, lateralAlias, context) {
|
|
365
491
|
const { selectedFields, targetTable, foreignKey, sourceTable, whereClause, orderByClause, limitValue, offsetValue, isDistinct, navigationJoins, relationName } = config;
|
|
366
|
-
// Use a unique table alias to avoid conflicts with outer query tables
|
|
367
|
-
// This is important when the collection targets the same table as the outer query
|
|
368
|
-
// (e.g., post.user.posts where both outer and inner are "posts" table)
|
|
369
492
|
const innerTableAlias = `${lateralAlias}_${relationName}`;
|
|
370
|
-
//
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (field.nested) {
|
|
376
|
-
result.push(...collectLeafFields(field.nested, fullAlias));
|
|
377
|
-
}
|
|
378
|
-
else if (field.expression) {
|
|
379
|
-
result.push({ alias: fullAlias, expression: field.expression });
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
return result;
|
|
383
|
-
};
|
|
384
|
-
// Helper to build json_build_object expression (handles nested structures)
|
|
385
|
-
const buildJsonbObject = (fields, prefix = '') => {
|
|
386
|
-
const parts = [];
|
|
387
|
-
for (const field of fields) {
|
|
388
|
-
if (field.nested) {
|
|
389
|
-
const nestedJsonb = buildJsonbObject(field.nested, prefix ? `${prefix}__${field.alias}` : field.alias);
|
|
390
|
-
parts.push(`'${field.alias}', ${nestedJsonb}`);
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
const fullAlias = prefix ? `${prefix}__${field.alias}` : field.alias;
|
|
394
|
-
parts.push(`'${field.alias}', "${fullAlias}"`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return `json_build_object(${parts.join(', ')})`;
|
|
398
|
-
};
|
|
399
|
-
// Collect all leaf fields for the SELECT clause
|
|
400
|
-
const leafFields = collectLeafFields(selectedFields);
|
|
401
|
-
// When there are navigation joins, we need to qualify unqualified field expressions
|
|
402
|
-
// with the inner table alias to avoid ambiguous column references
|
|
403
|
-
const hasNavigationJoins = navigationJoins && navigationJoins.length > 0;
|
|
404
|
-
// Helper to rewrite expressions that reference the collection's table to use inner alias
|
|
405
|
-
const rewriteTableReference = (expression) => {
|
|
406
|
-
// Replace the special marker alias `"__collection_tableName__".` with `"innerTableAlias".`
|
|
407
|
-
// This marker is set in CollectionQueryBuilder.createMockItem() to distinguish
|
|
408
|
-
// collection references from outer table references when both target the same table
|
|
409
|
-
if (!expression.includes('"__collection_')) {
|
|
410
|
-
return expression; // nothing to rewrite — skip the regex pass entirely
|
|
411
|
-
}
|
|
412
|
-
const markerPattern = (0, query_utils_1.collectionMarkerPattern)(targetTable, true);
|
|
413
|
-
return expression.replace(markerPattern, `"${innerTableAlias}".`);
|
|
414
|
-
};
|
|
415
|
-
// Build the subquery SELECT fields (no foreign key needed since we correlate with parent)
|
|
416
|
-
const allSelectFields = leafFields.map(f => {
|
|
417
|
-
// If expression is just a quoted column name (e.g., `"id"`), qualify it with inner table alias
|
|
418
|
-
// But if it's already qualified (e.g., `"user"."username"`), rewrite if it references target table
|
|
419
|
-
const isSimpleColumn = /^"[^".]+"$/.test(f.expression);
|
|
420
|
-
if (isSimpleColumn) {
|
|
421
|
-
// Unqualified column - qualify with inner table alias
|
|
422
|
-
const columnName = f.expression.slice(1, -1); // Remove quotes
|
|
423
|
-
return `"${innerTableAlias}"."${columnName}" as "${f.alias}"`;
|
|
424
|
-
}
|
|
425
|
-
// Already qualified - rewrite target table references
|
|
426
|
-
const rewritten = rewriteTableReference(f.expression);
|
|
427
|
-
if (rewritten !== `"${f.alias}"`) {
|
|
428
|
-
return `${rewritten} as "${f.alias}"`;
|
|
429
|
-
}
|
|
430
|
-
return rewritten;
|
|
431
|
-
});
|
|
432
|
-
// Build the JSONB fields for json_build_object
|
|
433
|
-
const jsonbObjectExpr = buildJsonbObject(selectedFields);
|
|
434
|
-
// Build navigation JOINs for multi-level navigation
|
|
435
|
-
// Pass innerTableAlias so navigation joins can reference it properly
|
|
436
|
-
const navJoinsSQL = this.buildNavigationJoinsWithAlias(navigationJoins, innerTableAlias, targetTable, context);
|
|
437
|
-
// Collect nested CTE/LATERAL joins (for collections within collections)
|
|
438
|
-
const nestedCteJoins = this.collectNestedCteJoins(selectedFields);
|
|
439
|
-
const nestedCteJoinsSQL = nestedCteJoins.length > 0 ? nestedCteJoins.join('\n ') : '';
|
|
440
|
-
// For nested collections, the source table may be aliased in a parent LATERAL
|
|
441
|
-
// Check the lateralTableAliasMap to get the correct alias
|
|
493
|
+
// Inner select list + json_build_object + nested lateral joins in one pass
|
|
494
|
+
const rendered = { select: '', joins: '' };
|
|
495
|
+
const jsonbObjectExpr = this.renderFields(selectedFields, '', targetTable, innerTableAlias, rendered);
|
|
496
|
+
const navJoinsSQL = this.buildNavigationJoinsWithAlias(navigationJoins, innerTableAlias, targetTable, context, relationName);
|
|
497
|
+
// Check if the source table has been aliased by a parent LATERAL (for nested collections)
|
|
442
498
|
const effectiveSourceTable = context.lateralTableAliasMap?.get(sourceTable) || sourceTable;
|
|
443
|
-
// Build WHERE clause
|
|
444
|
-
//
|
|
445
|
-
// predicates carried by `config.foreignKeys`/`matches`).
|
|
446
|
-
// For selectMany, the FK is on the intermediate table (joined via navJoins)
|
|
499
|
+
// Build WHERE clause with correlation to parent
|
|
500
|
+
// For selectMany, the FK is on the intermediate table (joined via navJoins), not the target table
|
|
447
501
|
const fkTableAlias = config.foreignKeyTableAlias || innerTableAlias;
|
|
448
502
|
let whereSQL = `WHERE ${this.buildParentCorrelation(config, fkTableAlias, effectiveSourceTable, foreignKey)}`;
|
|
449
503
|
if (whereClause) {
|
|
450
|
-
|
|
451
|
-
const rewrittenWhereClause = rewriteTableReference(whereClause);
|
|
452
|
-
whereSQL += ` AND ${rewrittenWhereClause}`;
|
|
504
|
+
whereSQL += ` AND ${this.rewriteMarker(whereClause, targetTable, innerTableAlias)}`;
|
|
453
505
|
}
|
|
454
|
-
// Build ORDER BY clause - also rewrite table references
|
|
455
506
|
let orderBySQL = '';
|
|
456
507
|
if (orderByClause) {
|
|
457
|
-
|
|
458
|
-
orderBySQL = `ORDER BY ${rewrittenOrderBy}`;
|
|
508
|
+
orderBySQL = `ORDER BY ${this.rewriteMarker(orderByClause, targetTable, innerTableAlias)}`;
|
|
459
509
|
}
|
|
460
|
-
// Build LIMIT/OFFSET
|
|
461
510
|
let limitOffsetClause = '';
|
|
462
511
|
if (limitValue !== undefined) {
|
|
463
512
|
limitOffsetClause = `LIMIT ${limitValue}`;
|
|
@@ -465,21 +514,18 @@ WHERE ${whereSQL})`;
|
|
|
465
514
|
if (offsetValue !== undefined) {
|
|
466
515
|
limitOffsetClause += ` OFFSET ${offsetValue}`;
|
|
467
516
|
}
|
|
468
|
-
// Build DISTINCT clause
|
|
469
517
|
const distinctClause = isDistinct ? 'DISTINCT ' : '';
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
// 2. Column aliases in the subquery may differ from original column names
|
|
473
|
-
// The order is preserved from the inner query's ORDER BY
|
|
518
|
+
// Build LATERAL subquery
|
|
519
|
+
// Structure: SELECT json_agg(json_build_object(...)) FROM (SELECT ... LIMIT/OFFSET) sub
|
|
474
520
|
const lateralSQL = `
|
|
475
521
|
SELECT json_agg(
|
|
476
522
|
${jsonbObjectExpr}
|
|
477
523
|
) as data
|
|
478
524
|
FROM (
|
|
479
|
-
SELECT ${distinctClause}${
|
|
525
|
+
SELECT ${distinctClause}${rendered.select}
|
|
480
526
|
FROM "${targetTable}" "${innerTableAlias}"
|
|
481
527
|
${navJoinsSQL}
|
|
482
|
-
${
|
|
528
|
+
${rendered.joins}
|
|
483
529
|
${whereSQL}
|
|
484
530
|
${orderBySQL}
|
|
485
531
|
${limitOffsetClause}
|
|
@@ -493,95 +539,30 @@ FROM (
|
|
|
493
539
|
*/
|
|
494
540
|
buildSingleJsonAggregation(config, lateralAlias, context) {
|
|
495
541
|
const { selectedFields, targetTable, foreignKey, sourceTable, whereClause, orderByClause, isDistinct, navigationJoins, relationName } = config;
|
|
496
|
-
// Use a unique table alias to avoid conflicts with outer query tables
|
|
497
542
|
const innerTableAlias = `${lateralAlias}_${relationName}`;
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
for (const field of fields) {
|
|
502
|
-
const fullAlias = prefix ? `${prefix}__${field.alias}` : field.alias;
|
|
503
|
-
if (field.nested) {
|
|
504
|
-
result.push(...collectLeafFields(field.nested, fullAlias));
|
|
505
|
-
}
|
|
506
|
-
else if (field.expression) {
|
|
507
|
-
result.push({ alias: fullAlias, expression: field.expression });
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
return result;
|
|
511
|
-
};
|
|
512
|
-
// Helper to build json_build_object expression (handles nested structures)
|
|
513
|
-
const buildJsonbObject = (fields, prefix = '') => {
|
|
514
|
-
const parts = [];
|
|
515
|
-
for (const field of fields) {
|
|
516
|
-
if (field.nested) {
|
|
517
|
-
const nestedJsonb = buildJsonbObject(field.nested, prefix ? `${prefix}__${field.alias}` : field.alias);
|
|
518
|
-
parts.push(`'${field.alias}', ${nestedJsonb}`);
|
|
519
|
-
}
|
|
520
|
-
else {
|
|
521
|
-
const fullAlias = prefix ? `${prefix}__${field.alias}` : field.alias;
|
|
522
|
-
parts.push(`'${field.alias}', "${fullAlias}"`);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
return `json_build_object(${parts.join(', ')})`;
|
|
526
|
-
};
|
|
527
|
-
// Collect all leaf fields for the SELECT clause
|
|
528
|
-
const leafFields = collectLeafFields(selectedFields);
|
|
529
|
-
// Helper to rewrite expressions that reference the collection's table to use inner alias
|
|
530
|
-
const rewriteTableReference = (expression) => {
|
|
531
|
-
// Replace the special marker alias `"__collection_tableName__".` with `"innerTableAlias".`
|
|
532
|
-
if (!expression.includes('"__collection_')) {
|
|
533
|
-
return expression; // nothing to rewrite — skip the regex pass entirely
|
|
534
|
-
}
|
|
535
|
-
const markerPattern = (0, query_utils_1.collectionMarkerPattern)(targetTable, true);
|
|
536
|
-
return expression.replace(markerPattern, `"${innerTableAlias}".`);
|
|
537
|
-
};
|
|
538
|
-
// Build the subquery SELECT fields using inner table alias
|
|
539
|
-
const allSelectFields = leafFields.map(f => {
|
|
540
|
-
const isSimpleColumn = /^"[^".]+"$/.test(f.expression);
|
|
541
|
-
if (isSimpleColumn) {
|
|
542
|
-
const columnName = f.expression.slice(1, -1);
|
|
543
|
-
return `"${innerTableAlias}"."${columnName}" as "${f.alias}"`;
|
|
544
|
-
}
|
|
545
|
-
const rewritten = rewriteTableReference(f.expression);
|
|
546
|
-
if (rewritten !== `"${f.alias}"`) {
|
|
547
|
-
return `${rewritten} as "${f.alias}"`;
|
|
548
|
-
}
|
|
549
|
-
return rewritten;
|
|
550
|
-
});
|
|
551
|
-
// Build the JSONB fields for json_build_object
|
|
552
|
-
const jsonbObjectExpr = buildJsonbObject(selectedFields);
|
|
553
|
-
// Build navigation JOINs for multi-level navigation
|
|
554
|
-
const navJoinsSQL = this.buildNavigationJoinsWithAlias(navigationJoins, innerTableAlias, targetTable, context);
|
|
555
|
-
// Collect nested CTE/LATERAL joins (for collections within collections)
|
|
556
|
-
const nestedCteJoins = this.collectNestedCteJoins(selectedFields);
|
|
557
|
-
const nestedCteJoinsSQL = nestedCteJoins.length > 0 ? nestedCteJoins.join('\n ') : '';
|
|
558
|
-
// For nested collections, the source table may be aliased in a parent LATERAL
|
|
543
|
+
const rendered = { select: '', joins: '' };
|
|
544
|
+
const jsonbObjectExpr = this.renderFields(selectedFields, '', targetTable, innerTableAlias, rendered);
|
|
545
|
+
const navJoinsSQL = this.buildNavigationJoinsWithAlias(navigationJoins, innerTableAlias, targetTable, context, relationName);
|
|
559
546
|
const effectiveSourceTable = context.lateralTableAliasMap?.get(sourceTable) || sourceTable;
|
|
560
|
-
// Build WHERE clause - LATERAL correlates with parent via foreign key
|
|
561
|
-
// For selectMany, the FK is on the intermediate table (joined via navJoins)
|
|
562
547
|
const fkTableAlias2 = config.foreignKeyTableAlias || innerTableAlias;
|
|
563
548
|
let whereSQL = `WHERE ${this.buildParentCorrelation(config, fkTableAlias2, effectiveSourceTable, foreignKey)}`;
|
|
564
549
|
if (whereClause) {
|
|
565
|
-
|
|
566
|
-
whereSQL += ` AND ${rewrittenWhereClause}`;
|
|
550
|
+
whereSQL += ` AND ${this.rewriteMarker(whereClause, targetTable, innerTableAlias)}`;
|
|
567
551
|
}
|
|
568
|
-
// Build ORDER BY clause
|
|
569
552
|
let orderBySQL = '';
|
|
570
553
|
if (orderByClause) {
|
|
571
|
-
|
|
572
|
-
orderBySQL = `ORDER BY ${rewrittenOrderBy}`;
|
|
554
|
+
orderBySQL = `ORDER BY ${this.rewriteMarker(orderByClause, targetTable, innerTableAlias)}`;
|
|
573
555
|
}
|
|
574
|
-
// Build DISTINCT clause
|
|
575
556
|
const distinctClause = isDistinct ? 'DISTINCT ' : '';
|
|
576
|
-
//
|
|
577
|
-
//
|
|
557
|
+
// Structure: SELECT json_build_object(...) FROM (SELECT ... LIMIT 1) sub
|
|
558
|
+
// Returns null if no rows (LEFT JOIN LATERAL handles this)
|
|
578
559
|
const lateralSQL = `
|
|
579
560
|
SELECT ${jsonbObjectExpr} as data
|
|
580
561
|
FROM (
|
|
581
|
-
SELECT ${distinctClause}${
|
|
562
|
+
SELECT ${distinctClause}${rendered.select}
|
|
582
563
|
FROM "${targetTable}" "${innerTableAlias}"
|
|
583
564
|
${navJoinsSQL}
|
|
584
|
-
${
|
|
565
|
+
${rendered.joins}
|
|
585
566
|
${whereSQL}
|
|
586
567
|
${orderBySQL}
|
|
587
568
|
LIMIT 1
|