@tanstack/db 0.1.1 → 0.1.4
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/cjs/collection.cjs +112 -6
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +12 -3
- package/dist/cjs/errors.cjs +6 -0
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/errors.d.cts +3 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.cjs +30 -19
- package/dist/cjs/indexes/auto-index.cjs.map +1 -1
- package/dist/cjs/indexes/auto-index.d.cts +1 -0
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +2 -1
- package/dist/cjs/indexes/btree-index.cjs +29 -3
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +7 -0
- package/dist/cjs/indexes/index-options.d.cts +1 -1
- package/dist/cjs/query/builder/index.cjs +9 -2
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/builder/index.d.cts +2 -2
- package/dist/cjs/query/builder/types.d.cts +27 -6
- package/dist/cjs/query/compiler/evaluators.cjs +2 -2
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/evaluators.d.cts +1 -1
- package/dist/cjs/query/compiler/group-by.cjs +3 -1
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs +72 -6
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.d.cts +16 -2
- package/dist/cjs/query/compiler/joins.cjs +111 -12
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.d.cts +9 -2
- package/dist/cjs/query/compiler/order-by.cjs +80 -23
- package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/order-by.d.cts +12 -2
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +2 -1
- package/dist/cjs/query/live-query-collection.cjs +196 -23
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/types.d.cts +1 -0
- package/dist/cjs/utils/btree.cjs +15 -0
- package/dist/cjs/utils/btree.cjs.map +1 -1
- package/dist/cjs/utils/btree.d.cts +8 -0
- package/dist/cjs/utils/comparison.cjs +29 -7
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/comparison.d.cts +6 -2
- package/dist/esm/collection.d.ts +12 -3
- package/dist/esm/collection.js +113 -7
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.js +6 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/indexes/auto-index.d.ts +1 -0
- package/dist/esm/indexes/auto-index.js +31 -20
- package/dist/esm/indexes/auto-index.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +2 -1
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.d.ts +7 -0
- package/dist/esm/indexes/btree-index.js +29 -3
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/index-options.d.ts +1 -1
- package/dist/esm/query/builder/index.d.ts +2 -2
- package/dist/esm/query/builder/index.js +9 -2
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/builder/types.d.ts +27 -6
- package/dist/esm/query/compiler/evaluators.d.ts +1 -1
- package/dist/esm/query/compiler/evaluators.js +2 -2
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js +3 -1
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.d.ts +16 -2
- package/dist/esm/query/compiler/index.js +73 -7
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/compiler/joins.d.ts +9 -2
- package/dist/esm/query/compiler/joins.js +114 -15
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/compiler/order-by.d.ts +12 -2
- package/dist/esm/query/compiler/order-by.js +81 -24
- package/dist/esm/query/compiler/order-by.js.map +1 -1
- package/dist/esm/query/ir.d.ts +2 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live-query-collection.js +196 -23
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/types.d.ts +1 -0
- package/dist/esm/utils/btree.d.ts +8 -0
- package/dist/esm/utils/btree.js +15 -0
- package/dist/esm/utils/btree.js.map +1 -1
- package/dist/esm/utils/comparison.d.ts +6 -2
- package/dist/esm/utils/comparison.js +30 -8
- package/dist/esm/utils/comparison.js.map +1 -1
- package/package.json +2 -2
- package/src/collection.ts +237 -11
- package/src/errors.ts +6 -0
- package/src/indexes/auto-index.ts +53 -31
- package/src/indexes/base-index.ts +6 -1
- package/src/indexes/btree-index.ts +32 -3
- package/src/indexes/index-options.ts +2 -2
- package/src/query/builder/index.ts +19 -2
- package/src/query/builder/types.ts +48 -15
- package/src/query/compiler/evaluators.ts +6 -3
- package/src/query/compiler/group-by.ts +3 -1
- package/src/query/compiler/index.ts +112 -5
- package/src/query/compiler/joins.ts +216 -20
- package/src/query/compiler/order-by.ts +117 -26
- package/src/query/ir.ts +2 -1
- package/src/query/live-query-collection.ts +352 -24
- package/src/types.ts +1 -0
- package/src/utils/btree.ts +17 -0
- package/src/utils/comparison.ts +40 -7
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { map, filter, distinct } from "@tanstack/db-ivm";
|
|
2
2
|
import { optimizeQuery } from "../optimizer.js";
|
|
3
3
|
import { DistinctRequiresSelectError, HavingRequiresGroupByError, LimitOffsetRequireOrderByError, UnsupportedFromTypeError, CollectionInputNotFoundError } from "../../errors.js";
|
|
4
|
+
import { PropRef } from "../ir.js";
|
|
4
5
|
import { compileExpression } from "./evaluators.js";
|
|
5
6
|
import { processJoins } from "./joins.js";
|
|
6
7
|
import { processGroupBy } from "./group-by.js";
|
|
7
8
|
import { processOrderBy } from "./order-by.js";
|
|
8
9
|
import { processSelectToResults } from "./select.js";
|
|
9
|
-
function compileQuery(rawQuery, inputs, cache = /* @__PURE__ */ new WeakMap(), queryMapping = /* @__PURE__ */ new WeakMap()) {
|
|
10
|
+
function compileQuery(rawQuery, inputs, collections, callbacks, lazyCollections, optimizableOrderByCollections, cache = /* @__PURE__ */ new WeakMap(), queryMapping = /* @__PURE__ */ new WeakMap()) {
|
|
10
11
|
const cachedResult = cache.get(rawQuery);
|
|
11
12
|
if (cachedResult) {
|
|
12
13
|
return cachedResult;
|
|
@@ -16,9 +17,17 @@ function compileQuery(rawQuery, inputs, cache = /* @__PURE__ */ new WeakMap(), q
|
|
|
16
17
|
mapNestedQueries(query, rawQuery, queryMapping);
|
|
17
18
|
const allInputs = { ...inputs };
|
|
18
19
|
const tables = {};
|
|
19
|
-
const {
|
|
20
|
+
const {
|
|
21
|
+
alias: mainTableAlias,
|
|
22
|
+
input: mainInput,
|
|
23
|
+
collectionId: mainCollectionId
|
|
24
|
+
} = processFrom(
|
|
20
25
|
query.from,
|
|
21
26
|
allInputs,
|
|
27
|
+
collections,
|
|
28
|
+
callbacks,
|
|
29
|
+
lazyCollections,
|
|
30
|
+
optimizableOrderByCollections,
|
|
22
31
|
cache,
|
|
23
32
|
queryMapping
|
|
24
33
|
);
|
|
@@ -34,10 +43,16 @@ function compileQuery(rawQuery, inputs, cache = /* @__PURE__ */ new WeakMap(), q
|
|
|
34
43
|
pipeline,
|
|
35
44
|
query.join,
|
|
36
45
|
tables,
|
|
46
|
+
mainCollectionId,
|
|
37
47
|
mainTableAlias,
|
|
38
48
|
allInputs,
|
|
39
49
|
cache,
|
|
40
|
-
queryMapping
|
|
50
|
+
queryMapping,
|
|
51
|
+
collections,
|
|
52
|
+
callbacks,
|
|
53
|
+
lazyCollections,
|
|
54
|
+
optimizableOrderByCollections,
|
|
55
|
+
rawQuery
|
|
41
56
|
);
|
|
42
57
|
}
|
|
43
58
|
if (query.where && query.where.length > 0) {
|
|
@@ -134,8 +149,11 @@ function compileQuery(rawQuery, inputs, cache = /* @__PURE__ */ new WeakMap(), q
|
|
|
134
149
|
}
|
|
135
150
|
if (query.orderBy && query.orderBy.length > 0) {
|
|
136
151
|
const orderedPipeline = processOrderBy(
|
|
152
|
+
rawQuery,
|
|
137
153
|
pipeline,
|
|
138
154
|
query.orderBy,
|
|
155
|
+
collections[mainCollectionId],
|
|
156
|
+
optimizableOrderByCollections,
|
|
139
157
|
query.limit,
|
|
140
158
|
query.offset
|
|
141
159
|
);
|
|
@@ -147,6 +165,7 @@ function compileQuery(rawQuery, inputs, cache = /* @__PURE__ */ new WeakMap(), q
|
|
|
147
165
|
);
|
|
148
166
|
const result2 = resultPipeline2;
|
|
149
167
|
const compilationResult2 = {
|
|
168
|
+
collectionId: mainCollectionId,
|
|
150
169
|
pipeline: result2,
|
|
151
170
|
collectionWhereClauses
|
|
152
171
|
};
|
|
@@ -163,26 +182,31 @@ function compileQuery(rawQuery, inputs, cache = /* @__PURE__ */ new WeakMap(), q
|
|
|
163
182
|
);
|
|
164
183
|
const result = resultPipeline;
|
|
165
184
|
const compilationResult = {
|
|
185
|
+
collectionId: mainCollectionId,
|
|
166
186
|
pipeline: result,
|
|
167
187
|
collectionWhereClauses
|
|
168
188
|
};
|
|
169
189
|
cache.set(rawQuery, compilationResult);
|
|
170
190
|
return compilationResult;
|
|
171
191
|
}
|
|
172
|
-
function processFrom(from, allInputs, cache, queryMapping) {
|
|
192
|
+
function processFrom(from, allInputs, collections, callbacks, lazyCollections, optimizableOrderByCollections, cache, queryMapping) {
|
|
173
193
|
switch (from.type) {
|
|
174
194
|
case `collectionRef`: {
|
|
175
195
|
const input = allInputs[from.collection.id];
|
|
176
196
|
if (!input) {
|
|
177
197
|
throw new CollectionInputNotFoundError(from.collection.id);
|
|
178
198
|
}
|
|
179
|
-
return { alias: from.alias, input };
|
|
199
|
+
return { alias: from.alias, input, collectionId: from.collection.id };
|
|
180
200
|
}
|
|
181
201
|
case `queryRef`: {
|
|
182
202
|
const originalQuery = queryMapping.get(from.query) || from.query;
|
|
183
203
|
const subQueryResult = compileQuery(
|
|
184
204
|
originalQuery,
|
|
185
205
|
allInputs,
|
|
206
|
+
collections,
|
|
207
|
+
callbacks,
|
|
208
|
+
lazyCollections,
|
|
209
|
+
optimizableOrderByCollections,
|
|
186
210
|
cache,
|
|
187
211
|
queryMapping
|
|
188
212
|
);
|
|
@@ -193,7 +217,11 @@ function processFrom(from, allInputs, cache, queryMapping) {
|
|
|
193
217
|
return [key, value];
|
|
194
218
|
})
|
|
195
219
|
);
|
|
196
|
-
return {
|
|
220
|
+
return {
|
|
221
|
+
alias: from.alias,
|
|
222
|
+
input: extractedInput,
|
|
223
|
+
collectionId: subQueryResult.collectionId
|
|
224
|
+
};
|
|
197
225
|
}
|
|
198
226
|
default:
|
|
199
227
|
throw new UnsupportedFromTypeError(from.type);
|
|
@@ -223,7 +251,45 @@ function mapNestedQueries(optimizedQuery, originalQuery, queryMapping) {
|
|
|
223
251
|
}
|
|
224
252
|
}
|
|
225
253
|
}
|
|
254
|
+
function getRefFromAlias(query, alias) {
|
|
255
|
+
if (query.from.alias === alias) {
|
|
256
|
+
return query.from;
|
|
257
|
+
}
|
|
258
|
+
for (const join of query.join || []) {
|
|
259
|
+
if (join.from.alias === alias) {
|
|
260
|
+
return join.from;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function followRef(query, ref, collection) {
|
|
265
|
+
if (ref.path.length === 0) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (ref.path.length === 1) {
|
|
269
|
+
const field = ref.path[0];
|
|
270
|
+
if (query.select) {
|
|
271
|
+
const selectedField = query.select[field];
|
|
272
|
+
if (selectedField && selectedField.type === `ref`) {
|
|
273
|
+
return followRef(query, selectedField, collection);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return { collection, path: [field] };
|
|
277
|
+
}
|
|
278
|
+
if (ref.path.length > 1) {
|
|
279
|
+
const [alias, ...rest] = ref.path;
|
|
280
|
+
const aliasRef = getRefFromAlias(query, alias);
|
|
281
|
+
if (!aliasRef) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (aliasRef.type === `queryRef`) {
|
|
285
|
+
return followRef(aliasRef.query, new PropRef(rest), collection);
|
|
286
|
+
} else {
|
|
287
|
+
return { collection: aliasRef.collection, path: rest };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
226
291
|
export {
|
|
227
|
-
compileQuery
|
|
292
|
+
compileQuery,
|
|
293
|
+
followRef
|
|
228
294
|
};
|
|
229
295
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/query/compiler/index.ts"],"sourcesContent":["import { distinct, filter, map } from \"@tanstack/db-ivm\"\nimport { optimizeQuery } from \"../optimizer.js\"\nimport {\n CollectionInputNotFoundError,\n DistinctRequiresSelectError,\n HavingRequiresGroupByError,\n LimitOffsetRequireOrderByError,\n UnsupportedFromTypeError,\n} from \"../../errors.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { processJoins } from \"./joins.js\"\nimport { processGroupBy } from \"./group-by.js\"\nimport { processOrderBy } from \"./order-by.js\"\nimport { processSelectToResults } from \"./select.js\"\nimport type {\n BasicExpression,\n CollectionRef,\n QueryIR,\n QueryRef,\n} from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n ResultStream,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping } from \"./types.js\"\n\n/**\n * Result of query compilation including both the pipeline and collection-specific WHERE clauses\n */\nexport interface CompilationResult {\n /** The compiled query pipeline */\n pipeline: ResultStream\n /** Map of collection aliases to their WHERE clauses for index optimization */\n collectionWhereClauses: Map<string, BasicExpression<boolean>>\n}\n\n/**\n * Compiles a query2 IR into a D2 pipeline\n * @param rawQuery The query IR to compile\n * @param inputs Mapping of collection names to input streams\n * @param cache Optional cache for compiled subqueries (used internally for recursion)\n * @param queryMapping Optional mapping from optimized queries to original queries\n * @returns A CompilationResult with the pipeline and collection WHERE clauses\n */\nexport function compileQuery(\n rawQuery: QueryIR,\n inputs: Record<string, KeyedStream>,\n cache: QueryCache = new WeakMap(),\n queryMapping: QueryMapping = new WeakMap()\n): CompilationResult {\n // Check if the original raw query has already been compiled\n const cachedResult = cache.get(rawQuery)\n if (cachedResult) {\n return cachedResult\n }\n\n // Optimize the query before compilation\n const { optimizedQuery: query, collectionWhereClauses } =\n optimizeQuery(rawQuery)\n\n // Create mapping from optimized query to original for caching\n queryMapping.set(query, rawQuery)\n mapNestedQueries(query, rawQuery, queryMapping)\n\n // Create a copy of the inputs map to avoid modifying the original\n const allInputs = { ...inputs }\n\n // Create a map of table aliases to inputs\n const tables: Record<string, KeyedStream> = {}\n\n // Process the FROM clause to get the main table\n const { alias: mainTableAlias, input: mainInput } = processFrom(\n query.from,\n allInputs,\n cache,\n queryMapping\n )\n tables[mainTableAlias] = mainInput\n\n // Prepare the initial pipeline with the main table wrapped in its alias\n let pipeline: NamespacedAndKeyedStream = mainInput.pipe(\n map(([key, row]) => {\n // Initialize the record with a nested structure\n const ret = [key, { [mainTableAlias]: row }] as [\n string,\n Record<string, typeof row>,\n ]\n return ret\n })\n )\n\n // Process JOIN clauses if they exist\n if (query.join && query.join.length > 0) {\n pipeline = processJoins(\n pipeline,\n query.join,\n tables,\n mainTableAlias,\n allInputs,\n cache,\n queryMapping\n )\n }\n\n // Process the WHERE clause if it exists\n if (query.where && query.where.length > 0) {\n // Apply each WHERE condition as a filter (they are ANDed together)\n for (const where of query.where) {\n const compiledWhere = compileExpression(where)\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return compiledWhere(namespacedRow)\n })\n )\n }\n }\n\n // Process functional WHERE clauses if they exist\n if (query.fnWhere && query.fnWhere.length > 0) {\n for (const fnWhere of query.fnWhere) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return fnWhere(namespacedRow)\n })\n )\n }\n }\n\n if (query.distinct && !query.fnSelect && !query.select) {\n throw new DistinctRequiresSelectError()\n }\n\n // Process the SELECT clause early - always create __select_results\n // This eliminates duplication and allows for DISTINCT implementation\n if (query.fnSelect) {\n // Handle functional select - apply the function to transform the row\n pipeline = pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults = query.fnSelect!(namespacedRow)\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [string, typeof namespacedRow & { __select_results: any }]\n })\n )\n } else if (query.select) {\n pipeline = processSelectToResults(pipeline, query.select, allInputs)\n } else {\n // If no SELECT clause, create __select_results with the main table data\n pipeline = pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults =\n !query.join && !query.groupBy\n ? namespacedRow[mainTableAlias]\n : namespacedRow\n\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [string, typeof namespacedRow & { __select_results: any }]\n })\n )\n }\n\n // Process the GROUP BY clause if it exists\n if (query.groupBy && query.groupBy.length > 0) {\n pipeline = processGroupBy(\n pipeline,\n query.groupBy,\n query.having,\n query.select,\n query.fnHaving\n )\n } else if (query.select) {\n // Check if SELECT contains aggregates but no GROUP BY (implicit single-group aggregation)\n const hasAggregates = Object.values(query.select).some(\n (expr) => expr.type === `agg`\n )\n if (hasAggregates) {\n // Handle implicit single-group aggregation\n pipeline = processGroupBy(\n pipeline,\n [], // Empty group by means single group\n query.having,\n query.select,\n query.fnHaving\n )\n }\n }\n\n // Process the HAVING clause if it exists (only applies after GROUP BY)\n if (query.having && (!query.groupBy || query.groupBy.length === 0)) {\n // Check if we have aggregates in SELECT that would trigger implicit grouping\n const hasAggregates = query.select\n ? Object.values(query.select).some((expr) => expr.type === `agg`)\n : false\n\n if (!hasAggregates) {\n throw new HavingRequiresGroupByError()\n }\n }\n\n // Process functional HAVING clauses outside of GROUP BY (treat as additional WHERE filters)\n if (\n query.fnHaving &&\n query.fnHaving.length > 0 &&\n (!query.groupBy || query.groupBy.length === 0)\n ) {\n // If there's no GROUP BY but there are fnHaving clauses, apply them as filters\n for (const fnHaving of query.fnHaving) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n // Process the DISTINCT clause if it exists\n if (query.distinct) {\n pipeline = pipeline.pipe(distinct(([_key, row]) => row.__select_results))\n }\n\n // Process orderBy parameter if it exists\n if (query.orderBy && query.orderBy.length > 0) {\n const orderedPipeline = processOrderBy(\n pipeline,\n query.orderBy,\n query.limit,\n query.offset\n )\n\n // Final step: extract the __select_results and include orderBy index\n const resultPipeline = orderedPipeline.pipe(\n map(([key, [row, orderByIndex]]) => {\n // Extract the final results from __select_results and include orderBy index\n const finalResults = (row as any).__select_results\n return [key, [finalResults, orderByIndex]] as [unknown, [any, string]]\n })\n )\n\n const result = resultPipeline\n // Cache the result before returning (use original query as key)\n const compilationResult = {\n pipeline: result,\n collectionWhereClauses,\n }\n cache.set(rawQuery, compilationResult)\n\n return compilationResult\n } else if (query.limit !== undefined || query.offset !== undefined) {\n // If there's a limit or offset without orderBy, throw an error\n throw new LimitOffsetRequireOrderByError()\n }\n\n // Final step: extract the __select_results and return tuple format (no orderBy)\n const resultPipeline: ResultStream = pipeline.pipe(\n map(([key, row]) => {\n // Extract the final results from __select_results and return [key, [results, undefined]]\n const finalResults = (row as any).__select_results\n return [key, [finalResults, undefined]] as [\n unknown,\n [any, string | undefined],\n ]\n })\n )\n\n const result = resultPipeline\n // Cache the result before returning (use original query as key)\n const compilationResult = {\n pipeline: result,\n collectionWhereClauses,\n }\n cache.set(rawQuery, compilationResult)\n\n return compilationResult\n}\n\n/**\n * Processes the FROM clause to extract the main table alias and input stream\n */\nfunction processFrom(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): { alias: string; input: KeyedStream } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new CollectionInputNotFoundError(from.collection.id)\n }\n return { alias: from.alias, input }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryResult = compileQuery(\n originalQuery,\n allInputs,\n cache,\n queryMapping\n )\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return { alias: from.alias, input: extractedInput }\n }\n default:\n throw new UnsupportedFromTypeError((from as any).type)\n }\n}\n\n/**\n * Recursively maps optimized subqueries to their original queries for proper caching.\n * This ensures that when we encounter the same QueryRef object in different contexts,\n * we can find the original query to check the cache.\n */\nfunction mapNestedQueries(\n optimizedQuery: QueryIR,\n originalQuery: QueryIR,\n queryMapping: QueryMapping\n): void {\n // Map the FROM clause if it's a QueryRef\n if (\n optimizedQuery.from.type === `queryRef` &&\n originalQuery.from.type === `queryRef`\n ) {\n queryMapping.set(optimizedQuery.from.query, originalQuery.from.query)\n // Recursively map nested queries\n mapNestedQueries(\n optimizedQuery.from.query,\n originalQuery.from.query,\n queryMapping\n )\n }\n\n // Map JOIN clauses if they exist\n if (optimizedQuery.join && originalQuery.join) {\n for (\n let i = 0;\n i < optimizedQuery.join.length && i < originalQuery.join.length;\n i++\n ) {\n const optimizedJoin = optimizedQuery.join[i]!\n const originalJoin = originalQuery.join[i]!\n\n if (\n optimizedJoin.from.type === `queryRef` &&\n originalJoin.from.type === `queryRef`\n ) {\n queryMapping.set(optimizedJoin.from.query, originalJoin.from.query)\n // Recursively map nested queries in joins\n mapNestedQueries(\n optimizedJoin.from.query,\n originalJoin.from.query,\n queryMapping\n )\n }\n }\n }\n}\n"],"names":["resultPipeline","result","compilationResult"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/query/compiler/index.ts"],"sourcesContent":["import { distinct, filter, map } from \"@tanstack/db-ivm\"\nimport { optimizeQuery } from \"../optimizer.js\"\nimport {\n CollectionInputNotFoundError,\n DistinctRequiresSelectError,\n HavingRequiresGroupByError,\n LimitOffsetRequireOrderByError,\n UnsupportedFromTypeError,\n} from \"../../errors.js\"\nimport { PropRef } from \"../ir.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { processJoins } from \"./joins.js\"\nimport { processGroupBy } from \"./group-by.js\"\nimport { processOrderBy } from \"./order-by.js\"\nimport { processSelectToResults } from \"./select.js\"\nimport type { OrderByOptimizationInfo } from \"./order-by.js\"\nimport type {\n BasicExpression,\n CollectionRef,\n QueryIR,\n QueryRef,\n} from \"../ir.js\"\nimport type { LazyCollectionCallbacks } from \"./joins.js\"\nimport type { Collection } from \"../../collection.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n ResultStream,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping } from \"./types.js\"\n\n/**\n * Result of query compilation including both the pipeline and collection-specific WHERE clauses\n */\nexport interface CompilationResult {\n /** The ID of the main collection */\n collectionId: string\n /** The compiled query pipeline */\n pipeline: ResultStream\n /** Map of collection aliases to their WHERE clauses for index optimization */\n collectionWhereClauses: Map<string, BasicExpression<boolean>>\n}\n\n/**\n * Compiles a query2 IR into a D2 pipeline\n * @param rawQuery The query IR to compile\n * @param inputs Mapping of collection names to input streams\n * @param cache Optional cache for compiled subqueries (used internally for recursion)\n * @param queryMapping Optional mapping from optimized queries to original queries\n * @returns A CompilationResult with the pipeline and collection WHERE clauses\n */\nexport function compileQuery(\n rawQuery: QueryIR,\n inputs: Record<string, KeyedStream>,\n collections: Record<string, Collection<any, any, any, any, any>>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n cache: QueryCache = new WeakMap(),\n queryMapping: QueryMapping = new WeakMap()\n): CompilationResult {\n // Check if the original raw query has already been compiled\n const cachedResult = cache.get(rawQuery)\n if (cachedResult) {\n return cachedResult\n }\n\n // Optimize the query before compilation\n const { optimizedQuery: query, collectionWhereClauses } =\n optimizeQuery(rawQuery)\n\n // Create mapping from optimized query to original for caching\n queryMapping.set(query, rawQuery)\n mapNestedQueries(query, rawQuery, queryMapping)\n\n // Create a copy of the inputs map to avoid modifying the original\n const allInputs = { ...inputs }\n\n // Create a map of table aliases to inputs\n const tables: Record<string, KeyedStream> = {}\n\n // Process the FROM clause to get the main table\n const {\n alias: mainTableAlias,\n input: mainInput,\n collectionId: mainCollectionId,\n } = processFrom(\n query.from,\n allInputs,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n tables[mainTableAlias] = mainInput\n\n // Prepare the initial pipeline with the main table wrapped in its alias\n let pipeline: NamespacedAndKeyedStream = mainInput.pipe(\n map(([key, row]) => {\n // Initialize the record with a nested structure\n const ret = [key, { [mainTableAlias]: row }] as [\n string,\n Record<string, typeof row>,\n ]\n return ret\n })\n )\n\n // Process JOIN clauses if they exist\n if (query.join && query.join.length > 0) {\n pipeline = processJoins(\n pipeline,\n query.join,\n tables,\n mainCollectionId,\n mainTableAlias,\n allInputs,\n cache,\n queryMapping,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n rawQuery\n )\n }\n\n // Process the WHERE clause if it exists\n if (query.where && query.where.length > 0) {\n // Apply each WHERE condition as a filter (they are ANDed together)\n for (const where of query.where) {\n const compiledWhere = compileExpression(where)\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return compiledWhere(namespacedRow)\n })\n )\n }\n }\n\n // Process functional WHERE clauses if they exist\n if (query.fnWhere && query.fnWhere.length > 0) {\n for (const fnWhere of query.fnWhere) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return fnWhere(namespacedRow)\n })\n )\n }\n }\n\n if (query.distinct && !query.fnSelect && !query.select) {\n throw new DistinctRequiresSelectError()\n }\n\n // Process the SELECT clause early - always create __select_results\n // This eliminates duplication and allows for DISTINCT implementation\n if (query.fnSelect) {\n // Handle functional select - apply the function to transform the row\n pipeline = pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults = query.fnSelect!(namespacedRow)\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [string, typeof namespacedRow & { __select_results: any }]\n })\n )\n } else if (query.select) {\n pipeline = processSelectToResults(pipeline, query.select, allInputs)\n } else {\n // If no SELECT clause, create __select_results with the main table data\n pipeline = pipeline.pipe(\n map(([key, namespacedRow]) => {\n const selectResults =\n !query.join && !query.groupBy\n ? namespacedRow[mainTableAlias]\n : namespacedRow\n\n return [\n key,\n {\n ...namespacedRow,\n __select_results: selectResults,\n },\n ] as [string, typeof namespacedRow & { __select_results: any }]\n })\n )\n }\n\n // Process the GROUP BY clause if it exists\n if (query.groupBy && query.groupBy.length > 0) {\n pipeline = processGroupBy(\n pipeline,\n query.groupBy,\n query.having,\n query.select,\n query.fnHaving\n )\n } else if (query.select) {\n // Check if SELECT contains aggregates but no GROUP BY (implicit single-group aggregation)\n const hasAggregates = Object.values(query.select).some(\n (expr) => expr.type === `agg`\n )\n if (hasAggregates) {\n // Handle implicit single-group aggregation\n pipeline = processGroupBy(\n pipeline,\n [], // Empty group by means single group\n query.having,\n query.select,\n query.fnHaving\n )\n }\n }\n\n // Process the HAVING clause if it exists (only applies after GROUP BY)\n if (query.having && (!query.groupBy || query.groupBy.length === 0)) {\n // Check if we have aggregates in SELECT that would trigger implicit grouping\n const hasAggregates = query.select\n ? Object.values(query.select).some((expr) => expr.type === `agg`)\n : false\n\n if (!hasAggregates) {\n throw new HavingRequiresGroupByError()\n }\n }\n\n // Process functional HAVING clauses outside of GROUP BY (treat as additional WHERE filters)\n if (\n query.fnHaving &&\n query.fnHaving.length > 0 &&\n (!query.groupBy || query.groupBy.length === 0)\n ) {\n // If there's no GROUP BY but there are fnHaving clauses, apply them as filters\n for (const fnHaving of query.fnHaving) {\n pipeline = pipeline.pipe(\n filter(([_key, namespacedRow]) => {\n return fnHaving(namespacedRow)\n })\n )\n }\n }\n\n // Process the DISTINCT clause if it exists\n if (query.distinct) {\n pipeline = pipeline.pipe(distinct(([_key, row]) => row.__select_results))\n }\n\n // Process orderBy parameter if it exists\n if (query.orderBy && query.orderBy.length > 0) {\n const orderedPipeline = processOrderBy(\n rawQuery,\n pipeline,\n query.orderBy,\n collections[mainCollectionId]!,\n optimizableOrderByCollections,\n query.limit,\n query.offset\n )\n\n // Final step: extract the __select_results and include orderBy index\n const resultPipeline = orderedPipeline.pipe(\n map(([key, [row, orderByIndex]]) => {\n // Extract the final results from __select_results and include orderBy index\n const finalResults = (row as any).__select_results\n return [key, [finalResults, orderByIndex]] as [unknown, [any, string]]\n })\n )\n\n const result = resultPipeline\n // Cache the result before returning (use original query as key)\n const compilationResult = {\n collectionId: mainCollectionId,\n pipeline: result,\n collectionWhereClauses,\n }\n cache.set(rawQuery, compilationResult)\n\n return compilationResult\n } else if (query.limit !== undefined || query.offset !== undefined) {\n // If there's a limit or offset without orderBy, throw an error\n throw new LimitOffsetRequireOrderByError()\n }\n\n // Final step: extract the __select_results and return tuple format (no orderBy)\n const resultPipeline: ResultStream = pipeline.pipe(\n map(([key, row]) => {\n // Extract the final results from __select_results and return [key, [results, undefined]]\n const finalResults = (row as any).__select_results\n return [key, [finalResults, undefined]] as [\n unknown,\n [any, string | undefined],\n ]\n })\n )\n\n const result = resultPipeline\n // Cache the result before returning (use original query as key)\n const compilationResult = {\n collectionId: mainCollectionId,\n pipeline: result,\n collectionWhereClauses,\n }\n cache.set(rawQuery, compilationResult)\n\n return compilationResult\n}\n\n/**\n * Processes the FROM clause to extract the main table alias and input stream\n */\nfunction processFrom(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): { alias: string; input: KeyedStream; collectionId: string } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new CollectionInputNotFoundError(from.collection.id)\n }\n return { alias: from.alias, input, collectionId: from.collection.id }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryResult = compileQuery(\n originalQuery,\n allInputs,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return {\n alias: from.alias,\n input: extractedInput,\n collectionId: subQueryResult.collectionId,\n }\n }\n default:\n throw new UnsupportedFromTypeError((from as any).type)\n }\n}\n\n/**\n * Recursively maps optimized subqueries to their original queries for proper caching.\n * This ensures that when we encounter the same QueryRef object in different contexts,\n * we can find the original query to check the cache.\n */\nfunction mapNestedQueries(\n optimizedQuery: QueryIR,\n originalQuery: QueryIR,\n queryMapping: QueryMapping\n): void {\n // Map the FROM clause if it's a QueryRef\n if (\n optimizedQuery.from.type === `queryRef` &&\n originalQuery.from.type === `queryRef`\n ) {\n queryMapping.set(optimizedQuery.from.query, originalQuery.from.query)\n // Recursively map nested queries\n mapNestedQueries(\n optimizedQuery.from.query,\n originalQuery.from.query,\n queryMapping\n )\n }\n\n // Map JOIN clauses if they exist\n if (optimizedQuery.join && originalQuery.join) {\n for (\n let i = 0;\n i < optimizedQuery.join.length && i < originalQuery.join.length;\n i++\n ) {\n const optimizedJoin = optimizedQuery.join[i]!\n const originalJoin = originalQuery.join[i]!\n\n if (\n optimizedJoin.from.type === `queryRef` &&\n originalJoin.from.type === `queryRef`\n ) {\n queryMapping.set(optimizedJoin.from.query, originalJoin.from.query)\n // Recursively map nested queries in joins\n mapNestedQueries(\n optimizedJoin.from.query,\n originalJoin.from.query,\n queryMapping\n )\n }\n }\n }\n}\n\nfunction getRefFromAlias(\n query: QueryIR,\n alias: string\n): CollectionRef | QueryRef | void {\n if (query.from.alias === alias) {\n return query.from\n }\n\n for (const join of query.join || []) {\n if (join.from.alias === alias) {\n return join.from\n }\n }\n}\n\n/**\n * Follows the given reference in a query\n * until its finds the root field the reference points to.\n * @returns The collection, its alias, and the path to the root field in this collection\n */\nexport function followRef(\n query: QueryIR,\n ref: PropRef<any>,\n collection: Collection\n): { collection: Collection; path: Array<string> } | void {\n if (ref.path.length === 0) {\n return\n }\n\n if (ref.path.length === 1) {\n // This field should be part of this collection\n const field = ref.path[0]!\n // is it part of the select clause?\n if (query.select) {\n const selectedField = query.select[field]\n if (selectedField && selectedField.type === `ref`) {\n return followRef(query, selectedField, collection)\n }\n }\n\n // Either this field is not part of the select clause\n // and thus it must be part of the collection itself\n // or it is part of the select but is not a reference\n // so we can stop here and don't have to follow it\n return { collection, path: [field] }\n }\n\n if (ref.path.length > 1) {\n // This is a nested field\n const [alias, ...rest] = ref.path\n const aliasRef = getRefFromAlias(query, alias!)\n if (!aliasRef) {\n return\n }\n\n if (aliasRef.type === `queryRef`) {\n return followRef(aliasRef.query, new PropRef(rest), collection)\n } else {\n // This is a reference to a collection\n // we can't follow it further\n // so the field must be on the collection itself\n return { collection: aliasRef.collection, path: rest }\n }\n }\n}\n"],"names":["resultPipeline","result","compilationResult"],"mappings":"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"}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OrderByOptimizationInfo } from './order-by.js';
|
|
2
|
+
import { JoinClause, QueryIR } from '../ir.js';
|
|
3
|
+
import { Collection } from '../../collection.js';
|
|
2
4
|
import { KeyedStream, NamespacedAndKeyedStream } from '../../types.js';
|
|
3
5
|
import { QueryCache, QueryMapping } from './types.js';
|
|
6
|
+
export type LoadKeysFn = (key: Set<string | number>) => void;
|
|
7
|
+
export type LazyCollectionCallbacks = {
|
|
8
|
+
loadKeys: LoadKeysFn;
|
|
9
|
+
loadInitialState: () => void;
|
|
10
|
+
};
|
|
4
11
|
/**
|
|
5
12
|
* Processes all join clauses in a query
|
|
6
13
|
*/
|
|
7
|
-
export declare function processJoins(pipeline: NamespacedAndKeyedStream, joinClauses: Array<JoinClause>, tables: Record<string, KeyedStream>, mainTableAlias: string, allInputs: Record<string, KeyedStream>, cache: QueryCache, queryMapping: QueryMapping): NamespacedAndKeyedStream;
|
|
14
|
+
export declare function processJoins(pipeline: NamespacedAndKeyedStream, joinClauses: Array<JoinClause>, tables: Record<string, KeyedStream>, mainTableId: string, mainTableAlias: string, allInputs: Record<string, KeyedStream>, cache: QueryCache, queryMapping: QueryMapping, collections: Record<string, Collection>, callbacks: Record<string, LazyCollectionCallbacks>, lazyCollections: Set<string>, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, rawQuery: QueryIR): NamespacedAndKeyedStream;
|
|
@@ -1,31 +1,59 @@
|
|
|
1
|
-
import { map, join, consolidate, filter } from "@tanstack/db-ivm";
|
|
2
|
-
import { UnsupportedJoinTypeError, UnsupportedJoinSourceTypeError, CollectionInputNotFoundError, InvalidJoinConditionSameTableError, InvalidJoinConditionTableMismatchError, InvalidJoinConditionWrongTablesError } from "../../errors.js";
|
|
1
|
+
import { map, tap, join, consolidate, filter } from "@tanstack/db-ivm";
|
|
2
|
+
import { JoinCollectionNotFoundError, UnsupportedJoinTypeError, UnsupportedJoinSourceTypeError, CollectionInputNotFoundError, InvalidJoinConditionSameTableError, InvalidJoinConditionTableMismatchError, InvalidJoinConditionWrongTablesError } from "../../errors.js";
|
|
3
|
+
import { findIndexForField } from "../../utils/index-optimization.js";
|
|
4
|
+
import { ensureIndexForField } from "../../indexes/auto-index.js";
|
|
3
5
|
import { compileExpression } from "./evaluators.js";
|
|
4
|
-
import { compileQuery } from "./index.js";
|
|
5
|
-
function processJoins(pipeline, joinClauses, tables, mainTableAlias, allInputs, cache, queryMapping) {
|
|
6
|
+
import { followRef, compileQuery } from "./index.js";
|
|
7
|
+
function processJoins(pipeline, joinClauses, tables, mainTableId, mainTableAlias, allInputs, cache, queryMapping, collections, callbacks, lazyCollections, optimizableOrderByCollections, rawQuery) {
|
|
6
8
|
let resultPipeline = pipeline;
|
|
7
9
|
for (const joinClause of joinClauses) {
|
|
8
10
|
resultPipeline = processJoin(
|
|
9
11
|
resultPipeline,
|
|
10
12
|
joinClause,
|
|
11
13
|
tables,
|
|
14
|
+
mainTableId,
|
|
12
15
|
mainTableAlias,
|
|
13
16
|
allInputs,
|
|
14
17
|
cache,
|
|
15
|
-
queryMapping
|
|
18
|
+
queryMapping,
|
|
19
|
+
collections,
|
|
20
|
+
callbacks,
|
|
21
|
+
lazyCollections,
|
|
22
|
+
optimizableOrderByCollections,
|
|
23
|
+
rawQuery
|
|
16
24
|
);
|
|
17
25
|
}
|
|
18
26
|
return resultPipeline;
|
|
19
27
|
}
|
|
20
|
-
function processJoin(pipeline, joinClause, tables, mainTableAlias, allInputs, cache, queryMapping) {
|
|
21
|
-
const {
|
|
28
|
+
function processJoin(pipeline, joinClause, tables, mainTableId, mainTableAlias, allInputs, cache, queryMapping, collections, callbacks, lazyCollections, optimizableOrderByCollections, rawQuery) {
|
|
29
|
+
const {
|
|
30
|
+
alias: joinedTableAlias,
|
|
31
|
+
input: joinedInput,
|
|
32
|
+
collectionId: joinedCollectionId
|
|
33
|
+
} = processJoinSource(
|
|
22
34
|
joinClause.from,
|
|
23
35
|
allInputs,
|
|
36
|
+
collections,
|
|
37
|
+
callbacks,
|
|
38
|
+
lazyCollections,
|
|
39
|
+
optimizableOrderByCollections,
|
|
24
40
|
cache,
|
|
25
41
|
queryMapping
|
|
26
42
|
);
|
|
27
43
|
tables[joinedTableAlias] = joinedInput;
|
|
28
|
-
const
|
|
44
|
+
const mainCollection = collections[mainTableId];
|
|
45
|
+
const joinedCollection = collections[joinedCollectionId];
|
|
46
|
+
if (!mainCollection) {
|
|
47
|
+
throw new JoinCollectionNotFoundError(mainTableId);
|
|
48
|
+
}
|
|
49
|
+
if (!joinedCollection) {
|
|
50
|
+
throw new JoinCollectionNotFoundError(joinedCollectionId);
|
|
51
|
+
}
|
|
52
|
+
const { activeCollection, lazyCollection } = getActiveAndLazyCollections(
|
|
53
|
+
joinClause.type,
|
|
54
|
+
mainCollection,
|
|
55
|
+
joinedCollection
|
|
56
|
+
);
|
|
29
57
|
const { mainExpr, joinedExpr } = analyzeJoinExpressions(
|
|
30
58
|
joinClause.left,
|
|
31
59
|
joinClause.right,
|
|
@@ -34,24 +62,72 @@ function processJoin(pipeline, joinClause, tables, mainTableAlias, allInputs, ca
|
|
|
34
62
|
);
|
|
35
63
|
const compiledMainExpr = compileExpression(mainExpr);
|
|
36
64
|
const compiledJoinedExpr = compileExpression(joinedExpr);
|
|
37
|
-
|
|
65
|
+
let mainPipeline = pipeline.pipe(
|
|
38
66
|
map(([currentKey, namespacedRow]) => {
|
|
39
67
|
const mainKey = compiledMainExpr(namespacedRow);
|
|
40
68
|
return [mainKey, [currentKey, namespacedRow]];
|
|
41
69
|
})
|
|
42
70
|
);
|
|
43
|
-
|
|
71
|
+
let joinedPipeline = joinedInput.pipe(
|
|
44
72
|
map(([currentKey, row]) => {
|
|
45
73
|
const namespacedRow = { [joinedTableAlias]: row };
|
|
46
74
|
const joinedKey = compiledJoinedExpr(namespacedRow);
|
|
47
75
|
return [joinedKey, [currentKey, namespacedRow]];
|
|
48
76
|
})
|
|
49
77
|
);
|
|
50
|
-
if (![`inner`, `left`, `right`, `full`].includes(
|
|
78
|
+
if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {
|
|
51
79
|
throw new UnsupportedJoinTypeError(joinClause.type);
|
|
52
80
|
}
|
|
81
|
+
if (activeCollection) {
|
|
82
|
+
lazyCollections.add(lazyCollection.id);
|
|
83
|
+
const activePipeline = activeCollection === `main` ? mainPipeline : joinedPipeline;
|
|
84
|
+
let index;
|
|
85
|
+
const lazyCollectionJoinExpr = activeCollection === `main` ? joinedExpr : mainExpr;
|
|
86
|
+
const activeColl = activeCollection === `main` ? collections[mainTableId] : lazyCollection;
|
|
87
|
+
const followRefResult = followRef(
|
|
88
|
+
rawQuery,
|
|
89
|
+
lazyCollectionJoinExpr,
|
|
90
|
+
activeColl
|
|
91
|
+
);
|
|
92
|
+
const followRefCollection = followRefResult.collection;
|
|
93
|
+
const fieldName = followRefResult.path[0];
|
|
94
|
+
if (fieldName) {
|
|
95
|
+
ensureIndexForField(fieldName, followRefResult.path, followRefCollection);
|
|
96
|
+
}
|
|
97
|
+
let deoptimized = false;
|
|
98
|
+
const activePipelineWithLoading = activePipeline.pipe(
|
|
99
|
+
tap(([joinKey, _]) => {
|
|
100
|
+
if (deoptimized) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
index ?? (index = findIndexForField(
|
|
104
|
+
followRefCollection.indexes,
|
|
105
|
+
followRefResult.path
|
|
106
|
+
));
|
|
107
|
+
const collectionCallbacks = callbacks[lazyCollection.id];
|
|
108
|
+
if (!collectionCallbacks) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Internal error: callbacks for collection are missing in join pipeline. Make sure the live query collection sets them before running the pipeline.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const { loadKeys, loadInitialState } = collectionCallbacks;
|
|
114
|
+
if (index && index.supports(`eq`)) {
|
|
115
|
+
const matchingKeys = index.lookup(`eq`, joinKey);
|
|
116
|
+
loadKeys(matchingKeys);
|
|
117
|
+
} else {
|
|
118
|
+
deoptimized = true;
|
|
119
|
+
loadInitialState();
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
if (activeCollection === `main`) {
|
|
124
|
+
mainPipeline = activePipelineWithLoading;
|
|
125
|
+
} else {
|
|
126
|
+
joinedPipeline = activePipelineWithLoading;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
53
129
|
return mainPipeline.pipe(
|
|
54
|
-
join(joinedPipeline,
|
|
130
|
+
join(joinedPipeline, joinClause.type),
|
|
55
131
|
consolidate(),
|
|
56
132
|
processJoinResults(joinClause.type)
|
|
57
133
|
);
|
|
@@ -99,20 +175,24 @@ function getTableAliasFromExpression(expr) {
|
|
|
99
175
|
return null;
|
|
100
176
|
}
|
|
101
177
|
}
|
|
102
|
-
function processJoinSource(from, allInputs, cache, queryMapping) {
|
|
178
|
+
function processJoinSource(from, allInputs, collections, callbacks, lazyCollections, optimizableOrderByCollections, cache, queryMapping) {
|
|
103
179
|
switch (from.type) {
|
|
104
180
|
case `collectionRef`: {
|
|
105
181
|
const input = allInputs[from.collection.id];
|
|
106
182
|
if (!input) {
|
|
107
183
|
throw new CollectionInputNotFoundError(from.collection.id);
|
|
108
184
|
}
|
|
109
|
-
return { alias: from.alias, input };
|
|
185
|
+
return { alias: from.alias, input, collectionId: from.collection.id };
|
|
110
186
|
}
|
|
111
187
|
case `queryRef`: {
|
|
112
188
|
const originalQuery = queryMapping.get(from.query) || from.query;
|
|
113
189
|
const subQueryResult = compileQuery(
|
|
114
190
|
originalQuery,
|
|
115
191
|
allInputs,
|
|
192
|
+
collections,
|
|
193
|
+
callbacks,
|
|
194
|
+
lazyCollections,
|
|
195
|
+
optimizableOrderByCollections,
|
|
116
196
|
cache,
|
|
117
197
|
queryMapping
|
|
118
198
|
);
|
|
@@ -123,7 +203,11 @@ function processJoinSource(from, allInputs, cache, queryMapping) {
|
|
|
123
203
|
return [key, value];
|
|
124
204
|
})
|
|
125
205
|
);
|
|
126
|
-
return {
|
|
206
|
+
return {
|
|
207
|
+
alias: from.alias,
|
|
208
|
+
input: extractedInput,
|
|
209
|
+
collectionId: subQueryResult.collectionId
|
|
210
|
+
};
|
|
127
211
|
}
|
|
128
212
|
default:
|
|
129
213
|
throw new UnsupportedJoinSourceTypeError(from.type);
|
|
@@ -167,6 +251,21 @@ function processJoinResults(joinType) {
|
|
|
167
251
|
);
|
|
168
252
|
};
|
|
169
253
|
}
|
|
254
|
+
function getActiveAndLazyCollections(joinType, leftCollection, rightCollection) {
|
|
255
|
+
if (leftCollection.id === rightCollection.id) {
|
|
256
|
+
return { activeCollection: void 0, lazyCollection: void 0 };
|
|
257
|
+
}
|
|
258
|
+
switch (joinType) {
|
|
259
|
+
case `left`:
|
|
260
|
+
return { activeCollection: `main`, lazyCollection: rightCollection };
|
|
261
|
+
case `right`:
|
|
262
|
+
return { activeCollection: `joined`, lazyCollection: leftCollection };
|
|
263
|
+
case `inner`:
|
|
264
|
+
return leftCollection.size < rightCollection.size ? { activeCollection: `main`, lazyCollection: rightCollection } : { activeCollection: `joined`, lazyCollection: leftCollection };
|
|
265
|
+
default:
|
|
266
|
+
return { activeCollection: void 0, lazyCollection: void 0 };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
170
269
|
export {
|
|
171
270
|
processJoins
|
|
172
271
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"joins.js","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n} from \"@tanstack/db-ivm\"\nimport {\n CollectionInputNotFoundError,\n InvalidJoinConditionSameTableError,\n InvalidJoinConditionTableMismatchError,\n InvalidJoinConditionWrongTablesError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from \"../../errors.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { compileQuery } from \"./index.js\"\nimport type { IStreamBuilder, JoinType } from \"@tanstack/db-ivm\"\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n QueryRef,\n} from \"../ir.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping } from \"./types.js\"\n\n/**\n * Processes all join clauses in a query\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n tables: Record<string, KeyedStream>,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n tables,\n mainTableAlias,\n allInputs,\n cache,\n queryMapping\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n tables: Record<string, KeyedStream>,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): NamespacedAndKeyedStream {\n // Get the joined table alias and input stream\n const { alias: joinedTableAlias, input: joinedInput } = processJoinSource(\n joinClause.from,\n allInputs,\n cache,\n queryMapping\n )\n\n // Add the joined table to the tables map\n tables[joinedTableAlias] = joinedInput\n\n // Convert join type to D2 join type\n const joinType: JoinType =\n joinClause.type === `cross`\n ? `inner`\n : joinClause.type === `outer`\n ? `full`\n : (joinClause.type as JoinType)\n\n // Analyze which table each expression refers to and swap if necessary\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n mainTableAlias,\n joinedTableAlias\n )\n\n // Pre-compile the join expressions\n const compiledMainExpr = compileExpression(mainExpr)\n const compiledJoinedExpr = compileExpression(joinedExpr)\n\n // Prepare the main pipeline for joining\n const mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the main table expression\n const mainKey = compiledMainExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [mainKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Prepare the joined pipeline\n const joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }\n\n // Extract the join key from the joined table expression\n const joinedKey = compiledJoinedExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [joinedKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinType)) {\n throw new UnsupportedJoinTypeError(joinClause.type)\n }\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinType),\n consolidate(),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which table\n * and returns them in the correct order (main table expression first, joined table expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n mainTableAlias: string,\n joinedTableAlias: string\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n const leftTableAlias = getTableAliasFromExpression(left)\n const rightTableAlias = getTableAliasFromExpression(right)\n\n // If left expression refers to main table and right refers to joined table, keep as is\n if (\n leftTableAlias === mainTableAlias &&\n rightTableAlias === joinedTableAlias\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined table and right refers to main table, swap them\n if (\n leftTableAlias === joinedTableAlias &&\n rightTableAlias === mainTableAlias\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (leftTableAlias === rightTableAlias) {\n throw new InvalidJoinConditionSameTableError(leftTableAlias || `unknown`)\n }\n\n // If one expression doesn't refer to either table, this is an invalid join\n if (!leftTableAlias || !rightTableAlias) {\n throw new InvalidJoinConditionTableMismatchError(\n mainTableAlias,\n joinedTableAlias\n )\n }\n\n // If expressions refer to tables not involved in this join, this is an invalid join\n throw new InvalidJoinConditionWrongTablesError(\n leftTableAlias,\n rightTableAlias,\n mainTableAlias,\n joinedTableAlias\n )\n}\n\n/**\n * Extracts the table alias from a join expression\n */\nfunction getTableAliasFromExpression(expr: BasicExpression): string | null {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the table alias as the first element\n return expr.path[0] || null\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same table\n const tableAliases = new Set<string>()\n for (const arg of expr.args) {\n const alias = getTableAliasFromExpression(arg)\n if (alias) {\n tableAliases.add(alias)\n }\n }\n // If all arguments refer to the same table, return that table alias\n return tableAliases.size === 1 ? Array.from(tableAliases)[0]! : null\n }\n default:\n // Values (type='val') don't reference any table\n return null\n }\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): { alias: string; input: KeyedStream } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new CollectionInputNotFoundError(from.collection.id)\n }\n return { alias: from.alias, input }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryResult = compileQuery(\n originalQuery,\n allInputs,\n cache,\n queryMapping\n )\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return { alias: from.alias, input: extractedInput as KeyedStream }\n }\n default:\n throw new UnsupportedJoinSourceTypeError((from as any).type)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n })\n )\n }\n}\n"],"names":["joinOperator"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"joins.js","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import {\n consolidate,\n filter,\n join as joinOperator,\n map,\n tap,\n} from \"@tanstack/db-ivm\"\nimport {\n CollectionInputNotFoundError,\n InvalidJoinConditionSameTableError,\n InvalidJoinConditionTableMismatchError,\n InvalidJoinConditionWrongTablesError,\n JoinCollectionNotFoundError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from \"../../errors.js\"\nimport { findIndexForField } from \"../../utils/index-optimization.js\"\nimport { ensureIndexForField } from \"../../indexes/auto-index.js\"\nimport { compileExpression } from \"./evaluators.js\"\nimport { compileQuery, followRef } from \"./index.js\"\nimport type { OrderByOptimizationInfo } from \"./order-by.js\"\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n PropRef,\n QueryIR,\n QueryRef,\n} from \"../ir.js\"\nimport type { IStreamBuilder, JoinType } from \"@tanstack/db-ivm\"\nimport type { Collection } from \"../../collection.js\"\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from \"../../types.js\"\nimport type { QueryCache, QueryMapping } from \"./types.js\"\nimport type { BaseIndex } from \"../../indexes/base-index.js\"\n\nexport type LoadKeysFn = (key: Set<string | number>) => void\nexport type LazyCollectionCallbacks = {\n loadKeys: LoadKeysFn\n loadInitialState: () => void\n}\n\n/**\n * Processes all join clauses in a query\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n tables: Record<string, KeyedStream>,\n mainTableId: string,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n rawQuery: QueryIR\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n tables,\n mainTableId,\n mainTableAlias,\n allInputs,\n cache,\n queryMapping,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n rawQuery\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n tables: Record<string, KeyedStream>,\n mainTableId: string,\n mainTableAlias: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n rawQuery: QueryIR\n): NamespacedAndKeyedStream {\n // Get the joined table alias and input stream\n const {\n alias: joinedTableAlias,\n input: joinedInput,\n collectionId: joinedCollectionId,\n } = processJoinSource(\n joinClause.from,\n allInputs,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n\n // Add the joined table to the tables map\n tables[joinedTableAlias] = joinedInput\n\n const mainCollection = collections[mainTableId]\n const joinedCollection = collections[joinedCollectionId]\n\n if (!mainCollection) {\n throw new JoinCollectionNotFoundError(mainTableId)\n }\n\n if (!joinedCollection) {\n throw new JoinCollectionNotFoundError(joinedCollectionId)\n }\n\n const { activeCollection, lazyCollection } = getActiveAndLazyCollections(\n joinClause.type,\n mainCollection,\n joinedCollection\n )\n\n // Analyze which table each expression refers to and swap if necessary\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n mainTableAlias,\n joinedTableAlias\n )\n\n // Pre-compile the join expressions\n const compiledMainExpr = compileExpression(mainExpr)\n const compiledJoinedExpr = compileExpression(joinedExpr)\n\n // Prepare the main pipeline for joining\n let mainPipeline = pipeline.pipe(\n map(([currentKey, namespacedRow]) => {\n // Extract the join key from the main table expression\n const mainKey = compiledMainExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [mainKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Prepare the joined pipeline\n let joinedPipeline = joinedInput.pipe(\n map(([currentKey, row]) => {\n // Wrap the row in a namespaced structure\n const namespacedRow: NamespacedRow = { [joinedTableAlias]: row }\n\n // Extract the join key from the joined table expression\n const joinedKey = compiledJoinedExpr(namespacedRow)\n\n // Return [joinKey, [originalKey, namespacedRow]]\n return [joinedKey, [currentKey, namespacedRow]] as [\n unknown,\n [string, typeof namespacedRow],\n ]\n })\n )\n\n // Apply the join operation\n if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {\n throw new UnsupportedJoinTypeError(joinClause.type)\n }\n\n if (activeCollection) {\n // This join can be optimized by having the active collection\n // dynamically load keys into the lazy collection\n // based on the value of the joinKey and by looking up\n // matching rows in the index of the lazy collection\n\n // Mark the lazy collection as lazy\n // this Set is passed by the liveQueryCollection to the compiler\n // such that the liveQueryCollection can check it after compilation\n // to know which collections are lazy collections\n lazyCollections.add(lazyCollection.id)\n\n const activePipeline =\n activeCollection === `main` ? mainPipeline : joinedPipeline\n\n let index: BaseIndex<string | number> | undefined\n\n const lazyCollectionJoinExpr =\n activeCollection === `main`\n ? (joinedExpr as PropRef)\n : (mainExpr as PropRef)\n\n const activeColl =\n activeCollection === `main` ? collections[mainTableId]! : lazyCollection\n\n const followRefResult = followRef(\n rawQuery,\n lazyCollectionJoinExpr,\n activeColl\n )!\n const followRefCollection = followRefResult.collection\n\n const fieldName = followRefResult.path[0]\n if (fieldName) {\n ensureIndexForField(fieldName, followRefResult.path, followRefCollection)\n }\n\n let deoptimized = false\n\n const activePipelineWithLoading: IStreamBuilder<\n [key: unknown, [originalKey: string, namespacedRow: NamespacedRow]]\n > = activePipeline.pipe(\n tap(([joinKey, _]) => {\n if (deoptimized) {\n return\n }\n\n // Find the index for the path we join on\n // we need to find the index inside the map operator\n // because the indexes are only available after the initial sync\n // so we can't fetch it during compilation\n index ??= findIndexForField(\n followRefCollection.indexes,\n followRefResult.path\n )\n\n // The `callbacks` object is passed by the liveQueryCollection to the compiler.\n // It contains a function to lazy load keys for each lazy collection\n // as well as a function to switch back to a regular collection\n // (useful when there's no index for available for lazily loading the collection)\n const collectionCallbacks = callbacks[lazyCollection.id]\n if (!collectionCallbacks) {\n throw new Error(\n `Internal error: callbacks for collection are missing in join pipeline. Make sure the live query collection sets them before running the pipeline.`\n )\n }\n\n const { loadKeys, loadInitialState } = collectionCallbacks\n\n if (index && index.supports(`eq`)) {\n // Use the index to fetch the PKs of the rows in the lazy collection\n // that match this row from the active collection based on the value of the joinKey\n const matchingKeys = index.lookup(`eq`, joinKey)\n // Inform the lazy collection that those keys need to be loaded\n loadKeys(matchingKeys)\n } else {\n // We can't optimize the join because there is no index for the join key\n // on the lazy collection, so we load the initial state\n deoptimized = true\n loadInitialState()\n }\n })\n )\n\n if (activeCollection === `main`) {\n mainPipeline = activePipelineWithLoading\n } else {\n joinedPipeline = activePipelineWithLoading\n }\n }\n\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinClause.type as JoinType),\n consolidate(),\n processJoinResults(joinClause.type)\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which table\n * and returns them in the correct order (main table expression first, joined table expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n mainTableAlias: string,\n joinedTableAlias: string\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n const leftTableAlias = getTableAliasFromExpression(left)\n const rightTableAlias = getTableAliasFromExpression(right)\n\n // If left expression refers to main table and right refers to joined table, keep as is\n if (\n leftTableAlias === mainTableAlias &&\n rightTableAlias === joinedTableAlias\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined table and right refers to main table, swap them\n if (\n leftTableAlias === joinedTableAlias &&\n rightTableAlias === mainTableAlias\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (leftTableAlias === rightTableAlias) {\n throw new InvalidJoinConditionSameTableError(leftTableAlias || `unknown`)\n }\n\n // If one expression doesn't refer to either table, this is an invalid join\n if (!leftTableAlias || !rightTableAlias) {\n throw new InvalidJoinConditionTableMismatchError(\n mainTableAlias,\n joinedTableAlias\n )\n }\n\n // If expressions refer to tables not involved in this join, this is an invalid join\n throw new InvalidJoinConditionWrongTablesError(\n leftTableAlias,\n rightTableAlias,\n mainTableAlias,\n joinedTableAlias\n )\n}\n\n/**\n * Extracts the table alias from a join expression\n */\nfunction getTableAliasFromExpression(expr: BasicExpression): string | null {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the table alias as the first element\n return expr.path[0] || null\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same table\n const tableAliases = new Set<string>()\n for (const arg of expr.args) {\n const alias = getTableAliasFromExpression(arg)\n if (alias) {\n tableAliases.add(alias)\n }\n }\n // If all arguments refer to the same table, return that table alias\n return tableAliases.size === 1 ? Array.from(tableAliases)[0]! : null\n }\n default:\n // Values (type='val') don't reference any table\n return null\n }\n}\n\n/**\n * Processes the join source (collection or sub-query)\n */\nfunction processJoinSource(\n from: CollectionRef | QueryRef,\n allInputs: Record<string, KeyedStream>,\n collections: Record<string, Collection>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazyCollections: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n cache: QueryCache,\n queryMapping: QueryMapping\n): { alias: string; input: KeyedStream; collectionId: string } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.collection.id]\n if (!input) {\n throw new CollectionInputNotFoundError(from.collection.id)\n }\n return { alias: from.alias, input, collectionId: from.collection.id }\n }\n case `queryRef`: {\n // Find the original query for caching purposes\n const originalQuery = queryMapping.get(from.query) || from.query\n\n // Recursively compile the sub-query with cache\n const subQueryResult = compileQuery(\n originalQuery,\n allInputs,\n collections,\n callbacks,\n lazyCollections,\n optimizableOrderByCollections,\n cache,\n queryMapping\n )\n\n // Extract the pipeline from the compilation result\n const subQueryInput = subQueryResult.pipeline\n\n // Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)\n // We need to extract just the value for use in parent queries\n const extractedInput = subQueryInput.pipe(\n map((data: any) => {\n const [key, [value, _orderByIndex]] = data\n return [key, value] as [unknown, any]\n })\n )\n\n return {\n alias: from.alias,\n input: extractedInput as KeyedStream,\n collectionId: subQueryResult.collectionId,\n }\n }\n default:\n throw new UnsupportedJoinSourceTypeError((from as any).type)\n }\n}\n\n/**\n * Processes the results of a join operation\n */\nfunction processJoinResults(joinType: string) {\n return function (\n pipeline: IStreamBuilder<\n [\n key: string,\n [\n [string, NamespacedRow] | undefined,\n [string, NamespacedRow] | undefined,\n ],\n ]\n >\n ): NamespacedAndKeyedStream {\n return pipeline.pipe(\n // Process the join result and handle nulls\n filter((result) => {\n const [_key, [main, joined]] = result\n const mainNamespacedRow = main?.[1]\n const joinedNamespacedRow = joined?.[1]\n\n // Handle different join types\n if (joinType === `inner`) {\n return !!(mainNamespacedRow && joinedNamespacedRow)\n }\n\n if (joinType === `left`) {\n return !!mainNamespacedRow\n }\n\n if (joinType === `right`) {\n return !!joinedNamespacedRow\n }\n\n // For full joins, always include\n return true\n }),\n map((result) => {\n const [_key, [main, joined]] = result\n const mainKey = main?.[0]\n const mainNamespacedRow = main?.[1]\n const joinedKey = joined?.[0]\n const joinedNamespacedRow = joined?.[1]\n\n // Merge the namespaced rows\n const mergedNamespacedRow: NamespacedRow = {}\n\n // Add main row data if it exists\n if (mainNamespacedRow) {\n Object.assign(mergedNamespacedRow, mainNamespacedRow)\n }\n\n // Add joined row data if it exists\n if (joinedNamespacedRow) {\n Object.assign(mergedNamespacedRow, joinedNamespacedRow)\n }\n\n // We create a composite key that combines the main and joined keys\n const resultKey = `[${mainKey},${joinedKey}]`\n\n return [resultKey, mergedNamespacedRow] as [string, NamespacedRow]\n })\n )\n }\n}\n\n/**\n * Returns the active and lazy collections for a join clause.\n * The active collection is the one that we need to fully iterate over\n * and it can be the main table (i.e. left collection) or the joined table (i.e. right collection).\n * The lazy collection is the one that we should join-in lazily based on matches in the active collection.\n * @param joinClause - The join clause to analyze\n * @param leftCollection - The left collection\n * @param rightCollection - The right collection\n * @returns The active and lazy collections. They are undefined if we need to loop over both collections (i.e. both are active)\n */\nfunction getActiveAndLazyCollections(\n joinType: JoinClause[`type`],\n leftCollection: Collection,\n rightCollection: Collection\n):\n | { activeCollection: `main` | `joined`; lazyCollection: Collection }\n | { activeCollection: undefined; lazyCollection: undefined } {\n if (leftCollection.id === rightCollection.id) {\n // We can't apply this optimization if there's only one collection\n // because `liveQueryCollection` will detect that the collection is lazy\n // and treat it lazily (because the collection is shared)\n // and thus it will not load any keys because both sides of the join\n // will be handled lazily\n return { activeCollection: undefined, lazyCollection: undefined }\n }\n\n switch (joinType) {\n case `left`:\n return { activeCollection: `main`, lazyCollection: rightCollection }\n case `right`:\n return { activeCollection: `joined`, lazyCollection: leftCollection }\n case `inner`:\n // The smallest collection should be the active collection\n // and the biggest collection should be lazy\n return leftCollection.size < rightCollection.size\n ? { activeCollection: `main`, lazyCollection: rightCollection }\n : { activeCollection: `joined`, lazyCollection: leftCollection }\n default:\n return { activeCollection: undefined, lazyCollection: undefined }\n }\n}\n"],"names":["joinOperator"],"mappings":"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"}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import { OrderByClause } from '../ir.js';
|
|
1
|
+
import { OrderByClause, QueryIR } from '../ir.js';
|
|
2
2
|
import { NamespacedAndKeyedStream, NamespacedRow } from '../../types.js';
|
|
3
3
|
import { IStreamBuilder, KeyValue } from '@tanstack/db-ivm';
|
|
4
|
+
import { BaseIndex } from '../../indexes/base-index.js';
|
|
5
|
+
import { Collection } from '../../collection.js';
|
|
6
|
+
export type OrderByOptimizationInfo = {
|
|
7
|
+
offset: number;
|
|
8
|
+
limit: number;
|
|
9
|
+
comparator: (a: Record<string, unknown> | null | undefined, b: Record<string, unknown> | null | undefined) => number;
|
|
10
|
+
valueExtractorForRawRow: (row: Record<string, unknown>) => any;
|
|
11
|
+
index: BaseIndex<string | number>;
|
|
12
|
+
dataNeeded?: () => number;
|
|
13
|
+
};
|
|
4
14
|
/**
|
|
5
15
|
* Processes the ORDER BY clause
|
|
6
16
|
* Works with the new structure that has both namespaced row data and __select_results
|
|
7
17
|
* Always uses fractional indexing and adds the index as __ordering_index to the result
|
|
8
18
|
*/
|
|
9
|
-
export declare function processOrderBy(pipeline: NamespacedAndKeyedStream, orderByClause: Array<OrderByClause>, limit?: number, offset?: number): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>>;
|
|
19
|
+
export declare function processOrderBy(rawQuery: QueryIR, pipeline: NamespacedAndKeyedStream, orderByClause: Array<OrderByClause>, collection: Collection, optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>, limit?: number, offset?: number): IStreamBuilder<KeyValue<unknown, [NamespacedRow, string]>>;
|