{"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 single-source WHERE clauses that\n * reference collection sources (not subqueries), including joined collections.\n *\n * For outer joins, clauses referencing the nullable side are excluded because\n * using them to pre-filter collection data would change join semantics.\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  // Determine which source aliases are on the nullable side of outer joins.\n  // WHERE clauses for these sources must not be used for index optimization\n  // because they should filter the final joined result, not the input data.\n  const nullableSources = getNullableJoinSources(query)\n\n  // Only include single-source clauses that reference collections directly\n  // and are not on the nullable side of an outer join\n  for (const [sourceAlias, whereClause] of groupedClauses.singleSource) {\n    // Check if this source alias corresponds to a collection reference\n    if (\n      isCollectionReference(query, sourceAlias) &&\n      !nullableSources.has(sourceAlias)\n    ) {\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 * Returns the set of source aliases that are on the nullable side of outer joins.\n *\n * For a LEFT join the joined (right) side is nullable.\n * For a RIGHT join the main (left/from) side is nullable.\n * For a FULL join both sides are nullable.\n *\n * WHERE clauses that reference only a nullable source must not be pushed down\n * into that source's subquery or used for index optimization, because doing so\n * changes the join semantics: rows that should be excluded by the WHERE become\n * unmatched outer-join rows (with the nullable side set to undefined) and\n * incorrectly survive residual filtering.\n */\nfunction getNullableJoinSources(query: QueryIR): Set<string> {\n  const nullable = new Set<string>()\n  if (query.join) {\n    const mainAlias = query.from.alias\n    for (const join of query.join) {\n      const joinedAlias = join.from.alias\n      if (join.type === `left` || join.type === `full`) {\n        nullable.add(joinedAlias)\n      }\n      if (join.type === `right` || join.type === `full`) {\n        nullable.add(mainAlias)\n      }\n    }\n  }\n  return nullable\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  // Determine which source aliases are on the nullable side of outer joins.\n  const nullableSources = getNullableJoinSources(query)\n\n  // Build a filtered copy of singleSource that excludes nullable-side clauses.\n  // Pushing a WHERE clause into the nullable side's subquery pre-filters the\n  // data before the join, converting \"matched but WHERE-excluded\" rows into\n  // \"unmatched\" outer-join rows.  These are indistinguishable from genuinely\n  // unmatched rows, so the residual WHERE cannot correct the result.\n  const pushableSingleSource = new Map<string, BasicExpression<boolean>>()\n  for (const [source, clause] of groupedClauses.singleSource) {\n    if (!nullableSources.has(source)) {\n      pushableSingleSource.set(source, clause)\n    }\n  }\n\n  // Optimize the main FROM clause and track what was optimized\n  const optimizedFrom = optimizeFromWithTracking(\n    query.from,\n    pushableSingleSource,\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          pushableSingleSource,\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 = nullableSources.size > 0\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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"}