1
1
import { distinct , filter , map } from "@electric-sql/d2mini"
2
+ import { optimizeQuery } from "../optimizer.js"
2
3
import { compileExpression } from "./evaluators.js"
3
4
import { processJoins } from "./joins.js"
4
5
import { processGroupBy } from "./group-by.js"
@@ -10,30 +11,35 @@ import type {
10
11
NamespacedAndKeyedStream ,
11
12
ResultStream ,
12
13
} from "../../types.js"
13
-
14
- /**
15
- * Cache for compiled subqueries to avoid duplicate compilation
16
- */
17
- type QueryCache = WeakMap < QueryIR , ResultStream >
14
+ import type { QueryCache , QueryMapping } from "./types.js"
18
15
19
16
/**
20
17
* Compiles a query2 IR into a D2 pipeline
21
- * @param query The query IR to compile
18
+ * @param rawQuery The query IR to compile
22
19
* @param inputs Mapping of collection names to input streams
23
20
* @param cache Optional cache for compiled subqueries (used internally for recursion)
21
+ * @param queryMapping Optional mapping from optimized queries to original queries
24
22
* @returns A stream builder representing the compiled query
25
23
*/
26
24
export function compileQuery (
27
- query : QueryIR ,
25
+ rawQuery : QueryIR ,
28
26
inputs : Record < string , KeyedStream > ,
29
- cache : QueryCache = new WeakMap ( )
27
+ cache : QueryCache = new WeakMap ( ) ,
28
+ queryMapping : QueryMapping = new WeakMap ( )
30
29
) : ResultStream {
31
- // Check if this query has already been compiled
32
- const cachedResult = cache . get ( query )
30
+ // Check if the original raw query has already been compiled
31
+ const cachedResult = cache . get ( rawQuery )
33
32
if ( cachedResult ) {
34
33
return cachedResult
35
34
}
36
35
36
+ // Optimize the query before compilation
37
+ const query = optimizeQuery ( rawQuery )
38
+
39
+ // Create mapping from optimized query to original for caching
40
+ queryMapping . set ( query , rawQuery )
41
+ mapNestedQueries ( query , rawQuery , queryMapping )
42
+
37
43
// Create a copy of the inputs map to avoid modifying the original
38
44
const allInputs = { ...inputs }
39
45
@@ -44,7 +50,8 @@ export function compileQuery(
44
50
const { alias : mainTableAlias , input : mainInput } = processFrom (
45
51
query . from ,
46
52
allInputs ,
47
- cache
53
+ cache ,
54
+ queryMapping
48
55
)
49
56
tables [ mainTableAlias ] = mainInput
50
57
@@ -68,7 +75,8 @@ export function compileQuery(
68
75
tables ,
69
76
mainTableAlias ,
70
77
allInputs ,
71
- cache
78
+ cache ,
79
+ queryMapping
72
80
)
73
81
}
74
82
@@ -218,8 +226,8 @@ export function compileQuery(
218
226
)
219
227
220
228
const result = resultPipeline
221
- // Cache the result before returning
222
- cache . set ( query , result )
229
+ // Cache the result before returning (use original query as key)
230
+ cache . set ( rawQuery , result )
223
231
return result
224
232
} else if ( query . limit !== undefined || query . offset !== undefined ) {
225
233
// If there's a limit or offset without orderBy, throw an error
@@ -241,8 +249,8 @@ export function compileQuery(
241
249
)
242
250
243
251
const result = resultPipeline
244
- // Cache the result before returning
245
- cache . set ( query , result )
252
+ // Cache the result before returning (use original query as key)
253
+ cache . set ( rawQuery , result )
246
254
return result
247
255
}
248
256
@@ -252,7 +260,8 @@ export function compileQuery(
252
260
function processFrom (
253
261
from : CollectionRef | QueryRef ,
254
262
allInputs : Record < string , KeyedStream > ,
255
- cache : QueryCache
263
+ cache : QueryCache ,
264
+ queryMapping : QueryMapping
256
265
) : { alias : string ; input : KeyedStream } {
257
266
switch ( from . type ) {
258
267
case `collectionRef` : {
@@ -265,8 +274,16 @@ function processFrom(
265
274
return { alias : from . alias , input }
266
275
}
267
276
case `queryRef` : {
277
+ // Find the original query for caching purposes
278
+ const originalQuery = queryMapping . get ( from . query ) || from . query
279
+
268
280
// Recursively compile the sub-query with cache
269
- const subQueryInput = compileQuery ( from . query , allInputs , cache )
281
+ const subQueryInput = compileQuery (
282
+ originalQuery ,
283
+ allInputs ,
284
+ cache ,
285
+ queryMapping
286
+ )
270
287
271
288
// Subqueries may return [key, [value, orderByIndex]] (with ORDER BY) or [key, value] (without ORDER BY)
272
289
// We need to extract just the value for use in parent queries
@@ -283,3 +300,53 @@ function processFrom(
283
300
throw new Error ( `Unsupported FROM type: ${ ( from as any ) . type } ` )
284
301
}
285
302
}
303
+
304
+ /**
305
+ * Recursively maps optimized subqueries to their original queries for proper caching.
306
+ * This ensures that when we encounter the same QueryRef object in different contexts,
307
+ * we can find the original query to check the cache.
308
+ */
309
+ function mapNestedQueries (
310
+ optimizedQuery : QueryIR ,
311
+ originalQuery : QueryIR ,
312
+ queryMapping : QueryMapping
313
+ ) : void {
314
+ // Map the FROM clause if it's a QueryRef
315
+ if (
316
+ optimizedQuery . from . type === `queryRef` &&
317
+ originalQuery . from . type === `queryRef`
318
+ ) {
319
+ queryMapping . set ( optimizedQuery . from . query , originalQuery . from . query )
320
+ // Recursively map nested queries
321
+ mapNestedQueries (
322
+ optimizedQuery . from . query ,
323
+ originalQuery . from . query ,
324
+ queryMapping
325
+ )
326
+ }
327
+
328
+ // Map JOIN clauses if they exist
329
+ if ( optimizedQuery . join && originalQuery . join ) {
330
+ for (
331
+ let i = 0 ;
332
+ i < optimizedQuery . join . length && i < originalQuery . join . length ;
333
+ i ++
334
+ ) {
335
+ const optimizedJoin = optimizedQuery . join [ i ] !
336
+ const originalJoin = originalQuery . join [ i ] !
337
+
338
+ if (
339
+ optimizedJoin . from . type === `queryRef` &&
340
+ originalJoin . from . type === `queryRef`
341
+ ) {
342
+ queryMapping . set ( optimizedJoin . from . query , originalJoin . from . query )
343
+ // Recursively map nested queries in joins
344
+ mapNestedQueries (
345
+ optimizedJoin . from . query ,
346
+ originalJoin . from . query ,
347
+ queryMapping
348
+ )
349
+ }
350
+ }
351
+ }
352
+ }
0 commit comments