@tanstack/db
Version:
A reactive client store for building super fast apps on sync
1 lines • 47.7 kB
Source Map (JSON)
{"version":3,"file":"optimizer.cjs","sources":["../../../src/query/optimizer.ts"],"sourcesContent":["/**\n * # Query Optimizer\n *\n * The query optimizer improves query performance by implementing predicate pushdown optimization.\n * It rewrites the intermediate representation (IR) to push WHERE clauses as close to the data\n * source as possible, reducing the amount of data processed during joins.\n *\n * ## How It Works\n *\n * The optimizer follows a 4-step process:\n *\n * ### 1. AND Clause Splitting\n * Splits AND clauses at the root level into separate WHERE clauses for granular optimization.\n * ```javascript\n * // Before: WHERE and(eq(users.department_id, 1), gt(users.age, 25))\n * // After: WHERE eq(users.department_id, 1) + WHERE gt(users.age, 25)\n * ```\n *\n * ### 2. Source Analysis\n * Analyzes each WHERE clause to determine which table sources it references:\n * - Single-source clauses: Touch only one table (e.g., `users.department_id = 1`)\n * - Multi-source clauses: Touch multiple tables (e.g., `users.id = posts.user_id`)\n *\n * ### 3. Clause Grouping\n * Groups WHERE clauses by the sources they touch:\n * - Single-source clauses are grouped by their respective table\n * - Multi-source clauses are combined for the main query\n *\n * ### 4. Subquery Creation\n * Lifts single-source WHERE clauses into subqueries that wrap the original table references.\n *\n * ## Safety & Edge Cases\n *\n * The optimizer includes targeted safety checks to prevent predicate pushdown when it could\n * break query semantics:\n *\n * ### Always Safe Operations\n * - **Creating new subqueries**: Wrapping collection references in subqueries with WHERE clauses\n * - **Main query optimizations**: Moving single-source WHERE clauses from main query to subqueries\n * - **Queries with aggregates/ORDER BY/HAVING**: Can still create new filtered subqueries\n *\n * ### Unsafe Operations (blocked by safety checks)\n * Pushing WHERE clauses **into existing subqueries** that have:\n * - **Aggregates**: GROUP BY, HAVING, or aggregate functions in SELECT (would change aggregation)\n * - **Ordering + Limits**: ORDER BY combined with LIMIT/OFFSET (would change result set)\n * - **Functional Operations**: fnSelect, fnWhere, fnHaving (potential side effects)\n *\n * ### Residual WHERE Clauses\n * For outer joins (LEFT, RIGHT, FULL), WHERE clauses are copied to subqueries for optimization\n * but also kept as \"residual\" clauses in the main query to preserve semantics. This ensures\n * that NULL values from outer joins are properly filtered according to SQL standards.\n *\n * The optimizer tracks which clauses were actually optimized and only removes those from the\n * main query. Subquery reuse is handled safely through immutable query copies.\n *\n * ## Example Optimizations\n *\n * ### Basic Query with Joins\n * **Original Query:**\n * ```javascript\n * query\n * .from({ users: usersCollection })\n * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.user_id))\n * .where(({users}) => eq(users.department_id, 1))\n * .where(({posts}) => gt(posts.views, 100))\n * .where(({users, posts}) => eq(users.id, posts.author_id))\n * ```\n *\n * **Optimized Query:**\n * ```javascript\n * query\n * .from({\n * users: subquery\n * .from({ users: usersCollection })\n * .where(({users}) => eq(users.department_id, 1))\n * })\n * .join({\n * posts: subquery\n * .from({ posts: postsCollection })\n * .where(({posts}) => gt(posts.views, 100))\n * }, ({users, posts}) => eq(users.id, posts.user_id))\n * .where(({users, posts}) => eq(users.id, posts.author_id))\n * ```\n *\n * ### Query with Aggregates (Now Optimizable!)\n * **Original Query:**\n * ```javascript\n * query\n * .from({ users: usersCollection })\n * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.user_id))\n * .where(({users}) => eq(users.department_id, 1))\n * .groupBy(['users.department_id'])\n * .select({ count: agg('count', '*') })\n * ```\n *\n * **Optimized Query:**\n * ```javascript\n * query\n * .from({\n * users: subquery\n * .from({ users: usersCollection })\n * .where(({users}) => eq(users.department_id, 1))\n * })\n * .join({ posts: postsCollection }, ({users, posts}) => eq(users.id, posts.user_id))\n * .groupBy(['users.department_id'])\n * .select({ count: agg('count', '*') })\n * ```\n *\n * ## Benefits\n *\n * - **Reduced Data Processing**: Filters applied before joins reduce intermediate result size\n * - **Better Performance**: Smaller datasets lead to faster query execution\n * - **Automatic Optimization**: No manual query rewriting required\n * - **Preserves Semantics**: Optimized queries return identical results\n * - **Safe by Design**: Comprehensive checks prevent semantic-breaking optimizations\n *\n * ## Integration\n *\n * The optimizer is automatically called during query compilation before the IR is\n * transformed into a D2Mini pipeline.\n */\n\nimport { deepEquals } from '../utils.js'\nimport { CannotCombineEmptyExpressionListError } from '../errors.js'\nimport {\n CollectionRef as CollectionRefClass,\n Func,\n PropRef,\n QueryRef as QueryRefClass,\n createResidualWhere,\n getWhereExpression,\n isResidualWhere,\n} from './ir.js'\nimport type { BasicExpression, From, QueryIR, Select, Where } from './ir.js'\n\n/**\n * Represents a WHERE clause after source analysis\n */\nexport interface AnalyzedWhereClause {\n /** The WHERE expression */\n expression: BasicExpression<boolean>\n /** Set of table/source aliases that this WHERE clause touches */\n touchedSources: Set<string>\n /** Whether this clause contains namespace-only references that prevent pushdown */\n hasNamespaceOnlyRef: boolean\n}\n\n/**\n * Represents WHERE clauses grouped by the sources they touch\n */\nexport interface GroupedWhereClauses {\n /** WHERE clauses that touch only a single source, grouped by source alias */\n singleSource: Map<string, BasicExpression<boolean>>\n /** WHERE clauses that touch multiple sources, combined into one expression */\n multiSource?: BasicExpression<boolean>\n}\n\n/**\n * Result of query optimization including both the optimized query and collection-specific WHERE clauses\n */\nexport interface OptimizationResult {\n /** The optimized query with WHERE clauses potentially moved to subqueries */\n optimizedQuery: QueryIR\n /** Map of source aliases to their extracted WHERE clauses for index optimization */\n sourceWhereClauses: Map<string, BasicExpression<boolean>>\n}\n\n/**\n * Main query optimizer entry point that lifts WHERE clauses into subqueries.\n *\n * This function implements multi-level predicate pushdown optimization by recursively\n * moving WHERE clauses through nested subqueries to get them as close to the data\n * sources as possible, then removing redundant subqueries.\n *\n * @param query - The QueryIR to optimize\n * @returns An OptimizationResult with the optimized query and collection WHERE clause mapping\n *\n * @example\n * ```typescript\n * const originalQuery = {\n * from: new CollectionRef(users, 'u'),\n * join: [{ from: new CollectionRef(posts, 'p'), ... }],\n * where: [eq(u.dept_id, 1), gt(p.views, 100)]\n * }\n *\n * const { optimizedQuery, sourceWhereClauses } = optimizeQuery(originalQuery)\n * // Result: Single-source clauses moved to deepest possible subqueries\n * // sourceWhereClauses: Map { 'u' => eq(u.dept_id, 1), 'p' => gt(p.views, 100) }\n * ```\n */\nexport function optimizeQuery(query: QueryIR): OptimizationResult {\n // First, extract source WHERE clauses before optimization\n const sourceWhereClauses = extractSourceWhereClauses(query)\n\n // Apply multi-level predicate pushdown with iterative convergence\n let optimized = query\n let previousOptimized: QueryIR | undefined\n let iterations = 0\n const maxIterations = 10 // Prevent infinite loops\n\n // Keep optimizing until no more changes occur or max iterations reached\n while (\n iterations < maxIterations &&\n !deepEquals(optimized, previousOptimized)\n ) {\n previousOptimized = optimized\n optimized = applyRecursiveOptimization(optimized)\n iterations++\n }\n\n // Remove redundant subqueries\n const cleaned = removeRedundantSubqueries(optimized)\n\n return {\n optimizedQuery: cleaned,\n sourceWhereClauses,\n }\n}\n\n/**\n * Extracts collection-specific WHERE clauses from a query for index optimization.\n * This analyzes the original query to identify WHERE clauses that can be pushed down\n * to specific collections, but only for simple queries without joins.\n *\n * @param query - The original QueryIR to analyze\n * @returns Map of source aliases to their WHERE clauses\n */\nfunction extractSourceWhereClauses(\n query: QueryIR,\n): Map<string, BasicExpression<boolean>> {\n const sourceWhereClauses = new Map<string, BasicExpression<boolean>>()\n\n // Only analyze queries that have WHERE clauses\n if (!query.where || query.where.length === 0) {\n return sourceWhereClauses\n }\n\n // Split all AND clauses at the root level for granular analysis\n const splitWhereClauses = splitAndClauses(query.where)\n\n // Analyze each WHERE clause to determine which sources it touches\n const analyzedClauses = splitWhereClauses.map((clause) =>\n analyzeWhereClause(clause),\n )\n\n // Group clauses by single-source vs multi-source\n const groupedClauses = groupWhereClauses(analyzedClauses)\n\n // Only include single-source clauses that reference collections directly\n for (const [sourceAlias, whereClause] of groupedClauses.singleSource) {\n // Check if this source alias corresponds to a collection reference\n if (isCollectionReference(query, sourceAlias)) {\n sourceWhereClauses.set(sourceAlias, whereClause)\n }\n }\n\n return sourceWhereClauses\n}\n\n/**\n * Determines if a source alias refers to a collection reference (not a subquery).\n * This is used to identify WHERE clauses that can be pushed down to collection subscriptions.\n *\n * @param query - The query to analyze\n * @param sourceAlias - The source alias to check\n * @returns True if the alias refers to a collection reference\n */\nfunction isCollectionReference(query: QueryIR, sourceAlias: string): boolean {\n // Check the FROM clause\n if (query.from.alias === sourceAlias) {\n return query.from.type === `collectionRef`\n }\n\n // Check JOIN clauses\n if (query.join) {\n for (const joinClause of query.join) {\n if (joinClause.from.alias === sourceAlias) {\n return joinClause.from.type === `collectionRef`\n }\n }\n }\n\n return false\n}\n\n/**\n * Applies recursive predicate pushdown optimization.\n *\n * @param query - The QueryIR to optimize\n * @returns A new QueryIR with optimizations applied\n */\nfunction applyRecursiveOptimization(query: QueryIR): QueryIR {\n // First, recursively optimize any existing subqueries\n const subqueriesOptimized = {\n ...query,\n from:\n query.from.type === `queryRef`\n ? new QueryRefClass(\n applyRecursiveOptimization(query.from.query),\n query.from.alias,\n )\n : query.from,\n join: query.join?.map((joinClause) => ({\n ...joinClause,\n from:\n joinClause.from.type === `queryRef`\n ? new QueryRefClass(\n applyRecursiveOptimization(joinClause.from.query),\n joinClause.from.alias,\n )\n : joinClause.from,\n })),\n }\n\n // Then apply single-level optimization to this query\n return applySingleLevelOptimization(subqueriesOptimized)\n}\n\n/**\n * Applies single-level predicate pushdown optimization (existing logic)\n */\nfunction applySingleLevelOptimization(query: QueryIR): QueryIR {\n // Skip optimization if no WHERE clauses exist\n if (!query.where || query.where.length === 0) {\n return query\n }\n\n // For queries without joins, combine multiple WHERE clauses into a single clause\n // to avoid creating multiple filter operators in the pipeline\n if (!query.join || query.join.length === 0) {\n // Only optimize if there are multiple WHERE clauses to combine\n if (query.where.length > 1) {\n // Combine multiple WHERE clauses into a single AND expression\n const splitWhereClauses = splitAndClauses(query.where)\n const combinedWhere = combineWithAnd(splitWhereClauses)\n\n return {\n ...query,\n where: [combinedWhere],\n }\n }\n\n // For single WHERE clauses, no optimization needed\n return query\n }\n\n // Filter out residual WHERE clauses to prevent them from being optimized again\n const nonResidualWhereClauses = query.where.filter(\n (where) => !isResidualWhere(where),\n )\n\n // Step 1: Split all AND clauses at the root level for granular optimization\n const splitWhereClauses = splitAndClauses(nonResidualWhereClauses)\n\n // Step 2: Analyze each WHERE clause to determine which sources it touches\n const analyzedClauses = splitWhereClauses.map((clause) =>\n analyzeWhereClause(clause),\n )\n\n // Step 3: Group clauses by single-source vs multi-source\n const groupedClauses = groupWhereClauses(analyzedClauses)\n\n // Step 4: Apply optimizations by lifting single-source clauses into subqueries\n const optimizedQuery = applyOptimizations(query, groupedClauses)\n\n // Add back any residual WHERE clauses that were filtered out\n const residualWhereClauses = query.where.filter((where) =>\n isResidualWhere(where),\n )\n if (residualWhereClauses.length > 0) {\n optimizedQuery.where = [\n ...(optimizedQuery.where || []),\n ...residualWhereClauses,\n ]\n }\n\n return optimizedQuery\n}\n\n/**\n * Removes redundant subqueries that don't add value.\n * A subquery is redundant if it only wraps another query without adding\n * WHERE, SELECT, GROUP BY, HAVING, ORDER BY, or LIMIT/OFFSET clauses.\n *\n * @param query - The QueryIR to process\n * @returns A new QueryIR with redundant subqueries removed\n */\nfunction removeRedundantSubqueries(query: QueryIR): QueryIR {\n return {\n ...query,\n from: removeRedundantFromClause(query.from),\n join: query.join?.map((joinClause) => ({\n ...joinClause,\n from: removeRedundantFromClause(joinClause.from),\n })),\n }\n}\n\n/**\n * Removes redundant subqueries from a FROM clause.\n *\n * @param from - The FROM clause to process\n * @returns A FROM clause with redundant subqueries removed\n */\nfunction removeRedundantFromClause(from: From): From {\n if (from.type === `collectionRef`) {\n return from\n }\n\n const processedQuery = removeRedundantSubqueries(from.query)\n\n // Check if this subquery is redundant\n if (isRedundantSubquery(processedQuery)) {\n // Return the inner query's FROM clause with this alias\n const innerFrom = removeRedundantFromClause(processedQuery.from)\n if (innerFrom.type === `collectionRef`) {\n return new CollectionRefClass(innerFrom.collection, from.alias)\n } else {\n return new QueryRefClass(innerFrom.query, from.alias)\n }\n }\n\n return new QueryRefClass(processedQuery, from.alias)\n}\n\n/**\n * Determines if a subquery is redundant (adds no value).\n *\n * @param query - The query to check\n * @returns True if the query is redundant and can be removed\n */\nfunction isRedundantSubquery(query: QueryIR): boolean {\n return (\n (!query.where || query.where.length === 0) &&\n !query.select &&\n (!query.groupBy || query.groupBy.length === 0) &&\n (!query.having || query.having.length === 0) &&\n (!query.orderBy || query.orderBy.length === 0) &&\n (!query.join || query.join.length === 0) &&\n query.limit === undefined &&\n query.offset === undefined &&\n !query.fnSelect &&\n (!query.fnWhere || query.fnWhere.length === 0) &&\n (!query.fnHaving || query.fnHaving.length === 0)\n )\n}\n\n/**\n * Step 1: Split all AND clauses recursively into separate WHERE clauses.\n *\n * This enables more granular optimization by treating each condition independently.\n * OR clauses are preserved as they cannot be split without changing query semantics.\n *\n * @param whereClauses - Array of WHERE expressions to split\n * @returns Flattened array with AND clauses split into separate expressions\n *\n * @example\n * ```typescript\n * // Input: [and(eq(a, 1), gt(b, 2)), eq(c, 3)]\n * // Output: [eq(a, 1), gt(b, 2), eq(c, 3)]\n * ```\n */\nfunction splitAndClauses(\n whereClauses: Array<Where>,\n): Array<BasicExpression<boolean>> {\n const result: Array<BasicExpression<boolean>> = []\n\n for (const whereClause of whereClauses) {\n const clause = getWhereExpression(whereClause)\n result.push(...splitAndClausesRecursive(clause))\n }\n\n return result\n}\n\n// Helper function for recursive splitting of BasicExpression arrays\nfunction splitAndClausesRecursive(\n clause: BasicExpression<boolean>,\n): Array<BasicExpression<boolean>> {\n if (clause.type === `func` && clause.name === `and`) {\n // Recursively split nested AND clauses to handle complex expressions\n const result: Array<BasicExpression<boolean>> = []\n for (const arg of clause.args as Array<BasicExpression<boolean>>) {\n result.push(...splitAndClausesRecursive(arg))\n }\n return result\n } else {\n // Preserve non-AND clauses as-is (including OR clauses)\n return [clause]\n }\n}\n\n/**\n * Step 2: Analyze which table sources a WHERE clause touches.\n *\n * This determines whether a clause can be pushed down to a specific table\n * or must remain in the main query (for multi-source clauses like join conditions).\n *\n * Special handling for namespace-only references in outer joins:\n * WHERE clauses that reference only a table namespace (e.g., isUndefined(special), eq(special, value))\n * rather than specific properties (e.g., isUndefined(special.id), eq(special.id, value)) are treated as\n * multi-source to prevent incorrect predicate pushdown that would change join semantics.\n *\n * @param clause - The WHERE expression to analyze\n * @returns Analysis result with the expression and touched source aliases\n *\n * @example\n * ```typescript\n * // eq(users.department_id, 1) -> touches ['users'], hasNamespaceOnlyRef: false\n * // eq(users.id, posts.user_id) -> touches ['users', 'posts'], hasNamespaceOnlyRef: false\n * // isUndefined(special) -> touches ['special'], hasNamespaceOnlyRef: true (prevents pushdown)\n * // eq(special, someValue) -> touches ['special'], hasNamespaceOnlyRef: true (prevents pushdown)\n * // isUndefined(special.id) -> touches ['special'], hasNamespaceOnlyRef: false (allows pushdown)\n * // eq(special.id, 5) -> touches ['special'], hasNamespaceOnlyRef: false (allows pushdown)\n * ```\n */\nfunction analyzeWhereClause(\n clause: BasicExpression<boolean>,\n): AnalyzedWhereClause {\n // Track which table aliases this WHERE clause touches\n const touchedSources = new Set<string>()\n // Track whether this clause contains namespace-only references that prevent pushdown\n let hasNamespaceOnlyRef = false\n\n /**\n * Recursively collect all table aliases referenced in an expression\n */\n function collectSources(expr: BasicExpression | any): void {\n switch (expr.type) {\n case `ref`:\n // PropRef path has the table alias as the first element\n if (expr.path && expr.path.length > 0) {\n const firstElement = expr.path[0]\n if (firstElement) {\n touchedSources.add(firstElement)\n\n // If the path has only one element (just the namespace),\n // this is a namespace-only reference that should not be pushed down\n // This applies to ANY function, not just existence-checking functions\n if (expr.path.length === 1) {\n hasNamespaceOnlyRef = true\n }\n }\n }\n break\n case `func`:\n // Recursively analyze function arguments (e.g., eq, gt, and, or)\n if (expr.args) {\n expr.args.forEach(collectSources)\n }\n break\n case `val`:\n // Values don't reference any sources\n break\n case `agg`:\n // Aggregates can reference sources in their arguments\n if (expr.args) {\n expr.args.forEach(collectSources)\n }\n break\n }\n }\n\n collectSources(clause)\n\n return {\n expression: clause,\n touchedSources,\n hasNamespaceOnlyRef,\n }\n}\n\n/**\n * Step 3: Group WHERE clauses by the sources they touch.\n *\n * Single-source clauses can be pushed down to subqueries for optimization.\n * Multi-source clauses must remain in the main query to preserve join semantics.\n *\n * @param analyzedClauses - Array of analyzed WHERE clauses\n * @returns Grouped clauses ready for optimization\n */\nfunction groupWhereClauses(\n analyzedClauses: Array<AnalyzedWhereClause>,\n): GroupedWhereClauses {\n const singleSource = new Map<string, Array<BasicExpression<boolean>>>()\n const multiSource: Array<BasicExpression<boolean>> = []\n\n // Categorize each clause based on how many sources it touches\n for (const clause of analyzedClauses) {\n if (clause.touchedSources.size === 1 && !clause.hasNamespaceOnlyRef) {\n // Single source clause without namespace-only references - can be optimized\n const source = Array.from(clause.touchedSources)[0]!\n if (!singleSource.has(source)) {\n singleSource.set(source, [])\n }\n singleSource.get(source)!.push(clause.expression)\n } else if (clause.touchedSources.size > 1 || clause.hasNamespaceOnlyRef) {\n // Multi-source clause or namespace-only reference - must stay in main query\n multiSource.push(clause.expression)\n }\n // Skip clauses that touch no sources (constants) - they don't need optimization\n }\n\n // Combine multiple clauses for each source with AND\n const combinedSingleSource = new Map<string, BasicExpression<boolean>>()\n for (const [source, clauses] of singleSource) {\n combinedSingleSource.set(source, combineWithAnd(clauses))\n }\n\n // Combine multi-source clauses with AND\n const combinedMultiSource =\n multiSource.length > 0 ? combineWithAnd(multiSource) : undefined\n\n return {\n singleSource: combinedSingleSource,\n multiSource: combinedMultiSource,\n }\n}\n\n/**\n * Step 4: Apply optimizations by lifting single-source clauses into subqueries.\n *\n * Creates a new QueryIR with single-source WHERE clauses moved to subqueries\n * that wrap the original table references. This ensures immutability and prevents\n * infinite recursion issues.\n *\n * @param query - Original QueryIR to optimize\n * @param groupedClauses - WHERE clauses grouped by optimization strategy\n * @returns New QueryIR with optimizations applied\n */\nfunction applyOptimizations(\n query: QueryIR,\n groupedClauses: GroupedWhereClauses,\n): QueryIR {\n // Track which single-source clauses were actually optimized\n const actuallyOptimized = new Set<string>()\n\n // Optimize the main FROM clause and track what was optimized\n const optimizedFrom = optimizeFromWithTracking(\n query.from,\n groupedClauses.singleSource,\n actuallyOptimized,\n )\n\n // Optimize JOIN clauses and track what was optimized\n const optimizedJoins = query.join\n ? query.join.map((joinClause) => ({\n ...joinClause,\n from: optimizeFromWithTracking(\n joinClause.from,\n groupedClauses.singleSource,\n actuallyOptimized,\n ),\n }))\n : undefined\n\n // Build the remaining WHERE clauses: multi-source + residual single-source clauses\n const remainingWhereClauses: Array<Where> = []\n\n // Add multi-source clauses\n if (groupedClauses.multiSource) {\n remainingWhereClauses.push(groupedClauses.multiSource)\n }\n\n // Determine if we need residual clauses (when query has outer JOINs)\n const hasOuterJoins =\n query.join &&\n query.join.some(\n (join) =>\n join.type === `left` || join.type === `right` || join.type === `full`,\n )\n\n // Add single-source clauses\n for (const [source, clause] of groupedClauses.singleSource) {\n if (!actuallyOptimized.has(source)) {\n // Wasn't optimized at all - keep as regular WHERE clause\n remainingWhereClauses.push(clause)\n } else if (hasOuterJoins) {\n // Was optimized AND query has outer JOINs - keep as residual WHERE clause\n remainingWhereClauses.push(createResidualWhere(clause))\n }\n // If optimized and no outer JOINs - don't keep (original behavior)\n }\n\n // Combine multiple remaining WHERE clauses into a single clause to avoid\n // multiple filter operations in the pipeline (performance optimization)\n // First flatten any nested AND expressions to avoid and(and(...), ...)\n const finalWhere: Array<Where> =\n remainingWhereClauses.length > 1\n ? [\n combineWithAnd(\n remainingWhereClauses.flatMap((clause) =>\n splitAndClausesRecursive(getWhereExpression(clause)),\n ),\n ),\n ]\n : remainingWhereClauses\n\n // Create a completely new query object to ensure immutability\n const optimizedQuery: QueryIR = {\n // Copy all non-optimized fields as-is\n select: query.select,\n groupBy: query.groupBy ? [...query.groupBy] : undefined,\n having: query.having ? [...query.having] : undefined,\n orderBy: query.orderBy ? [...query.orderBy] : undefined,\n limit: query.limit,\n offset: query.offset,\n distinct: query.distinct,\n fnSelect: query.fnSelect,\n fnWhere: query.fnWhere ? [...query.fnWhere] : undefined,\n fnHaving: query.fnHaving ? [...query.fnHaving] : undefined,\n\n // Use the optimized FROM and JOIN clauses\n from: optimizedFrom,\n join: optimizedJoins,\n\n // Include combined WHERE clauses\n where: finalWhere.length > 0 ? finalWhere : [],\n }\n\n return optimizedQuery\n}\n\n/**\n * Helper function to create a deep copy of a QueryIR object for immutability.\n *\n * This ensures that all optimizations create new objects rather than modifying\n * existing ones, preventing infinite recursion and shared reference issues.\n *\n * @param query - QueryIR to deep copy\n * @returns New QueryIR object with all nested objects copied\n */\nfunction deepCopyQuery(query: QueryIR): QueryIR {\n return {\n // Recursively copy the FROM clause\n from:\n query.from.type === `collectionRef`\n ? new CollectionRefClass(query.from.collection, query.from.alias)\n : new QueryRefClass(deepCopyQuery(query.from.query), query.from.alias),\n\n // Copy all other fields, creating new arrays where necessary\n select: query.select,\n join: query.join\n ? query.join.map((joinClause) => ({\n type: joinClause.type,\n left: joinClause.left,\n right: joinClause.right,\n from:\n joinClause.from.type === `collectionRef`\n ? new CollectionRefClass(\n joinClause.from.collection,\n joinClause.from.alias,\n )\n : new QueryRefClass(\n deepCopyQuery(joinClause.from.query),\n joinClause.from.alias,\n ),\n }))\n : undefined,\n where: query.where ? [...query.where] : undefined,\n groupBy: query.groupBy ? [...query.groupBy] : undefined,\n having: query.having ? [...query.having] : undefined,\n orderBy: query.orderBy ? [...query.orderBy] : undefined,\n limit: query.limit,\n offset: query.offset,\n fnSelect: query.fnSelect,\n fnWhere: query.fnWhere ? [...query.fnWhere] : undefined,\n fnHaving: query.fnHaving ? [...query.fnHaving] : undefined,\n }\n}\n\n/**\n * Helper function to optimize a FROM clause while tracking what was actually optimized.\n *\n * @param from - FROM clause to optimize\n * @param singleSourceClauses - Map of source aliases to their WHERE clauses\n * @param actuallyOptimized - Set to track which sources were actually optimized\n * @returns New FROM clause, potentially wrapped in a subquery\n */\nfunction optimizeFromWithTracking(\n from: From,\n singleSourceClauses: Map<string, BasicExpression<boolean>>,\n actuallyOptimized: Set<string>,\n): From {\n const whereClause = singleSourceClauses.get(from.alias)\n\n if (!whereClause) {\n // No optimization needed, but return a copy to maintain immutability\n if (from.type === `collectionRef`) {\n return new CollectionRefClass(from.collection, from.alias)\n }\n // Must be queryRef due to type system\n return new QueryRefClass(deepCopyQuery(from.query), from.alias)\n }\n\n if (from.type === `collectionRef`) {\n // Create a new subquery with the WHERE clause for the collection\n // This is always safe since we're creating a new subquery\n const subQuery: QueryIR = {\n from: new CollectionRefClass(from.collection, from.alias),\n where: [whereClause],\n }\n actuallyOptimized.add(from.alias) // Mark as successfully optimized\n return new QueryRefClass(subQuery, from.alias)\n }\n\n // SAFETY CHECK: Only check safety when pushing WHERE clauses into existing subqueries\n // We need to be careful about pushing WHERE clauses into subqueries that already have\n // aggregates, HAVING, or ORDER BY + LIMIT since that could change their semantics\n if (!isSafeToPushIntoExistingSubquery(from.query, whereClause, from.alias)) {\n // Return a copy without optimization to maintain immutability\n // Do NOT mark as optimized since we didn't actually optimize it\n return new QueryRefClass(deepCopyQuery(from.query), from.alias)\n }\n\n // Skip pushdown when a clause references a field that only exists via a renamed\n // projection inside the subquery; leaving it outside preserves the alias mapping.\n if (referencesAliasWithRemappedSelect(from.query, whereClause, from.alias)) {\n return new QueryRefClass(deepCopyQuery(from.query), from.alias)\n }\n\n // Add the WHERE clause to the existing subquery\n // Create a deep copy to ensure immutability\n const existingWhere = from.query.where || []\n const optimizedSubQuery: QueryIR = {\n ...deepCopyQuery(from.query),\n where: [...existingWhere, whereClause],\n }\n actuallyOptimized.add(from.alias) // Mark as successfully optimized\n return new QueryRefClass(optimizedSubQuery, from.alias)\n}\n\nfunction unsafeSelect(\n query: QueryIR,\n whereClause: BasicExpression<boolean>,\n outerAlias: string,\n): boolean {\n if (!query.select) return false\n\n return (\n selectHasAggregates(query.select) ||\n whereReferencesComputedSelectFields(query.select, whereClause, outerAlias)\n )\n}\n\nfunction unsafeGroupBy(query: QueryIR) {\n return query.groupBy && query.groupBy.length > 0\n}\n\nfunction unsafeHaving(query: QueryIR) {\n return query.having && query.having.length > 0\n}\n\nfunction unsafeOrderBy(query: QueryIR) {\n return (\n query.orderBy &&\n query.orderBy.length > 0 &&\n (query.limit !== undefined || query.offset !== undefined)\n )\n}\n\nfunction unsafeFnSelect(query: QueryIR) {\n return (\n query.fnSelect ||\n (query.fnWhere && query.fnWhere.length > 0) ||\n (query.fnHaving && query.fnHaving.length > 0)\n )\n}\n\nfunction isSafeToPushIntoExistingSubquery(\n query: QueryIR,\n whereClause: BasicExpression<boolean>,\n outerAlias: string,\n): boolean {\n return !(\n unsafeSelect(query, whereClause, outerAlias) ||\n unsafeGroupBy(query) ||\n unsafeHaving(query) ||\n unsafeOrderBy(query) ||\n unsafeFnSelect(query)\n )\n}\n\n/**\n * Detects whether a SELECT projection contains any aggregate expressions.\n * Recursively traverses nested select objects.\n *\n * @param select - The SELECT object from the IR\n * @returns True if any field is an aggregate, false otherwise\n */\nfunction selectHasAggregates(select: Select): boolean {\n for (const value of Object.values(select)) {\n if (typeof value === `object`) {\n const v: any = value\n if (v.type === `agg`) return true\n if (!(`type` in v)) {\n if (selectHasAggregates(v as unknown as Select)) return true\n }\n }\n }\n return false\n}\n\n/**\n * Recursively collects all PropRef references from an expression.\n *\n * @param expr - The expression to traverse\n * @returns Array of PropRef references found in the expression\n */\nfunction collectRefs(expr: any): Array<PropRef> {\n const refs: Array<PropRef> = []\n\n if (expr == null || typeof expr !== `object`) return refs\n\n switch (expr.type) {\n case `ref`:\n refs.push(expr as PropRef)\n break\n case `func`:\n case `agg`:\n for (const arg of expr.args ?? []) {\n refs.push(...collectRefs(arg))\n }\n break\n default:\n break\n }\n\n return refs\n}\n\n/**\n * Determines whether the provided WHERE clause references fields that are\n * computed by a subquery SELECT rather than pass-through properties.\n *\n * If true, pushing the WHERE clause into the subquery could change semantics\n * (since computed fields do not necessarily exist at the subquery input level),\n * so predicate pushdown must be avoided.\n *\n * @param select - The subquery SELECT map\n * @param whereClause - The WHERE expression to analyze\n * @param outerAlias - The alias of the subquery in the outer query\n * @returns True if WHERE references computed fields, otherwise false\n */\nfunction whereReferencesComputedSelectFields(\n select: Select,\n whereClause: BasicExpression<boolean>,\n outerAlias: string,\n): boolean {\n // Build a set of computed field names at the top-level of the subquery output\n const computed = new Set<string>()\n for (const [key, value] of Object.entries(select)) {\n if (key.startsWith(`__SPREAD_SENTINEL__`)) continue\n if (value instanceof PropRef) continue\n // Nested object or non-PropRef expression counts as computed\n computed.add(key)\n }\n\n const refs = collectRefs(whereClause)\n\n for (const ref of refs) {\n const path = (ref as any).path as Array<string>\n if (!Array.isArray(path) || path.length < 2) continue\n const alias = path[0]\n const field = path[1] as string\n if (alias !== outerAlias) continue\n if (computed.has(field)) return true\n }\n return false\n}\n\n/**\n * Detects whether a WHERE clause references the subquery alias through fields that\n * are re-exposed under different names (renamed SELECT projections or fnSelect output).\n * In those cases we keep the clause at the outer level to avoid alias remapping bugs.\n * TODO: in future we should handle this by rewriting the clause to use the subquery's\n * internal field references, but it likely needs a wider refactor to do cleanly.\n */\nfunction referencesAliasWithRemappedSelect(\n subquery: QueryIR,\n whereClause: BasicExpression<boolean>,\n outerAlias: string,\n): boolean {\n const refs = collectRefs(whereClause)\n // Only care about clauses that actually reference the outer alias.\n if (refs.every((ref) => ref.path[0] !== outerAlias)) {\n return false\n }\n\n // fnSelect always rewrites the row shape, so alias-safe pushdown is impossible.\n if (subquery.fnSelect) {\n return true\n }\n\n const select = subquery.select\n // Without an explicit SELECT the clause still refers to the original collection.\n if (!select) {\n return false\n }\n\n for (const ref of refs) {\n const path = ref.path\n // Need at least alias + field to matter.\n if (path.length < 2) continue\n if (path[0] !== outerAlias) continue\n\n const projected = select[path[1]!]\n // Unselected fields can't be remapped, so skip - only care about fields in the SELECT.\n if (!projected) continue\n\n // Non-PropRef projections are computed values; cannot push down.\n if (!(projected instanceof PropRef)) {\n return true\n }\n\n // If the projection is just the alias (whole row) without a specific field,\n // we can't verify whether the field we're referencing is being preserved or remapped.\n if (projected.path.length < 2) {\n return true\n }\n\n const [innerAlias, innerField] = projected.path\n\n // Safe only when the projection points straight back to the same alias or the\n // underlying source alias and preserves the field name.\n if (innerAlias !== outerAlias && innerAlias !== subquery.from.alias) {\n return true\n }\n\n if (innerField !== path[1]) {\n return true\n }\n }\n\n return false\n}\n\n/**\n * Helper function to combine multiple expressions with AND.\n *\n * If there's only one expression, it's returned as-is.\n * If there are multiple expressions, they're combined with an AND function.\n *\n * @param expressions - Array of expressions to combine\n * @returns Single expression representing the AND combination\n * @throws Error if the expressions array is empty\n */\nfunction combineWithAnd(\n expressions: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> {\n if (expressions.length === 0) {\n throw new CannotCombineEmptyExpressionListError()\n }\n\n if (expressions.length === 1) {\n return expressions[0]!\n }\n\n // Create an AND function with all expressions as arguments\n return new Func(`and`, expressions)\n}\n"],"names":["deepEquals","QueryRefClass","splitWhereClauses","isResidualWhere","CollectionRefClass","getWhereExpression","createResidualWhere","PropRef","CannotCombineEmptyExpressionListError","Func"],"mappings":"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"}