joist-orm 1.218.2 → 1.220.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/build/AliasAssigner.d.ts +2 -0
- package/build/AliasAssigner.d.ts.map +1 -1
- package/build/AliasAssigner.js +23 -8
- package/build/AliasAssigner.js.map +1 -1
- package/build/Aliases.d.ts +18 -0
- package/build/Aliases.d.ts.map +1 -1
- package/build/Aliases.js +24 -0
- package/build/Aliases.js.map +1 -1
- package/build/EntityFilter.d.ts +1 -0
- package/build/EntityFilter.d.ts.map +1 -1
- package/build/QueryParser.d.ts +86 -17
- package/build/QueryParser.d.ts.map +1 -1
- package/build/QueryParser.js +453 -143
- package/build/QueryParser.js.map +1 -1
- package/build/QueryVisitor.d.ts +1 -1
- package/build/QueryVisitor.d.ts.map +1 -1
- package/build/QueryVisitor.js +11 -2
- package/build/QueryVisitor.js.map +1 -1
- package/build/dataloaders/findByUniqueDataLoader.js +1 -1
- package/build/dataloaders/findByUniqueDataLoader.js.map +1 -1
- package/build/dataloaders/findCountDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findCountDataLoader.js +31 -24
- package/build/dataloaders/findCountDataLoader.js.map +1 -1
- package/build/dataloaders/findDataLoader.d.ts +7 -14
- package/build/dataloaders/findDataLoader.d.ts.map +1 -1
- package/build/dataloaders/findDataLoader.js +71 -87
- package/build/dataloaders/findDataLoader.js.map +1 -1
- package/build/drivers/Driver.d.ts +1 -1
- package/build/drivers/Driver.d.ts.map +1 -1
- package/build/drivers/buildKnexQuery.d.ts.map +1 -1
- package/build/drivers/buildKnexQuery.js +14 -12
- package/build/drivers/buildKnexQuery.js.map +1 -1
- package/build/drivers/buildRawQuery.d.ts.map +1 -1
- package/build/drivers/buildRawQuery.js +18 -14
- package/build/drivers/buildRawQuery.js.map +1 -1
- package/build/drivers/buildUtils.d.ts +3 -1
- package/build/drivers/buildUtils.d.ts.map +1 -1
- package/build/drivers/buildUtils.js +3 -4
- package/build/drivers/buildUtils.js.map +1 -1
- package/build/plugins/PreloadPlugin.d.ts +2 -4
- package/build/plugins/PreloadPlugin.d.ts.map +1 -1
- package/package.json +2 -2
package/build/QueryParser.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConditionBuilder = exports.skipCondition = void 0;
|
|
4
4
|
exports.parseFindQuery = parseFindQuery;
|
|
5
|
+
exports.parseAlias = parseAlias;
|
|
5
6
|
exports.parseEntityFilter = parseEntityFilter;
|
|
6
7
|
exports.parseValueFilter = parseValueFilter;
|
|
7
8
|
exports.mapToDb = mapToDb;
|
|
@@ -9,9 +10,6 @@ exports.maybeAddOrderBy = maybeAddOrderBy;
|
|
|
9
10
|
exports.addTablePerClassJoinsAndClassTag = addTablePerClassJoinsAndClassTag;
|
|
10
11
|
exports.maybeAddNotSoftDeleted = maybeAddNotSoftDeleted;
|
|
11
12
|
exports.getTables = getTables;
|
|
12
|
-
exports.joinKeywords = joinKeywords;
|
|
13
|
-
exports.joinClause = joinClause;
|
|
14
|
-
exports.joinClauses = joinClauses;
|
|
15
13
|
exports.makeLike = makeLike;
|
|
16
14
|
const joist_utils_1 = require("joist-utils");
|
|
17
15
|
const Aliases_1 = require("./Aliases");
|
|
@@ -20,6 +18,7 @@ const EntityMetadata_1 = require("./EntityMetadata");
|
|
|
20
18
|
const QueryBuilder_1 = require("./QueryBuilder");
|
|
21
19
|
const QueryVisitor_1 = require("./QueryVisitor");
|
|
22
20
|
const configure_1 = require("./configure");
|
|
21
|
+
const buildUtils_1 = require("./drivers/buildUtils");
|
|
23
22
|
const index_1 = require("./index");
|
|
24
23
|
const keywords_1 = require("./keywords");
|
|
25
24
|
const utils_1 = require("./utils");
|
|
@@ -37,61 +36,200 @@ function parseFindQuery(meta, filter, opts = {}) {
|
|
|
37
36
|
const tables = [];
|
|
38
37
|
const orderBys = [];
|
|
39
38
|
const query = { selects, tables, orderBys };
|
|
40
|
-
const { orderBy = undefined,
|
|
39
|
+
const { orderBy = undefined, softDeletes = "exclude", pruneJoins = true, keepAliases = [] } = opts;
|
|
41
40
|
const cb = new ConditionBuilder();
|
|
42
|
-
const aliases = {};
|
|
41
|
+
const aliases = opts.aliases ?? {};
|
|
43
42
|
function getAlias(tableName) {
|
|
44
43
|
const abbrev = (0, QueryBuilder_1.abbreviation)(tableName);
|
|
45
44
|
const i = aliases[abbrev] || 0;
|
|
46
45
|
aliases[abbrev] = i + 1;
|
|
47
46
|
return i === 0 ? abbrev : `${abbrev}${i}`;
|
|
48
47
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
// If they passed extra `conditions: ...`, parse that.
|
|
49
|
+
// We can do this up-front b/c it doesn't require any join-tree metadata to perform,
|
|
50
|
+
// and then it's available for our `addLateralJoin`s to rewrite.
|
|
51
|
+
if (opts.conditions)
|
|
52
|
+
cb.maybeAddExpression(opts.conditions);
|
|
53
|
+
// Also see if outer `addLateralJoin` is passing us its join conditions
|
|
54
|
+
if (opts.rawConditions) {
|
|
55
|
+
for (const rc of opts.rawConditions)
|
|
56
|
+
cb.addRawCondition(rc);
|
|
57
|
+
}
|
|
58
|
+
function addLateralJoin(meta, fromAlias, alias, filter,
|
|
59
|
+
// The join condition for the subquery from the outer table
|
|
60
|
+
condition,
|
|
61
|
+
// If we're a m2m, the join table to inject (which will use the outer table)
|
|
62
|
+
joinTable,
|
|
63
|
+
// Allow addOrderBy to do its own post-addTable lateral join building
|
|
64
|
+
orderByTables = undefined) {
|
|
65
|
+
const ef = parseEntityFilter(meta, filter);
|
|
66
|
+
// Maybe skip
|
|
67
|
+
if (!ef && !(0, Aliases_1.isAlias)(filter) && !orderByTables)
|
|
68
|
+
return;
|
|
69
|
+
bindAlias(filter, meta, alias);
|
|
70
|
+
// Create an alias to use for our subquery's `where parent_id = id` condition
|
|
71
|
+
const a = (0, Aliases_1.newAliasProxy)(meta.cstr);
|
|
72
|
+
// This is kinda janky, but take the `ef: ParsedEntityFilter` and re-work it back into a
|
|
73
|
+
// `subFilter/count` pair that we can use for our recursive `parseFindQuery` call.
|
|
74
|
+
function convertFilter() {
|
|
75
|
+
if (ef) {
|
|
76
|
+
if (ef.kind === "join") {
|
|
77
|
+
// subFilter will be unprocessed, so we can pass it recursively into `parseFindQuery`
|
|
78
|
+
return { subFilter: ef.subFilter, count: undefined };
|
|
79
|
+
}
|
|
80
|
+
else if (ef.kind === "not-null") {
|
|
81
|
+
return { subFilter: {}, count: { kind: "gt", value: 0 } };
|
|
82
|
+
}
|
|
83
|
+
else if (ef.kind === "is-null") {
|
|
84
|
+
return { subFilter: {}, count: { kind: "eq", value: 0 } };
|
|
85
|
+
}
|
|
86
|
+
else if (ef.kind === "eq") {
|
|
87
|
+
return { subFilter: { id: ef.value }, count: undefined };
|
|
88
|
+
}
|
|
89
|
+
else if (ef.kind === "in") {
|
|
90
|
+
return { subFilter: { id: { in: ef.value } }, count: undefined };
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// If `ef` is set, it's already parsed, which `parseFindQuery` won't expect, so pass the original `filter`
|
|
94
|
+
return { subFilter: { id: filter }, count: undefined };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
return { subFilter: {}, count: undefined };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const { subFilter,
|
|
102
|
+
// If `ef` was is-null/not-null, use that as the count, otherwise probe for subFilter[$count]
|
|
103
|
+
count = subFilter && "$count" in subFilter ? { kind: "eq", value: subFilter["$count"] } : undefined, } = convertFilter();
|
|
104
|
+
const subQuery = parseFindQuery(meta, { as: a, ...subFilter }, {
|
|
105
|
+
// Only pass through a subset of the opts...
|
|
106
|
+
softDeletes: opts.softDeletes,
|
|
107
|
+
pruneJoins: opts.pruneJoins,
|
|
108
|
+
keepAliases: opts.keepAliases,
|
|
109
|
+
aliases,
|
|
110
|
+
alias,
|
|
111
|
+
// And set our own complex condition as the join condition (for o2m, two for m2m)
|
|
112
|
+
rawConditions: [condition],
|
|
113
|
+
// Let the subquery's pruneUnusedJoins know about the top-level WHERE clauses
|
|
114
|
+
topLevelCondition: opts.topLevelCondition ?? cb,
|
|
115
|
+
outerLateralJoins: [{ alias, select: selects, outerCb: cb }, ...(opts.outerLateralJoins ?? [])],
|
|
116
|
+
});
|
|
117
|
+
subQuery.orderBys = [];
|
|
118
|
+
if (joinTable)
|
|
119
|
+
subQuery.tables.unshift(joinTable);
|
|
120
|
+
const join = { join: "lateral", table: meta.tableName, query: subQuery, alias, fromAlias };
|
|
121
|
+
(orderByTables ?? tables).push(join);
|
|
122
|
+
// Look for complex conditions...
|
|
123
|
+
const topLevelAlias = opts.outerLateralJoins?.[opts.outerLateralJoins?.length - 1]?.alias;
|
|
124
|
+
const complexConditions = (opts.topLevelCondition ?? cb).findAndRewrite(topLevelAlias ?? alias, alias);
|
|
125
|
+
for (const cc of complexConditions) {
|
|
126
|
+
if (cc.cond.kind === "column" && cc.cond.column === "$count") {
|
|
127
|
+
subQuery.selects.push(buildCountStar(cc));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const [sql, bindings] = (0, buildUtils_1.buildCondition)(cc.cond);
|
|
131
|
+
subQuery.selects.push({
|
|
132
|
+
sql: `BOOL_OR(${sql}) as ${cc.as}`,
|
|
133
|
+
aliases: [cc.cond.alias],
|
|
134
|
+
bindings,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// If there are complex conditions looking at our data, we don't want a "make sure at least one matched"
|
|
139
|
+
const usedByComplexCondition = selects.some((s) => typeof s === "object" && "aliases" in s && s.aliases.includes(alias)) ||
|
|
140
|
+
// If we're the very 1st addLateralJoin, we don't push our selects into the next-up
|
|
141
|
+
// lateral join, so instead look through the top-level condition
|
|
142
|
+
(!opts.topLevelCondition &&
|
|
143
|
+
deepFindConditions(cb.expressions[0], true).some((c) => {
|
|
144
|
+
return c.kind === "raw" && c.aliases.includes(alias);
|
|
145
|
+
}));
|
|
146
|
+
// If there are literally no conditions on this child relation, don't add the "make sure at least one matched"
|
|
147
|
+
const hasAnyFilter = deepFindConditions(subQuery.condition, true).length > 0 || count !== undefined;
|
|
148
|
+
if (!usedByComplexCondition && hasAnyFilter) {
|
|
52
149
|
cb.addSimpleCondition({
|
|
53
150
|
kind: "column",
|
|
54
151
|
alias,
|
|
55
|
-
column:
|
|
56
|
-
dbType:
|
|
57
|
-
cond: { kind: "
|
|
58
|
-
|
|
152
|
+
column: "_",
|
|
153
|
+
dbType: "int",
|
|
154
|
+
cond: count ?? { kind: "gt", value: 0 },
|
|
155
|
+
// Don't let this condition pin the join, unless the user asked for a specific count
|
|
156
|
+
// (or deepFindConditions finds a real condition from the above filter).
|
|
157
|
+
pruneable: count === undefined,
|
|
158
|
+
});
|
|
159
|
+
// Go up the tree and make sure any parent lateral joins have a "at least 1 match"
|
|
160
|
+
opts.outerLateralJoins?.forEach(({ alias, outerCb }) => {
|
|
161
|
+
outerCb.addSimpleCondition({
|
|
162
|
+
kind: "column",
|
|
163
|
+
alias,
|
|
164
|
+
column: "_",
|
|
165
|
+
dbType: "int",
|
|
166
|
+
cond: { kind: "gt", value: 0 },
|
|
167
|
+
pruneable: true,
|
|
168
|
+
});
|
|
59
169
|
});
|
|
60
170
|
}
|
|
171
|
+
return join;
|
|
61
172
|
}
|
|
173
|
+
/** Adds `meta` to the query, i.e. for m2o/o2o joins into parents. */
|
|
62
174
|
function addTable(meta, alias, join, col1, col2, filter) {
|
|
63
175
|
// look at filter, is it `{ book: "b2" }` or `{ book: { ... } }`
|
|
64
176
|
const ef = parseEntityFilter(meta, filter);
|
|
65
|
-
|
|
177
|
+
// Maybe skip
|
|
178
|
+
if (!ef && join !== "primary" && !(0, Aliases_1.isAlias)(filter))
|
|
66
179
|
return;
|
|
67
|
-
}
|
|
68
180
|
if (join === "primary") {
|
|
69
181
|
tables.push({ alias, table: meta.tableName, join });
|
|
70
182
|
}
|
|
183
|
+
else if (join === "lateral") {
|
|
184
|
+
(0, utils_1.fail)("Unexpected lateral join");
|
|
185
|
+
}
|
|
71
186
|
else {
|
|
72
187
|
tables.push({ alias, table: meta.tableName, join, col1, col2 });
|
|
73
188
|
}
|
|
74
189
|
// Maybe only do this if we're the primary, or have a field that needs it?
|
|
75
|
-
addTablePerClassJoinsAndClassTag(query, meta, alias,
|
|
190
|
+
addTablePerClassJoinsAndClassTag(query, meta, alias,
|
|
191
|
+
// Use opts.topLevelCondition to tell we're in a `addLateralJoin` and don't want the `CASE ... END as __class`
|
|
192
|
+
// select clause, which won't work because it's not an aggregate
|
|
193
|
+
join === "primary" && !opts.topLevelCondition);
|
|
76
194
|
if (needsStiDiscriminator(meta)) {
|
|
77
195
|
addStiSubtypeFilter(cb, meta, alias);
|
|
78
196
|
}
|
|
79
|
-
maybeAddNotSoftDeleted(meta, alias);
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
// might
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
197
|
+
maybeAddNotSoftDeleted(cb, softDeletes, meta, alias);
|
|
198
|
+
bindAlias(filter, meta, alias);
|
|
199
|
+
// If we're inside a lateral join, look for top-level conditions that need to be rewritten as `BOOL_OR(...)`
|
|
200
|
+
// I.e. we might be a regular m2o join, but we just came from a lateral join, so any complex conditions
|
|
201
|
+
// like `ourTable.column.eq(...)` need to be:
|
|
202
|
+
// a) injected as a `BOOL_OR(ourTable.column.eq(...)) as _b_column_0` select to surface outside the lateral join, and
|
|
203
|
+
// b) rewritten in the top-level query to be just `_b_column_0`
|
|
204
|
+
if (opts.topLevelCondition) {
|
|
205
|
+
const topLevelAlias = opts.outerLateralJoins?.[opts.outerLateralJoins?.length - 1]?.alias;
|
|
206
|
+
const complexConditions = opts.topLevelCondition.findAndRewrite(topLevelAlias ?? alias, alias);
|
|
207
|
+
for (const cc of complexConditions) {
|
|
208
|
+
if (cc.cond.kind === "column" && cc.cond.column === "$count") {
|
|
209
|
+
selects.push(buildCountStar(cc));
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const [sql, bindings] = (0, buildUtils_1.buildCondition)(cc.cond);
|
|
213
|
+
selects.push({ sql: `BOOL_OR(${sql}) as ${cc.as}`, aliases: [cc.cond.alias], bindings });
|
|
214
|
+
}
|
|
215
|
+
// Expose the `_b_column_0` through `SELECT`s all the up the tree
|
|
216
|
+
// (...until the top-level query, which doesn't need to SELECT it, only WHERE against it)
|
|
217
|
+
opts.outerLateralJoins?.forEach(({ alias, select }, i) => {
|
|
218
|
+
const isTopLevel = i === opts.outerLateralJoins.length - 1;
|
|
219
|
+
if (!isTopLevel) {
|
|
220
|
+
select.push({ sql: `BOOL_OR(${alias}.${cc.as}) as ${cc.as}`, bindings: [], aliases: [alias] });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
// prune needs to see the immediate, not necessarily the whole conditions...
|
|
225
|
+
// only rewriting needs to see the whole thing
|
|
89
226
|
}
|
|
227
|
+
// See if the clause says we must do a join into the relation
|
|
90
228
|
if (ef && ef.kind === "join") {
|
|
91
229
|
// subFilter really means we're matching against the entity columns/further joins
|
|
92
230
|
Object.keys(ef.subFilter).forEach((key) => {
|
|
93
231
|
// Skip the `{ as: ... }` alias binding
|
|
94
|
-
if (key === "as")
|
|
232
|
+
if (key === "as" || key === "$count")
|
|
95
233
|
return;
|
|
96
234
|
const field = meta.allFields[key] ??
|
|
97
235
|
meta.polyComponentFields?.[key] ??
|
|
@@ -209,55 +347,22 @@ function parseFindQuery(meta, filter, opts = {}) {
|
|
|
209
347
|
(0, utils_1.fail)(`No poly component found for ${otherField.fieldName}`);
|
|
210
348
|
otherColumn = otherComponent.columnName;
|
|
211
349
|
}
|
|
212
|
-
|
|
350
|
+
const condition = `${(0, keywords_1.kqDot)(alias, "id")} = ${(0, keywords_1.kqDot)(a + otherField.aliasSuffix, otherColumn)}`;
|
|
351
|
+
addLateralJoin(field.otherMetadata(), alias, a, ef.subFilter[key], { kind: "raw", aliases: [a, alias], condition, pruneable: true, bindings: [] }, undefined);
|
|
213
352
|
}
|
|
214
353
|
else if (field.kind === "m2m") {
|
|
215
354
|
// Always join into the m2m table
|
|
216
355
|
const ja = getAlias(field.joinTableName);
|
|
217
|
-
|
|
356
|
+
const jt = {
|
|
218
357
|
alias: ja,
|
|
219
|
-
join: "
|
|
358
|
+
join: "inner",
|
|
220
359
|
table: field.joinTableName,
|
|
221
360
|
col1: (0, keywords_1.kqDot)(alias, "id"),
|
|
222
361
|
col2: (0, keywords_1.kqDot)(ja, field.columnNames[0]),
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
const a = getAlias(field.otherMetadata().tableName);
|
|
228
|
-
addTable(field.otherMetadata(), a, "outer", (0, keywords_1.kqDot)(ja, field.columnNames[1]), (0, keywords_1.kqDot)(a, "id"), sub);
|
|
229
|
-
}
|
|
230
|
-
const f = parseEntityFilter(field.otherMetadata(), sub);
|
|
231
|
-
// Probe the filter and see if it's just an id, if so we can avoid the join
|
|
232
|
-
if (!f) {
|
|
233
|
-
// skip
|
|
234
|
-
}
|
|
235
|
-
else if (f.kind === "join" || filterSoftDeletes(field.otherMetadata(), softDeletes)) {
|
|
236
|
-
const a = getAlias(field.otherMetadata().tableName);
|
|
237
|
-
addTable(field.otherMetadata(), a, "outer", (0, keywords_1.kqDot)(ja, field.columnNames[1]), (0, keywords_1.kqDot)(a, "id"), ef.subFilter[key]);
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
const meta = field.otherMetadata();
|
|
241
|
-
// We normally don't have `columns` for m2m fields, b/c they don't go through normal serde
|
|
242
|
-
// codepaths, so make one up to leverage the existing `mapToDb` function.
|
|
243
|
-
const column = {
|
|
244
|
-
columnName: field.columnNames[1],
|
|
245
|
-
dbType: meta.idDbType,
|
|
246
|
-
mapToDb(value) {
|
|
247
|
-
// Check for `typeof value === number` in case this is a new entity, and we've been given the nilIdValue
|
|
248
|
-
return value === null || isNilIdValue(value)
|
|
249
|
-
? value
|
|
250
|
-
: (0, index_1.keyToNumber)(meta, (0, index_1.maybeResolveReferenceToId)(value));
|
|
251
|
-
},
|
|
252
|
-
};
|
|
253
|
-
cb.addSimpleCondition({
|
|
254
|
-
kind: "column",
|
|
255
|
-
alias: ja,
|
|
256
|
-
column: field.columnNames[1],
|
|
257
|
-
dbType: meta.idDbType,
|
|
258
|
-
cond: mapToDb(column, f),
|
|
259
|
-
});
|
|
260
|
-
}
|
|
362
|
+
};
|
|
363
|
+
const a = getAlias(field.otherMetadata().tableName);
|
|
364
|
+
const condition = `${(0, keywords_1.kqDot)(ja, field.columnNames[1])} = ${(0, keywords_1.kqDot)(a, "id")}`;
|
|
365
|
+
addLateralJoin(field.otherMetadata(), alias, a, ef.subFilter[key], { kind: "raw", aliases: [ja, a], condition, bindings: [], pruneable: true }, jt);
|
|
261
366
|
}
|
|
262
367
|
else {
|
|
263
368
|
throw new Error(`Unsupported field ${key}`);
|
|
@@ -269,8 +374,10 @@ function parseFindQuery(meta, filter, opts = {}) {
|
|
|
269
374
|
cb.addValueFilter(alias, column, ef);
|
|
270
375
|
}
|
|
271
376
|
}
|
|
272
|
-
function addOrderBy(meta, alias, orderBy) {
|
|
377
|
+
function addOrderBy(meta, alias, orderBy, lateralJoins) {
|
|
273
378
|
const entries = Object.entries(orderBy);
|
|
379
|
+
// If we're recursing for lateral joins, look in the local query's tables,
|
|
380
|
+
const tables = (lateralJoins[lateralJoins.length - 1]?.query ?? query).tables;
|
|
274
381
|
if (entries.length === 0)
|
|
275
382
|
return;
|
|
276
383
|
for (const [key, value] of entries) {
|
|
@@ -279,33 +386,73 @@ function parseFindQuery(meta, filter, opts = {}) {
|
|
|
279
386
|
const field = meta.allFields[key] ?? (0, utils_1.fail)(`${key} not found on ${meta.tableName}`);
|
|
280
387
|
if (field.kind === "primitive" || field.kind === "primaryKey" || field.kind === "enum") {
|
|
281
388
|
const column = field.serde.columns[0];
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
389
|
+
if (lateralJoins.length === 0) {
|
|
390
|
+
// We can add the orderBy directly against the column
|
|
391
|
+
orderBys.push({
|
|
392
|
+
alias: `${alias}${field.aliasSuffix}`,
|
|
393
|
+
column: column.columnName,
|
|
394
|
+
order: value,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
// We need to orderBy the summed column
|
|
399
|
+
const [topJoin, ...rest] = lateralJoins;
|
|
400
|
+
const lastJoin = rest[rest.length - 1] ?? topJoin;
|
|
401
|
+
const as = `_${alias}_${column.columnName}_sum`;
|
|
402
|
+
lastJoin.query.selects.push({
|
|
403
|
+
sql: `SUM(${alias}${field.aliasSuffix}.${column.columnName}) AS ${as}`,
|
|
404
|
+
aliases: [alias],
|
|
405
|
+
bindings: [],
|
|
406
|
+
});
|
|
407
|
+
for (const join of lateralJoins) {
|
|
408
|
+
if (join !== lastJoin) {
|
|
409
|
+
join.query.selects.push({
|
|
410
|
+
sql: `SUM(${alias}.${as}) AS ${as}`,
|
|
411
|
+
aliases: [join.alias],
|
|
412
|
+
bindings: [],
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
orderBys.push({ alias: `${topJoin.alias}`, column: as, order: value });
|
|
417
|
+
}
|
|
287
418
|
}
|
|
288
419
|
else if (field.kind === "m2o") {
|
|
289
420
|
// Do we already this table joined in?
|
|
290
421
|
let table = tables.find((t) => t.table === field.otherMetadata().tableName);
|
|
291
|
-
if (table) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
else {
|
|
295
|
-
const table = field.otherMetadata().tableName;
|
|
296
|
-
const a = getAlias(table);
|
|
422
|
+
if (!table) {
|
|
423
|
+
const { tableName } = field.otherMetadata();
|
|
424
|
+
const a = getAlias(tableName);
|
|
297
425
|
const column = field.serde.columns[0].columnName;
|
|
298
|
-
|
|
299
|
-
tables.push({
|
|
426
|
+
table = {
|
|
300
427
|
alias: a,
|
|
301
|
-
table,
|
|
302
|
-
join
|
|
428
|
+
table: tableName,
|
|
429
|
+
// If we don't have a join, don't force this to be an inner join
|
|
430
|
+
join: "outer", // don't drop the entity just b/c of a missing order by
|
|
303
431
|
col1: (0, keywords_1.kqDot)(alias, column),
|
|
304
432
|
col2: (0, keywords_1.kqDot)(a, "id"),
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
433
|
+
};
|
|
434
|
+
tables.push(table);
|
|
435
|
+
}
|
|
436
|
+
addOrderBy(field.otherMetadata(), table.alias, value, lateralJoins);
|
|
437
|
+
}
|
|
438
|
+
else if (field.kind === "o2m") {
|
|
439
|
+
let table = tables.filter((t) => t.join === "lateral").find((t) => t.table === field.otherMetadata().tableName);
|
|
440
|
+
if (!table) {
|
|
441
|
+
// ...big copy/paste from up above...
|
|
442
|
+
const a = getAlias(field.otherMetadata().tableName);
|
|
443
|
+
const otherField = field.otherMetadata().allFields[field.otherFieldName];
|
|
444
|
+
let otherColumn = otherField.serde.columns[0].columnName;
|
|
445
|
+
// If the other field is a poly, we need to find the right column
|
|
446
|
+
if (otherField.kind === "poly") {
|
|
447
|
+
// For a subcomponent that matches field's metadata
|
|
448
|
+
const otherComponent = otherField.components.find((c) => c.otherMetadata() === meta) ??
|
|
449
|
+
(0, utils_1.fail)(`No poly component found for ${otherField.fieldName}`);
|
|
450
|
+
otherColumn = otherComponent.columnName;
|
|
451
|
+
}
|
|
452
|
+
const condition = `${(0, keywords_1.kqDot)(alias, "id")} = ${(0, keywords_1.kqDot)(a + otherField.aliasSuffix, otherColumn)}`;
|
|
453
|
+
table = addLateralJoin(field.otherMetadata(), alias, a, undefined, { kind: "raw", aliases: [a, alias], condition, pruneable: true, bindings: [] }, undefined, tables);
|
|
308
454
|
}
|
|
455
|
+
addOrderBy(field.otherMetadata(), table.alias, value, [...lateralJoins, table]);
|
|
309
456
|
}
|
|
310
457
|
else {
|
|
311
458
|
throw new Error(`Unsupported field ${key}`);
|
|
@@ -313,13 +460,14 @@ function parseFindQuery(meta, filter, opts = {}) {
|
|
|
313
460
|
}
|
|
314
461
|
}
|
|
315
462
|
// always add the main table
|
|
316
|
-
const alias = getAlias(meta.tableName);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
463
|
+
const alias = opts.alias ?? getAlias(meta.tableName);
|
|
464
|
+
if (opts.topLevelCondition === undefined) {
|
|
465
|
+
selects.push(`${(0, keywords_1.kq)(alias)}.*`);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
selects.push("count(*) as _");
|
|
322
469
|
}
|
|
470
|
+
addTable(meta, alias, "primary", "n/a", "n/a", filter);
|
|
323
471
|
Object.assign(query, {
|
|
324
472
|
condition: cb.toExpressionFilter(),
|
|
325
473
|
});
|
|
@@ -329,10 +477,10 @@ function parseFindQuery(meta, filter, opts = {}) {
|
|
|
329
477
|
if (orderBy) {
|
|
330
478
|
if (Array.isArray(orderBy)) {
|
|
331
479
|
for (const ob of orderBy)
|
|
332
|
-
addOrderBy(meta, alias, ob);
|
|
480
|
+
addOrderBy(meta, alias, ob, []);
|
|
333
481
|
}
|
|
334
482
|
else {
|
|
335
|
-
addOrderBy(meta, alias, orderBy);
|
|
483
|
+
addOrderBy(meta, alias, orderBy, []);
|
|
336
484
|
}
|
|
337
485
|
}
|
|
338
486
|
maybeAddOrderBy(query, meta, alias);
|
|
@@ -384,48 +532,62 @@ function maybeAddIdNotNulls(query) {
|
|
|
384
532
|
}
|
|
385
533
|
// Remove any joins that are not used in the select or conditions
|
|
386
534
|
function pruneUnusedJoins(parsed, keepAliases) {
|
|
535
|
+
const dt = new DependencyTracker();
|
|
536
|
+
// First setup the alias -> alias dependencies...
|
|
537
|
+
const todo = [...parsed.tables];
|
|
538
|
+
while (todo.length > 0) {
|
|
539
|
+
const t = todo.pop();
|
|
540
|
+
if (t.join === "lateral") {
|
|
541
|
+
dt.addAlias(t.alias, [t.fromAlias]);
|
|
542
|
+
// Recurse into lateral joins...
|
|
543
|
+
todo.push(...t.query.tables);
|
|
544
|
+
}
|
|
545
|
+
else if (t.join === "cross") {
|
|
546
|
+
// Doesn't have any conditions
|
|
547
|
+
}
|
|
548
|
+
else if (t.join !== "primary") {
|
|
549
|
+
dt.addAlias(t.alias, [parseAlias(t.col1)]);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
387
552
|
// Mark all terminal usages
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
case "column":
|
|
397
|
-
used.add(c.alias);
|
|
398
|
-
break;
|
|
399
|
-
case "raw":
|
|
400
|
-
for (const alias of c.aliases) {
|
|
401
|
-
used.add(alias);
|
|
402
|
-
}
|
|
403
|
-
break;
|
|
404
|
-
default:
|
|
405
|
-
(0, utils_1.assertNever)(c);
|
|
553
|
+
parsed.selects.forEach((s) => {
|
|
554
|
+
if (typeof s === "string") {
|
|
555
|
+
if (!s.includes("count("))
|
|
556
|
+
dt.markRequired(parseAlias(s));
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
for (const a of s.aliases)
|
|
560
|
+
dt.markRequired(a);
|
|
406
561
|
}
|
|
407
562
|
});
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
// Restart at zero to find dependencies before us
|
|
418
|
-
i = 0;
|
|
563
|
+
parsed.orderBys.forEach((o) => dt.markRequired(o.alias));
|
|
564
|
+
keepAliases.forEach((a) => dt.markRequired(a));
|
|
565
|
+
// Look recursively into lateral join conditions
|
|
566
|
+
const todo2 = [parsed];
|
|
567
|
+
while (todo2.length > 0) {
|
|
568
|
+
const query = todo2.pop();
|
|
569
|
+
deepFindConditions(query.condition, true).forEach((c) => {
|
|
570
|
+
if (c.kind === "column") {
|
|
571
|
+
dt.markRequired(c.alias);
|
|
419
572
|
}
|
|
420
|
-
|
|
573
|
+
else if (c.kind === "raw") {
|
|
574
|
+
for (const alias of c.aliases)
|
|
575
|
+
dt.markRequired(alias);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
(0, utils_1.assertNever)(c);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
todo2.push(...query.tables.filter((t) => t.join === "lateral").map((t) => t.query));
|
|
421
582
|
}
|
|
422
583
|
// Now remove any unused joins
|
|
423
|
-
parsed.tables = parsed.tables.filter((t) =>
|
|
584
|
+
parsed.tables = parsed.tables.filter((t) => dt.required.has(t.alias));
|
|
424
585
|
// And then remove any inline soft-delete conditions we don't need anymore
|
|
425
586
|
if (parsed.condition && parsed.condition.op === "and") {
|
|
426
587
|
parsed.condition.conditions = parsed.condition.conditions.filter((c) => {
|
|
427
588
|
if (c.kind === "column") {
|
|
428
|
-
const prune = c.pruneable && !
|
|
589
|
+
const prune = c.pruneable && !dt.required.has(c.alias);
|
|
590
|
+
// if (prune) console.log(`DROPPING`, c);
|
|
429
591
|
return !prune;
|
|
430
592
|
}
|
|
431
593
|
else {
|
|
@@ -433,9 +595,13 @@ function pruneUnusedJoins(parsed, keepAliases) {
|
|
|
433
595
|
}
|
|
434
596
|
});
|
|
435
597
|
}
|
|
598
|
+
// Remove any `{ and: [...] }`s that are empty; we should probably do this deeply?
|
|
599
|
+
if (parsed.condition && parsed.condition.conditions.length === 0) {
|
|
600
|
+
parsed.condition = undefined;
|
|
601
|
+
}
|
|
436
602
|
}
|
|
437
603
|
/** Pulls out a flat list of all `ColumnCondition`s from a `ParsedExpressionFilter` tree. */
|
|
438
|
-
function deepFindConditions(condition) {
|
|
604
|
+
function deepFindConditions(condition, filterPruneable) {
|
|
439
605
|
const todo = condition ? [condition] : [];
|
|
440
606
|
const result = [];
|
|
441
607
|
while (todo.length !== 0) {
|
|
@@ -444,8 +610,12 @@ function deepFindConditions(condition) {
|
|
|
444
610
|
if (c.kind === "exp") {
|
|
445
611
|
todo.push(c);
|
|
446
612
|
}
|
|
613
|
+
else if (c.kind === "column" || c.kind === "raw") {
|
|
614
|
+
if (!filterPruneable || !c.pruneable)
|
|
615
|
+
result.push(c);
|
|
616
|
+
}
|
|
447
617
|
else {
|
|
448
|
-
|
|
618
|
+
(0, utils_1.assertNever)(c);
|
|
449
619
|
}
|
|
450
620
|
}
|
|
451
621
|
}
|
|
@@ -681,6 +851,14 @@ class ConditionBuilder {
|
|
|
681
851
|
addSimpleCondition(condition) {
|
|
682
852
|
this.conditions.push(condition);
|
|
683
853
|
}
|
|
854
|
+
/** Adds an already-db-level condition to the simple conditions list. */
|
|
855
|
+
addRawCondition(condition) {
|
|
856
|
+
this.conditions.push({
|
|
857
|
+
kind: "raw",
|
|
858
|
+
bindings: [],
|
|
859
|
+
...condition,
|
|
860
|
+
});
|
|
861
|
+
}
|
|
684
862
|
/** Adds an already-db-level expression to the expressions list. */
|
|
685
863
|
addParsedExpression(parsed) {
|
|
686
864
|
this.expressions.push(parsed);
|
|
@@ -733,12 +911,83 @@ class ConditionBuilder {
|
|
|
733
911
|
// If no inline conditions, and just 1 opt expression, just use that
|
|
734
912
|
return expressions[0];
|
|
735
913
|
}
|
|
914
|
+
else if (expressions.length === 1 && expressions[0].op === "and") {
|
|
915
|
+
// Merge the 1 `AND` expression with the other simple conditions
|
|
916
|
+
return { kind: "exp", op: "and", conditions: [...conditions, ...expressions[0].conditions] };
|
|
917
|
+
}
|
|
736
918
|
else if (conditions.length > 0 || expressions.length > 0) {
|
|
737
919
|
// Combine the conditions within the `em.find` join literal & the `conditions` as ANDs
|
|
738
920
|
return { kind: "exp", op: "and", conditions: [...conditions, ...expressions] };
|
|
739
921
|
}
|
|
740
922
|
return undefined;
|
|
741
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* Finds `child.column.eq(...)` complex conditions that need pushed down into each lateral join.
|
|
926
|
+
*
|
|
927
|
+
* Once we find something like `{ column: "first_name", cond: { eq: "a1" } }`, we return it to the
|
|
928
|
+
* caller (for injection into the lateral join's `SELECT` clause), and replace it with a boolean
|
|
929
|
+
* expression that is basically "did any of my children match this condition?".
|
|
930
|
+
*
|
|
931
|
+
* We also assume that `findAndRewrite` is only called on the top-level/user-facing `ParsedFindQuery`,
|
|
932
|
+
* and not any intermediate `LateralJoinTable` queries (which are allowed to have their own
|
|
933
|
+
* `ConditionBuilder`s for building their internal query, but it's not exposed to the user,
|
|
934
|
+
* so won't have any truly-complex conditions that should need rewritten).
|
|
935
|
+
*
|
|
936
|
+
* @param topLevelLateralJoin the outermost lateral join alias, as that will be the only alias
|
|
937
|
+
* that is visible to the rewritten condition, i.e. `_alias._whatever_condition_`.
|
|
938
|
+
* @param alias the alias being "hidden" in a lateral join, and so its columns/data won't be
|
|
939
|
+
* available for the top-level condition to directly AND/OR against.
|
|
940
|
+
*/
|
|
941
|
+
findAndRewrite(topLevelLateralJoin, alias) {
|
|
942
|
+
let j = 0;
|
|
943
|
+
const found = [];
|
|
944
|
+
const todo = [this.conditions];
|
|
945
|
+
for (const exp of this.expressions)
|
|
946
|
+
todo.push(exp.conditions);
|
|
947
|
+
while (todo.length > 0) {
|
|
948
|
+
const array = todo.pop();
|
|
949
|
+
array.forEach((cond, i) => {
|
|
950
|
+
if (cond.kind === "column") {
|
|
951
|
+
// Use startsWith to look for `_b0` / `_s0` base/subtype conditions
|
|
952
|
+
if (cond.alias === alias || cond.alias.startsWith(`${alias}_`)) {
|
|
953
|
+
if (cond.column === "_")
|
|
954
|
+
return; // Hack to skip rewriting `alias._ > 0`
|
|
955
|
+
const as = `_${alias}_${cond.column}_${j++}`;
|
|
956
|
+
array[i] = {
|
|
957
|
+
kind: "raw",
|
|
958
|
+
aliases: [topLevelLateralJoin],
|
|
959
|
+
condition: `${topLevelLateralJoin}.${as}`,
|
|
960
|
+
bindings: [],
|
|
961
|
+
pruneable: false,
|
|
962
|
+
...{ rewritten: true },
|
|
963
|
+
};
|
|
964
|
+
found.push({ cond, as });
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
else if (cond.kind === "exp") {
|
|
968
|
+
todo.push(cond.conditions);
|
|
969
|
+
}
|
|
970
|
+
else if (cond.kind === "raw") {
|
|
971
|
+
// what would we do here?
|
|
972
|
+
if (cond.aliases.includes(alias)) {
|
|
973
|
+
// Look for a hacky hint that this is our own already-rewritten query; this is likely
|
|
974
|
+
// because `findAndRewrite` is mutating condition expressions that get passed into
|
|
975
|
+
// `parseFindQuery` multiple times, i.e. while batching/dataloading.
|
|
976
|
+
//
|
|
977
|
+
// ...although in theory parseExpression should already be making a copy of any user-facing
|
|
978
|
+
// `em.find` conditions. :thinking:
|
|
979
|
+
if ("rewritten" in cond)
|
|
980
|
+
return;
|
|
981
|
+
throw new Error("Joist doesn't support raw conditions in lateral joins yet");
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
(0, utils_1.assertNever)(cond);
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
return found;
|
|
990
|
+
}
|
|
742
991
|
}
|
|
743
992
|
exports.ConditionBuilder = ConditionBuilder;
|
|
744
993
|
/** Converts domain-level values like string ids/enums into their db equivalent. */
|
|
@@ -835,7 +1084,6 @@ function addTablePerClassJoinsAndClassTag(query, meta, alias, isPrimary) {
|
|
|
835
1084
|
join: "outer",
|
|
836
1085
|
col1: (0, keywords_1.kqDot)(alias, "id"),
|
|
837
1086
|
col2: `${alias}_b${i}.id`,
|
|
838
|
-
distinct: false,
|
|
839
1087
|
});
|
|
840
1088
|
});
|
|
841
1089
|
// We always join in the base table in case a query happens to use
|
|
@@ -855,7 +1103,6 @@ function addTablePerClassJoinsAndClassTag(query, meta, alias, isPrimary) {
|
|
|
855
1103
|
join: "outer",
|
|
856
1104
|
col1: (0, keywords_1.kqDot)(alias, "id"),
|
|
857
1105
|
col2: `${alias}_s${i}.id`,
|
|
858
|
-
distinct: false,
|
|
859
1106
|
});
|
|
860
1107
|
for (const field of Object.values(st.fields)) {
|
|
861
1108
|
if (field.fieldName !== "id" && field.serde) {
|
|
@@ -881,16 +1128,25 @@ function addTablePerClassJoinsAndClassTag(query, meta, alias, isPrimary) {
|
|
|
881
1128
|
}
|
|
882
1129
|
}
|
|
883
1130
|
}
|
|
884
|
-
function maybeAddNotSoftDeleted(
|
|
1131
|
+
function maybeAddNotSoftDeleted(
|
|
1132
|
+
// Within this file we pass ConditionBuilder, but findByUniqueDataLoader passes ColumnCondition[]
|
|
1133
|
+
cb, softDeletes, meta, alias) {
|
|
885
1134
|
if (filterSoftDeletes(meta, softDeletes)) {
|
|
886
1135
|
const column = meta.allFields[(0, EntityMetadata_1.getBaseMeta)(meta).timestampFields.deletedAt].serde?.columns[0];
|
|
887
|
-
|
|
1136
|
+
const condition = {
|
|
888
1137
|
kind: "column",
|
|
889
1138
|
alias,
|
|
890
1139
|
column: column.columnName,
|
|
891
1140
|
dbType: column.dbType,
|
|
892
1141
|
cond: { kind: "is-null" },
|
|
893
|
-
|
|
1142
|
+
pruneable: true,
|
|
1143
|
+
};
|
|
1144
|
+
if (cb instanceof ConditionBuilder) {
|
|
1145
|
+
cb.addSimpleCondition(condition);
|
|
1146
|
+
}
|
|
1147
|
+
else {
|
|
1148
|
+
cb.push(condition);
|
|
1149
|
+
}
|
|
894
1150
|
}
|
|
895
1151
|
}
|
|
896
1152
|
function filterSoftDeletes(meta, softDeletes) {
|
|
@@ -899,12 +1155,15 @@ function filterSoftDeletes(meta, softDeletes) {
|
|
|
899
1155
|
// We don't support CTI subtype soft-delete filtering yet
|
|
900
1156
|
(meta.inheritanceType !== "cti" || meta.baseTypes.length === 0));
|
|
901
1157
|
}
|
|
1158
|
+
/** Parses user-facing `{ and: ... }` or `{ or: ... }` into a `ParsedExpressionFilter`. */
|
|
902
1159
|
function parseExpression(expression) {
|
|
1160
|
+
// Look for `{ and: [...] }` or `{ or: [...] }`
|
|
903
1161
|
const [op, expressions] = "and" in expression && expression.and
|
|
904
1162
|
? ["and", expression.and]
|
|
905
1163
|
: "or" in expression && expression.or
|
|
906
1164
|
? ["or", expression.or]
|
|
907
1165
|
: (0, utils_1.fail)(`Invalid expression ${expression}`);
|
|
1166
|
+
// Potentially recurse into nested expressions
|
|
908
1167
|
const conditions = expressions.map((exp) => (exp && ("and" in exp || "or" in exp) ? parseExpression(exp) : exp));
|
|
909
1168
|
const [skip, valid] = (0, utils_1.partition)(conditions, (cond) => cond === undefined || cond === exports.skipCondition);
|
|
910
1169
|
if ((skip.length > 0 && expression.pruneIfUndefined === "any") || valid.length === 0) {
|
|
@@ -915,24 +1174,23 @@ function parseExpression(expression) {
|
|
|
915
1174
|
function getTables(query) {
|
|
916
1175
|
let primary;
|
|
917
1176
|
const joins = [];
|
|
1177
|
+
const laterals = [];
|
|
1178
|
+
const crosses = [];
|
|
918
1179
|
for (const table of query.tables) {
|
|
919
1180
|
if (table.join === "primary") {
|
|
920
1181
|
primary = table;
|
|
921
1182
|
}
|
|
1183
|
+
else if (table.join === "lateral") {
|
|
1184
|
+
laterals.push(table);
|
|
1185
|
+
}
|
|
1186
|
+
else if (table.join === "cross") {
|
|
1187
|
+
crosses.push(table);
|
|
1188
|
+
}
|
|
922
1189
|
else {
|
|
923
1190
|
joins.push(table);
|
|
924
1191
|
}
|
|
925
1192
|
}
|
|
926
|
-
return [primary, joins];
|
|
927
|
-
}
|
|
928
|
-
function joinKeywords(join) {
|
|
929
|
-
return join.join === "inner" ? "JOIN" : "LEFT OUTER JOIN";
|
|
930
|
-
}
|
|
931
|
-
function joinClause(join) {
|
|
932
|
-
return `${joinKeywords(join)} ${(0, keywords_1.kq)(join.table)} ${(0, keywords_1.kq)(join.alias)} ON ${join.col1} = ${join.col2}`;
|
|
933
|
-
}
|
|
934
|
-
function joinClauses(joins) {
|
|
935
|
-
return joins.map((t) => (t.join !== "primary" ? joinClause(t) : ""));
|
|
1193
|
+
return [primary, joins, laterals, crosses];
|
|
936
1194
|
}
|
|
937
1195
|
function needsClassPerTableJoins(meta) {
|
|
938
1196
|
return meta.inheritanceType === "cti" && (meta.subTypes.length > 0 || meta.baseTypes.length > 0);
|
|
@@ -951,8 +1209,60 @@ function addStiSubtypeFilter(cb, subtypeMeta, alias) {
|
|
|
951
1209
|
cond: { kind: "eq", value: subtypeMeta.stiDiscriminatorValue },
|
|
952
1210
|
});
|
|
953
1211
|
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Given a filter that might be an `alias(Author)` placeholder, or have an `as: author`
|
|
1214
|
+
* binding, tells the `Alias` its canonical meta/alias.
|
|
1215
|
+
*
|
|
1216
|
+
* That way, when we later walk `conditions` and build the `AND/OR` tree, each condition
|
|
1217
|
+
* will know the canonical alias to output into the SQL clause.
|
|
1218
|
+
*/
|
|
1219
|
+
function bindAlias(filter, meta, alias) {
|
|
1220
|
+
// The user's locally declared aliases, i.e. `const [a, b] = aliases(Author, Book)`,
|
|
1221
|
+
// aren't guaranteed to line up with the aliases we've assigned internally, like `a`
|
|
1222
|
+
// might actually be `a1` if there are two `authors` tables in the query, so push the
|
|
1223
|
+
// canonical alias value for the current clause into the Alias.
|
|
1224
|
+
if (filter && typeof filter === "object" && "as" in filter && (0, Aliases_1.isAlias)(filter.as)) {
|
|
1225
|
+
filter.as[Aliases_1.aliasMgmt].setAlias(meta, alias);
|
|
1226
|
+
}
|
|
1227
|
+
else if ((0, Aliases_1.isAlias)(filter)) {
|
|
1228
|
+
filter[Aliases_1.aliasMgmt].setAlias(meta, alias);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
954
1231
|
/** Converts a search term like `foo bar` into a SQL `like` pattern like `%foo%bar%`. */
|
|
955
1232
|
function makeLike(search) {
|
|
956
1233
|
return search ? `%${search.replace(/\s+/g, "%")}%` : undefined;
|
|
957
1234
|
}
|
|
1235
|
+
/** Track join dependencies for `pruneUnusedJoins`. */
|
|
1236
|
+
class DependencyTracker {
|
|
1237
|
+
nodes = new Map();
|
|
1238
|
+
required = new Set();
|
|
1239
|
+
addAlias(alias, dependencies = []) {
|
|
1240
|
+
this.nodes.set(alias, dependencies);
|
|
1241
|
+
}
|
|
1242
|
+
markRequired(alias) {
|
|
1243
|
+
const marked = new Set();
|
|
1244
|
+
const todo = [alias];
|
|
1245
|
+
while (todo.length > 0) {
|
|
1246
|
+
const alias = todo.pop();
|
|
1247
|
+
if (!marked.has(alias)) {
|
|
1248
|
+
marked.add(alias);
|
|
1249
|
+
todo.push(...(this.nodes.get(alias) || []));
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
this.required = new Set([...this.required, ...marked]);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
/** Takes a `{ column: "$count", kind: eq/gt/etc }` and turns it into a ParsedSelect. */
|
|
1256
|
+
function buildCountStar(cc) {
|
|
1257
|
+
// Reuse buildCondition to get the et/gt/etc --> operator
|
|
1258
|
+
const [op, bindings] = (0, buildUtils_1.buildCondition)(cc.cond);
|
|
1259
|
+
// But swap the dummy column name with `count(*)`
|
|
1260
|
+
const parts = op.split(" ");
|
|
1261
|
+
parts[0] = "count(*)";
|
|
1262
|
+
return {
|
|
1263
|
+
sql: `${parts.join(" ")} as ${cc.as}`,
|
|
1264
|
+
aliases: [cc.cond.alias],
|
|
1265
|
+
bindings,
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
958
1268
|
//# sourceMappingURL=QueryParser.js.map
|