Skip to content

Commit 1249a08

Browse files
committed
wip
1 parent 05776f5 commit 1249a08

File tree

3 files changed

+259
-62
lines changed

3 files changed

+259
-62
lines changed

packages/db/src/query/live/collection-config-builder.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export class CollectionConfigBuilder<
3232
private readonly id: string
3333
readonly query: QueryIR
3434
private readonly collections: Record<string, Collection<any, any, any>>
35+
private readonly collectionAliasesById: Map<string, Set<string>>
3536

3637
// WeakMap to store the keys of the results
3738
// so that we can retrieve them in the getKey function
@@ -68,6 +69,7 @@ export class CollectionConfigBuilder<
6869

6970
this.query = buildQueryFromConfig(config)
7071
this.collections = extractCollectionsFromQuery(this.query)
72+
this.collectionAliasesById = extractCollectionAliases(this.query)
7173

7274
// Create compare function for ordering if the query has orderBy
7375
if (this.query.orderBy && this.query.orderBy.length > 0) {
@@ -96,6 +98,11 @@ export class CollectionConfigBuilder<
9698
}
9799
}
98100

101+
getCollectionAliases(collectionId: string): Array<string> {
102+
const aliases = this.collectionAliasesById.get(collectionId)
103+
return aliases ? Array.from(aliases) : []
104+
}
105+
99106
// The callback function is called after the graph has run.
100107
// This gives the callback a chance to load more data if needed,
101108
// that's used to optimize orderBy operators that set a limit,
@@ -444,6 +451,42 @@ function extractCollectionsFromQuery(
444451
return collections
445452
}
446453

454+
function extractCollectionAliases(query: QueryIR): Map<string, Set<string>> {
455+
const aliasesById = new Map<string, Set<string>>()
456+
457+
function recordAlias(source: any) {
458+
if (!source) return
459+
460+
if (source.type === `collectionRef`) {
461+
const { id } = source.collection
462+
const existing = aliasesById.get(id)
463+
if (existing) {
464+
existing.add(source.alias)
465+
} else {
466+
aliasesById.set(id, new Set([source.alias]))
467+
}
468+
} else if (source.type === `queryRef`) {
469+
traverse(source.query)
470+
}
471+
}
472+
473+
function traverse(q: QueryIR) {
474+
if (!q) return
475+
476+
recordAlias(q.from)
477+
478+
if (q.join) {
479+
for (const joinClause of q.join) {
480+
recordAlias(joinClause.from)
481+
}
482+
}
483+
}
484+
485+
traverse(query)
486+
487+
return aliasesById
488+
}
489+
447490
function accumulateChanges<T>(
448491
acc: Map<unknown, Changes<T>>,
449492
[[key, tupleData], multiplicity]: [

packages/db/src/query/live/collection-subscriber.ts

Lines changed: 50 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { MultiSet } from "@tanstack/db-ivm"
22
import { convertToBasicExpression } from "../compiler/expressions.js"
3+
import { Func } from "../ir.js"
34
import type { FullSyncState } from "./types.js"
45
import type { MultiSetArray, RootStreamBuilder } from "@tanstack/db-ivm"
56
import type { Collection } from "../../collection/index.js"
@@ -25,34 +26,14 @@ export class CollectionSubscriber<
2526
) {}
2627

2728
subscribe(): CollectionSubscription {
28-
const collectionAlias = findCollectionAlias(
29-
this.collectionId,
30-
this.collectionConfigBuilder.query
31-
)
32-
const whereClause = this.getWhereClauseFromAlias(collectionAlias)
33-
34-
if (whereClause) {
35-
// Convert WHERE clause to BasicExpression format for collection subscription
36-
const whereExpression = convertToBasicExpression(
37-
whereClause,
38-
collectionAlias!
39-
)
29+
const whereExpression = this.buildCollectionFilter()
4030

41-
if (whereExpression) {
42-
// Use index optimization for this collection
43-
return this.subscribeToChanges(whereExpression)
44-
} else {
45-
// This should not happen - if we have a whereClause but can't create whereExpression,
46-
// it indicates a bug in our optimization logic
47-
throw new Error(
48-
`Failed to convert WHERE clause to collection filter for collection '${this.collectionId}'. ` +
49-
`This indicates a bug in the query optimization logic.`
50-
)
51-
}
52-
} else {
53-
// No WHERE clause for this collection, use regular subscription
54-
return this.subscribeToChanges()
31+
if (whereExpression) {
32+
return this.subscribeToChanges(whereExpression)
5533
}
34+
35+
// No applicable filter for this collection, use regular subscription
36+
return this.subscribeToChanges()
5637
}
5738

5839
private subscribeToChanges(whereExpression?: BasicExpression<boolean>) {
@@ -240,15 +221,38 @@ export class CollectionSubscriber<
240221
})
241222
}
242223

