UNPKG

@tanstack/db

Version:

A reactive client store for building super fast apps on sync

1 lines 33.5 kB
{"version":3,"file":"joins.cjs","sources":["../../../../src/query/compiler/joins.ts"],"sourcesContent":["import {\n filter,\n join as joinOperator,\n map,\n serializeValue,\n tap,\n} from '@tanstack/db-ivm'\nimport {\n CollectionInputNotFoundError,\n InvalidJoinCondition,\n InvalidJoinConditionLeftSourceError,\n InvalidJoinConditionRightSourceError,\n InvalidJoinConditionSameSourceError,\n InvalidJoinConditionSourceMismatchError,\n JoinCollectionNotFoundError,\n UnsupportedJoinSourceTypeError,\n UnsupportedJoinTypeError,\n} from '../../errors.js'\nimport { normalizeValue } from '../../utils/comparison.js'\nimport { ensureIndexForField } from '../../indexes/auto-index.js'\nimport { compileExpression } from './evaluators.js'\nimport { getLazyLoadTargets } from './lazy-targets.js'\nimport type { CompileQueryFn } from './index.js'\nimport type { OrderByOptimizationInfo } from './order-by.js'\nimport type {\n BasicExpression,\n CollectionRef,\n JoinClause,\n QueryIR,\n QueryRef,\n} from '../ir.js'\nimport type { IStreamBuilder, JoinType } from '@tanstack/db-ivm'\nimport type { Collection } from '../../collection/index.js'\nimport type {\n KeyedStream,\n NamespacedAndKeyedStream,\n NamespacedRow,\n} from '../../types.js'\nimport type { QueryCache, QueryMapping, WindowOptions } from './types.js'\nimport type { CollectionSubscription } from '../../collection/subscription.js'\n\nexport type LazyDemandPlan = {\n id: string\n path: Array<string>\n collectionId: string\n initialKeys: Set<unknown>\n}\n\n/** Callbacks for managing lazy-loaded collections in optimized joins */\nexport type LazyCollectionCallbacks = {\n plans?: Array<LazyDemandPlan>\n setDemand?: (plan: LazyDemandPlan, keys: Set<unknown>) => void\n}\n\nlet nextLazyDemandPlanId = 0\n\nexport function registerLazyDemandPlan(\n callbacks: Record<string, LazyCollectionCallbacks>,\n target: { sourceId: string; path: Array<string>; collection: Collection },\n initialKeys: Set<unknown> = new Set(),\n): LazyDemandPlan {\n const plan: LazyDemandPlan = {\n id: `lazy-demand-${++nextLazyDemandPlanId}`,\n path: target.path,\n collectionId: target.collection.id,\n initialKeys: new Set(initialKeys),\n }\n const state = (callbacks[target.sourceId] ??= {})\n ;(state.plans ??= []).push(plan)\n return plan\n}\n\n/**\n * Processes all join clauses, applying lazy loading optimizations and maintaining\n * alias tracking for per-alias subscriptions (enables self-joins).\n */\nexport function processJoins(\n pipeline: NamespacedAndKeyedStream,\n joinClauses: Array<JoinClause>,\n sources: Record<string, KeyedStream>,\n mainCollectionId: string,\n mainSource: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n rawQuery: QueryIR,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n mainSourceIsParentFiltered: boolean,\n): NamespacedAndKeyedStream {\n let resultPipeline = pipeline\n\n for (const joinClause of joinClauses) {\n resultPipeline = processJoin(\n resultPipeline,\n joinClause,\n sources,\n mainCollectionId,\n mainSource,\n allInputs,\n cache,\n queryMapping,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n rawQuery,\n onCompileSubquery,\n aliasToCollectionId,\n aliasRemapping,\n sourceWhereClauses,\n mainSourceIsParentFiltered,\n )\n }\n\n return resultPipeline\n}\n\n/**\n * Processes a single join clause with lazy loading optimization.\n * For LEFT/RIGHT/INNER joins, marks one side as \"lazy\" (loads on-demand based on join keys).\n */\nfunction processJoin(\n pipeline: NamespacedAndKeyedStream,\n joinClause: JoinClause,\n sources: Record<string, KeyedStream>,\n mainCollectionId: string,\n mainSource: string,\n allInputs: Record<string, KeyedStream>,\n cache: QueryCache,\n queryMapping: QueryMapping,\n collections: Record<string, Collection>,\n subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n rawQuery: QueryIR,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n mainSourceIsParentFiltered: boolean,\n): NamespacedAndKeyedStream {\n const isCollectionRef = joinClause.from.type === `collectionRef`\n\n // Get the joined source alias and input stream\n const {\n alias: joinedSource,\n input: joinedInput,\n collectionId: joinedCollectionId,\n } = processJoinSource(\n joinClause.from,\n allInputs,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n cache,\n queryMapping,\n onCompileSubquery,\n aliasToCollectionId,\n aliasRemapping,\n sourceWhereClauses,\n )\n\n // Add the joined source to the sources map\n sources[joinedSource] = joinedInput\n if (isCollectionRef) {\n // Only direct collection references form new alias bindings. Subquery\n // aliases reuse the mapping returned from the recursive compilation above.\n aliasToCollectionId[joinedSource] = joinedCollectionId\n }\n\n const mainCollection = collections[mainCollectionId]\n const joinedCollection = collections[joinedCollectionId]\n\n if (!mainCollection) {\n throw new JoinCollectionNotFoundError(mainCollectionId)\n }\n\n if (!joinedCollection) {\n throw new JoinCollectionNotFoundError(joinedCollectionId)\n }\n\n const { activeSource, lazySource } = getActiveAndLazySources(\n joinClause.type,\n mainCollection,\n joinedCollection,\n mainSourceIsParentFiltered,\n )\n\n // Analyze which source each expression refers to and swap if necessary\n const availableSources = Object.keys(sources)\n const { mainExpr, joinedExpr } = analyzeJoinExpressions(\n joinClause.left,\n joinClause.right,\n availableSources,\n joinedSource,\n rawQuery.from.type === `unionAll`,\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 source expression\n const mainKey = normalizeValue(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 = { [joinedSource]: row }\n\n // Extract the join key from the joined source expression\n const joinedKey = normalizeValue(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 (activeSource) {\n // If the lazy collection comes from a subquery that has a limit and/or an offset clause\n // then we need to deoptimize the join because we don't know which rows are in the result set\n // since we simply lookup matching keys in the index but the index contains all rows\n // (not just the ones that pass the limit and offset clauses)\n const lazyFrom = activeSource === `main` ? joinClause.from : rawQuery.from\n const limitedSubquery =\n lazyFrom.type === `queryRef` &&\n (lazyFrom.query.limit || lazyFrom.query.offset)\n const resultUnionLazySide = lazyFrom.type === `unionAll`\n\n const lazySourceJoinExpr = activeSource === `main` ? joinedExpr : mainExpr\n const lazyAlias = activeSource === `main` ? joinedSource : mainSource\n const lazyTargets = resultUnionLazySide\n ? []\n : getLazyLoadTargets(\n rawQuery,\n lazyFrom,\n lazyAlias,\n lazySourceJoinExpr,\n lazySource,\n aliasRemapping,\n )\n\n if (!limitedSubquery && lazyTargets.length > 0) {\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 source aliases 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 source aliases should load data lazily (not initially)\n for (const target of lazyTargets) {\n lazySources.add(target.sourceId)\n }\n\n const demandPlans = lazyTargets.map((target) =>\n registerLazyDemandPlan(callbacks, target),\n )\n const demandWeights = new Map<string, { key: unknown; weight: number }>()\n\n const activePipeline =\n activeSource === `main` ? mainPipeline : joinedPipeline\n\n for (const target of lazyTargets) {\n const fieldName = target.path[0]\n if (fieldName) {\n ensureIndexForField(fieldName, target.path, target.collection)\n }\n }\n\n // Set up lazy loading: intercept active side's stream and dynamically load\n // matching rows from lazy side based on join keys.\n const activePipelineWithLoading: IStreamBuilder<\n [key: unknown, [originalKey: string, namespacedRow: NamespacedRow]]\n > = activePipeline.pipe(\n tap((data) => {\n for (const [[joinKey], weight] of data.getInner()) {\n if (joinKey == null) continue\n const encoded = serializeValue(joinKey)\n const previous = demandWeights.get(encoded)\n const nextWeight = (previous?.weight ?? 0) + weight\n if (nextWeight === 0) {\n demandWeights.delete(encoded)\n } else {\n demandWeights.set(encoded, { key: joinKey, weight: nextWeight })\n }\n }\n\n const keys = new Set(\n [...demandWeights.values()]\n .filter(({ weight }) => weight > 0)\n .map(({ key }) => key),\n )\n for (let index = 0; index < lazyTargets.length; index++) {\n const target = lazyTargets[index]!\n callbacks[target.sourceId]?.setDemand?.(demandPlans[index]!, keys)\n }\n }),\n )\n\n if (activeSource === `main`) {\n mainPipeline = activePipelineWithLoading\n } else {\n joinedPipeline = activePipelineWithLoading\n }\n }\n }\n\n return mainPipeline.pipe(\n joinOperator(joinedPipeline, joinClause.type as JoinType),\n processJoinResults(joinClause.type),\n )\n}\n\n/**\n * Analyzes join expressions to determine which refers to which source\n * and returns them in the correct order (available source expression first, joined source expression second)\n */\nfunction analyzeJoinExpressions(\n left: BasicExpression,\n right: BasicExpression,\n allAvailableSourceAliases: Array<string>,\n joinedSource: string,\n allowResultFields: boolean = false,\n): { mainExpr: BasicExpression; joinedExpr: BasicExpression } {\n // Filter out the joined source alias from the available source aliases\n const availableSources = allAvailableSourceAliases.filter(\n (alias) => alias !== joinedSource,\n )\n\n const leftSourceAliases = getSourceAliasesFromExpression(left)\n const rightSourceAliases = getSourceAliasesFromExpression(right)\n const leftReferencesJoined = leftSourceAliases.has(joinedSource)\n const rightReferencesJoined = rightSourceAliases.has(joinedSource)\n const leftAvailableAliases = [...leftSourceAliases].filter(\n (alias) =>\n availableSources.includes(alias) ||\n (allowResultFields && alias !== joinedSource),\n )\n const rightAvailableAliases = [...rightSourceAliases].filter(\n (alias) =>\n availableSources.includes(alias) ||\n (allowResultFields && alias !== joinedSource),\n )\n\n // If left expression refers to an available source and right refers to joined source, keep as is\n if (\n leftAvailableAliases.length > 0 &&\n !leftReferencesJoined &&\n rightReferencesJoined &&\n rightAvailableAliases.length === 0\n ) {\n return { mainExpr: left, joinedExpr: right }\n }\n\n // If left expression refers to joined source and right refers to an available source, swap them\n if (\n leftReferencesJoined &&\n leftAvailableAliases.length === 0 &&\n rightAvailableAliases.length > 0 &&\n !rightReferencesJoined\n ) {\n return { mainExpr: right, joinedExpr: left }\n }\n\n // If one expression doesn't refer to any source, this is an invalid join\n if (leftSourceAliases.size === 0 || rightSourceAliases.size === 0) {\n throw new InvalidJoinConditionSourceMismatchError()\n }\n\n // If both expressions refer to the same alias, this is an invalid join\n if (\n leftSourceAliases.size === 1 &&\n rightSourceAliases.size === 1 &&\n [...leftSourceAliases][0] === [...rightSourceAliases][0]\n ) {\n throw new InvalidJoinConditionSameSourceError([...leftSourceAliases][0]!)\n }\n\n // Left side must refer to an available source\n // This cannot happen with the query builder as there is no way to build a ref\n // to an unavailable source, but just in case, but could happen with the IR\n if (leftAvailableAliases.length === 0) {\n throw new InvalidJoinConditionLeftSourceError([...leftSourceAliases][0]!)\n }\n\n // Right side must refer to the joined source\n if (!rightReferencesJoined) {\n throw new InvalidJoinConditionRightSourceError(joinedSource)\n }\n\n // This should not be reachable given the logic above, but just in case\n throw new InvalidJoinCondition()\n}\n\n/**\n * Extracts the source alias from a join expression\n */\nfunction getSourceAliasesFromExpression(expr: BasicExpression): Set<string> {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the source alias as the first element\n return new Set(expr.path[0] ? [expr.path[0]] : [])\n case `func`: {\n // For function expressions, we need to check if all arguments refer to the same source\n const sourceAliases = new Set<string>()\n for (const arg of expr.args) {\n for (const alias of getSourceAliasesFromExpression(arg)) {\n sourceAliases.add(alias)\n }\n }\n return sourceAliases\n }\n default:\n // Values (type='val') don't reference any source\n return new Set()\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 subscriptions: Record<string, CollectionSubscription>,\n callbacks: Record<string, LazyCollectionCallbacks>,\n lazySources: Set<string>,\n optimizableOrderByCollections: Record<string, OrderByOptimizationInfo>,\n setWindowFn: (windowFn: (options: WindowOptions) => void) => void,\n cache: QueryCache,\n queryMapping: QueryMapping,\n onCompileSubquery: CompileQueryFn,\n aliasToCollectionId: Record<string, string>,\n aliasRemapping: Record<string, string>,\n sourceWhereClauses: Map<string, BasicExpression<boolean>>,\n): { alias: string; input: KeyedStream; collectionId: string } {\n switch (from.type) {\n case `collectionRef`: {\n const input = allInputs[from.sourceId] ?? allInputs[from.alias]\n if (!input) {\n throw new CollectionInputNotFoundError(\n from.alias,\n from.collection.id,\n Object.keys(allInputs),\n )\n }\n aliasToCollectionId[from.alias] = from.collection.id\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 = onCompileSubquery(\n originalQuery,\n allInputs,\n collections,\n subscriptions,\n callbacks,\n lazySources,\n optimizableOrderByCollections,\n setWindowFn,\n cache,\n queryMapping,\n )\n\n // Pull up alias mappings from subquery to parent scope.\n // This includes both the innermost alias-to-collection mappings AND\n // any existing remappings from nested subquery levels.\n Object.assign(aliasToCollectionId, subQueryResult.aliasToCollectionId)\n Object.assign(aliasRemapping, subQueryResult.aliasRemapping)\n\n // Pull up source WHERE clauses from subquery to parent scope.\n // This enables loadSubset to receive the correct where clauses for subquery collections.\n //\n // IMPORTANT: Skip pull-up for optimizer-created subqueries. These are detected when:\n // 1. The outer alias (from.alias) matches the inner alias (from.query.from.alias)\n // 2. The subquery was found in queryMapping (it's a user-defined subquery, not optimizer-created)\n //\n // For optimizer-created subqueries, the parent already has the sourceWhereClauses\n // extracted from the original raw query, so pulling up would be redundant.\n const isUserDefinedSubquery = queryMapping.has(from.query)\n const fromInnerAlias = getFirstFromAlias(from.query)\n const isOptimizerCreated =\n !isUserDefinedSubquery &&\n fromInnerAlias !== undefined &&\n from.alias === fromInnerAlias\n\n if (!isOptimizerCreated) {\n for (const [alias, whereClause] of subQueryResult.sourceWhereClauses) {\n sourceWhereClauses.set(alias, whereClause)\n }\n }\n\n // Create a flattened remapping from outer alias to innermost alias.\n // For nested subqueries, this ensures one-hop lookups (not recursive chains).\n //\n // Example with 3-level nesting:\n // Inner: .from({ user: usersCollection })\n // Middle: .from({ activeUser: innerSubquery }) → creates: activeUser → user\n // Outer: .join({ author: middleSubquery }, ...) → creates: author → user (not author → activeUser)\n //\n // We search through the PULLED-UP aliasToCollectionId (which contains the\n // innermost 'user' alias), so we always map directly to the deepest level.\n // This means aliasRemapping[lazyAlias] is always a single lookup, never recursive.\n const innerAlias = Object.keys(subQueryResult.aliasToCollectionId).find(\n (alias) =>\n subQueryResult.aliasToCollectionId[alias] ===\n subQueryResult.collectionId,\n )\n if (innerAlias && innerAlias !== from.alias) {\n aliasRemapping[from.alias] = innerAlias\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\nfunction getFirstFromAlias(query: QueryIR): string | undefined {\n if (query.from.type === `unionFrom`) {\n return query.from.sources[0]?.alias\n }\n\n if (query.from.type === `unionAll`) {\n return undefined\n }\n\n return query.from.alias\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 source (i.e. left collection) or the joined source (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 getActiveAndLazySources(\n joinType: JoinClause[`type`],\n leftCollection: Collection,\n rightCollection: Collection,\n mainSourceIsParentFiltered: boolean,\n):\n | { activeSource: `main` | `joined`; lazySource: Collection }\n | { activeSource: undefined; lazySource: undefined } {\n // Self-joins can now be optimized since we track lazy loading by source alias\n // rather than collection ID. Each alias has its own subscription and lazy state.\n\n switch (joinType) {\n case `left`:\n return { activeSource: `main`, lazySource: rightCollection }\n case `right`:\n return { activeSource: `joined`, lazySource: leftCollection }\n case `inner`:\n // A correlated include has already reduced the main relation to active\n // parent routes. Keep that relation active and load the joined side from\n // its keys; reversing the join would create an independent demand plan\n // that widens the correlated source back to a union of constraints.\n if (mainSourceIsParentFiltered) {\n return { activeSource: `main`, lazySource: rightCollection }\n }\n // The smallest collection should be the active collection\n // and the biggest collection should be lazy\n return leftCollection.size < rightCollection.size\n ? { activeSource: `main`, lazySource: rightCollection }\n : { activeSource: `joined`, lazySource: leftCollection }\n default:\n return { activeSource: undefined, lazySource: undefined }\n }\n}\n"],"names":["JoinCollectionNotFoundError","compileExpression","map","normalizeValue","UnsupportedJoinTypeError","lazyTargets","getLazyLoadTargets","ensureIndexForField","tap","serializeValue","joinOperator","InvalidJoinConditionSourceMismatchError","InvalidJoinConditionSameSourceError","InvalidJoinConditionLeftSourceError","InvalidJoinConditionRightSourceError","InvalidJoinCondition","CollectionInputNotFoundError","UnsupportedJoinSourceTypeError","filter"],"mappings":"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"}