77 LimitOffsetRequireOrderByError ,
88 UnsupportedFromTypeError ,
99} from "../../errors.js"
10+ import { PropRef } from "../ir.js"
1011import { compileExpression } from "./evaluators.js"
1112import { processJoins } from "./joins.js"
1213import { processGroupBy } from "./group-by.js"
@@ -18,6 +19,8 @@ import type {
1819 QueryIR ,
1920 QueryRef ,
2021} from "../ir.js"
22+ import type { LazyCollectionCallbacks } from "./joins.js"
23+ import type { Collection } from "../../collection.js"
2124import type {
2225 KeyedStream ,
2326 NamespacedAndKeyedStream ,
@@ -29,6 +32,8 @@ import type { QueryCache, QueryMapping } from "./types.js"
2932 * Result of query compilation including both the pipeline and collection-specific WHERE clauses
3033 */
3134export interface CompilationResult {
35+ /** The ID of the main collection */
36+ collectionId : string
3237 /** The compiled query pipeline */
3338 pipeline : ResultStream
3439 /** Map of collection aliases to their WHERE clauses for index optimization */
@@ -46,6 +51,9 @@ export interface CompilationResult {
4651export function compileQuery (
4752 rawQuery : QueryIR ,
4853 inputs : Record < string , KeyedStream > ,
54+ collections : Record < string , Collection < any , any , any , any , any > > ,
55+ callbacks : Record < string , LazyCollectionCallbacks > ,
56+ lazyCollections : Set < string > ,
4957 cache : QueryCache = new WeakMap ( ) ,
5058 queryMapping : QueryMapping = new WeakMap ( )
5159) : CompilationResult {
@@ -70,9 +78,16 @@ export function compileQuery(
7078 const tables : Record < string , KeyedStream > = { }
7179
7280 // Process the FROM clause to get the main table
73- const { alias : mainTableAlias , input : mainInput } = processFrom (
81+ const {
82+ alias : mainTableAlias ,
83+ input : mainInput ,
84+ collectionId : mainCollectionId ,
85+ } = processFrom (
7486 query . from ,
7587 allInputs ,
88+ collections ,
89+ callbacks ,
90+ lazyCollections ,
7691 cache ,
7792 queryMapping
7893 )
@@ -96,10 +111,15 @@ export function compileQuery(
96111 pipeline ,
97112 query . join ,
98113 tables ,
114+ mainCollectionId ,
99115 mainTableAlias ,
100116 allInputs ,
101117 cache ,
102- queryMapping
118+ queryMapping ,
119+ collections ,
120+ callbacks ,
121+ lazyCollections ,
122+ rawQuery
103123 )
104124 }
105125
@@ -249,6 +269,7 @@ export function compileQuery(
249269 const result = resultPipeline
250270 // Cache the result before returning (use original query as key)
251271 const compilationResult = {
272+ collectionId : mainCollectionId ,
252273 pipeline : result ,
253274 collectionWhereClauses,
254275 }
@@ -275,6 +296,7 @@ export function compileQuery(
275296 const result = resultPipeline
276297 // Cache the result before returning (use original query as key)
277298 const compilationResult = {
299+ collectionId : mainCollectionId ,
278300 pipeline : result ,
279301 collectionWhereClauses,
280302 }
@@ -289,16 +311,19 @@ export function compileQuery(
289311function processFrom (
290312 from : CollectionRef | QueryRef ,
291313 allInputs : Record < string , KeyedStream > ,
314+ collections : Record < string , Collection > ,
315+ callbacks : Record < string , LazyCollectionCallbacks > ,
316+ lazyCollections : Set < string > ,
292317 cache : QueryCache ,
293318 queryMapping : QueryMapping
294- ) : { alias : string ; input : KeyedStream } {
319+ ) : { alias : string ; input : KeyedStream ; collectionId : string } {
295320 switch ( from . type ) {
296321 case `collectionRef` : {
297322 const input = allInputs [ from . collection . id ]
298323 if ( ! input ) {
299324 throw new CollectionInputNotFoundError ( from . collection . id )
300325 }
301- return { alias : from . alias , input }
326+ return { alias : from . alias , input, collectionId : from . collection . id }
302327 }
303328 case `queryRef` : {
304329 // Find the original query for caching purposes
@@ -308,6 +333,9 @@ function processFrom(
308333 const subQueryResult = compileQuery (
309334 originalQuery ,
310335 allInputs ,
336+ collections ,
337+ callbacks ,
338+ lazyCollections ,
311339 cache ,
312340 queryMapping
313341 )
@@ -324,7 +352,11 @@ function processFrom(
324352 } )
325353 )
326354
327- return { alias : from . alias , input : extractedInput }
355+ return {
356+ alias : from . alias ,
357+ input : extractedInput ,
358+ collectionId : subQueryResult . collectionId ,
359+ }
328360 }
329361 default :
330362 throw new UnsupportedFromTypeError ( ( from as any ) . type )
@@ -380,3 +412,69 @@ function mapNestedQueries(
380412 }
381413 }
382414}
415+
416+ function getRefFromAlias (
417+ query : QueryIR ,
418+ alias : string
419+ ) : CollectionRef | QueryRef | void {
420+ if ( query . from . alias === alias ) {
421+ return query . from
422+ }
423+
424+ for ( const join of query . join || [ ] ) {
425+ if ( join . from . alias === alias ) {
426+ return join . from
427+ }
428+ }
429+ }
430+
431+ /**
432+ * Follows the given reference in a query
433+ * until its finds the root field the reference points to.
434+ * @returns The collection, its alias, and the path to the root field in this collection
435+ */
436+ export function followRef (
437+ query : QueryIR ,
438+ ref : PropRef < any > ,
439+ collection : Collection
440+ ) : { collection : Collection ; path : Array < string > } | void {
441+ if ( ref . path . length === 0 ) {
442+ return
443+ }
444+
445+ if ( ref . path . length === 1 ) {
446+ // This field should be part of this collection
447+ const field = ref . path [ 0 ] !
448+ // is it part of the select clause?
449+ if ( query . select ) {
450+ const selectedField = query . select [ field ]
451+ if ( selectedField && selectedField . type === `ref` ) {
452+ return followRef ( query , selectedField , collection )
453+ }
454+ }
455+
456+ // Either this field is not part of the select clause
457+ // and thus it must be part of the collection itself
458+ // or it is part of the select but is not a reference
459+ // so we can stop here and don't have to follow it
460+ return { collection, path : [ field ] }
461+ }
462+
463+ if ( ref . path . length > 1 ) {
464+ // This is a nested field
465+ const [ alias , ...rest ] = ref . path
466+ const aliasRef = getRefFromAlias ( query , alias ! )
467+ if ( ! aliasRef ) {
468+ return
469+ }
470+
471+ if ( aliasRef . type === `queryRef` ) {
472+ return followRef ( aliasRef . query , new PropRef ( rest ) , collection )
473+ } else {
474+ // This is a reference to a collection
475+ // we can't follow it further
476+ // so the field must be on the collection itself
477+ return { collection : aliasRef . collection , path : rest }
478+ }
479+ }
480+ }
0 commit comments