243-
private getWhereClauseFromAlias(
244-
collectionAlias: string | undefined
245-
): BasicExpression<boolean> | undefined {
224+
private buildCollectionFilter(): BasicExpression<boolean> | undefined {
246225
const collectionWhereClausesCache =
247226
this.collectionConfigBuilder.collectionWhereClausesCache
248-
if (collectionAlias && collectionWhereClausesCache) {
249-
return collectionWhereClausesCache.get(collectionAlias)
227+
if (!collectionWhereClausesCache) {
228+
return undefined
250229
}
251-
return undefined
230+
231+
const aliases = this.collectionConfigBuilder.getCollectionAliases(
232+
this.collectionId
233+
)
234+
if (aliases.length === 0) {
235+
return undefined
236+
}
237+
238+
const convertedClauses: Array<BasicExpression<boolean>> = []
239+
for (const alias of aliases) {
240+
const clause = collectionWhereClausesCache.get(alias)
241+
if (!clause) {
242+
// At least one alias requires the full collection, so we cannot safely filter
243+
return undefined
244+
}
245+
246+
const converted = convertToBasicExpression(clause, alias)
247+
if (!converted) {
248+
// Conversion failed which means we cannot use this filter at the subscription level
249+
return undefined
250+
}
251+
252+
convertedClauses.push(converted)
253+
}
254+
255+
return combineWithOr(convertedClauses)
252256
}
253257

254258
private *trackSentValues(
@@ -267,36 +271,6 @@ export class CollectionSubscriber<
267271
}
268272
}
269273

270-
/**
271-
* Finds the alias for a collection ID in the query
272-
*/
273-
function findCollectionAlias(
274-
collectionId: string,
275-
query: any
276-
): string | undefined {
277-
// Check FROM clause
278-
if (
279-
query.from?.type === `collectionRef` &&
280-
query.from.collection?.id === collectionId
281-
) {
282-
return query.from.alias
283-
}
284-
285-
// Check JOIN clauses
286-
if (query.join) {
287-
for (const joinClause of query.join) {
288-
if (
289-
joinClause.from?.type === `collectionRef` &&
290-
joinClause.from.collection?.id === collectionId
291-
) {
292-
return joinClause.from.alias
293-
}
294-
}
295-
}
296-
297-
return undefined
298-
}
299-
300274
/**
301275
* Helper function to send changes to a D2 input stream
302276
*/
@@ -326,6 +300,20 @@ function sendChangesToInput(
326300
return multiSetArray.length
327301
}
328302

303+
function combineWithOr(
304+
expressions: Array<BasicExpression<boolean>>
305+
): BasicExpression<boolean> | undefined {
306+
if (expressions.length === 0) {
307+
return undefined
308+
}
309+
310+
if (expressions.length === 1) {
311+
return expressions[0]!
312+
}
313+
314+
return new Func(`or`, expressions)
315+
}
316+
329317
/** Splits updates into a delete of the old value and an insert of the new value */
330318
function* splitUpdates<
331319
T extends object = Record<string, unknown>,

0 commit comments

Comments
 (0)