@tanstack/db 0.5.11 → 0.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/dist/cjs/SortedMap.cjs +40 -26
  2. package/dist/cjs/SortedMap.cjs.map +1 -1
  3. package/dist/cjs/SortedMap.d.cts +10 -15
  4. package/dist/cjs/collection/change-events.cjs.map +1 -1
  5. package/dist/cjs/collection/changes.cjs.map +1 -1
  6. package/dist/cjs/collection/events.cjs.map +1 -1
  7. package/dist/cjs/collection/events.d.cts +12 -4
  8. package/dist/cjs/collection/index.cjs +2 -1
  9. package/dist/cjs/collection/index.cjs.map +1 -1
  10. package/dist/cjs/collection/indexes.cjs.map +1 -1
  11. package/dist/cjs/collection/lifecycle.cjs.map +1 -1
  12. package/dist/cjs/collection/mutations.cjs +5 -2
  13. package/dist/cjs/collection/mutations.cjs.map +1 -1
  14. package/dist/cjs/collection/state.cjs +6 -5
  15. package/dist/cjs/collection/state.cjs.map +1 -1
  16. package/dist/cjs/collection/state.d.cts +4 -1
  17. package/dist/cjs/collection/subscription.cjs +60 -53
  18. package/dist/cjs/collection/subscription.cjs.map +1 -1
  19. package/dist/cjs/collection/subscription.d.cts +18 -4
  20. package/dist/cjs/collection/sync.cjs.map +1 -1
  21. package/dist/cjs/errors.cjs +9 -0
  22. package/dist/cjs/errors.cjs.map +1 -1
  23. package/dist/cjs/errors.d.cts +3 -0
  24. package/dist/cjs/event-emitter.cjs.map +1 -1
  25. package/dist/cjs/index.cjs +2 -0
  26. package/dist/cjs/index.cjs.map +1 -1
  27. package/dist/cjs/index.d.cts +1 -1
  28. package/dist/cjs/indexes/auto-index.cjs.map +1 -1
  29. package/dist/cjs/indexes/base-index.cjs.map +1 -1
  30. package/dist/cjs/indexes/btree-index.cjs +8 -6
  31. package/dist/cjs/indexes/btree-index.cjs.map +1 -1
  32. package/dist/cjs/indexes/lazy-index.cjs.map +1 -1
  33. package/dist/cjs/indexes/reverse-index.cjs.map +1 -1
  34. package/dist/cjs/local-only.cjs.map +1 -1
  35. package/dist/cjs/local-storage.cjs.map +1 -1
  36. package/dist/cjs/optimistic-action.cjs.map +1 -1
  37. package/dist/cjs/paced-mutations.cjs.map +1 -1
  38. package/dist/cjs/proxy.cjs.map +1 -1
  39. package/dist/cjs/query/builder/functions.cjs.map +1 -1
  40. package/dist/cjs/query/builder/index.cjs.map +1 -1
  41. package/dist/cjs/query/builder/ref-proxy.cjs.map +1 -1
  42. package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
  43. package/dist/cjs/query/compiler/expressions.cjs.map +1 -1
  44. package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
  45. package/dist/cjs/query/compiler/index.cjs.map +1 -1
  46. package/dist/cjs/query/compiler/joins.cjs.map +1 -1
  47. package/dist/cjs/query/compiler/order-by.cjs +91 -38
  48. package/dist/cjs/query/compiler/order-by.cjs.map +1 -1
  49. package/dist/cjs/query/compiler/order-by.d.cts +6 -2
  50. package/dist/cjs/query/compiler/select.cjs.map +1 -1
  51. package/dist/cjs/query/expression-helpers.cjs.map +1 -1
  52. package/dist/cjs/query/index.d.cts +1 -1
  53. package/dist/cjs/query/ir.cjs.map +1 -1
  54. package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
  55. package/dist/cjs/query/live/collection-registry.cjs.map +1 -1
  56. package/dist/cjs/query/live/collection-subscriber.cjs +30 -15
  57. package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
  58. package/dist/cjs/query/live/internal.cjs.map +1 -1
  59. package/dist/cjs/query/live-query-collection.cjs.map +1 -1
  60. package/dist/cjs/query/optimizer.cjs.map +1 -1
  61. package/dist/cjs/query/predicate-utils.cjs +19 -2
  62. package/dist/cjs/query/predicate-utils.cjs.map +1 -1
  63. package/dist/cjs/query/predicate-utils.d.cts +32 -1
  64. package/dist/cjs/query/subset-dedupe.cjs.map +1 -1
  65. package/dist/cjs/scheduler.cjs.map +1 -1
  66. package/dist/cjs/strategies/debounceStrategy.cjs.map +1 -1
  67. package/dist/cjs/strategies/queueStrategy.cjs.map +1 -1
  68. package/dist/cjs/strategies/throttleStrategy.cjs.map +1 -1
  69. package/dist/cjs/transactions.cjs.map +1 -1
  70. package/dist/cjs/types.d.cts +43 -5
  71. package/dist/cjs/utils/browser-polyfills.cjs.map +1 -1
  72. package/dist/cjs/utils/btree.cjs.map +1 -1
  73. package/dist/cjs/utils/comparison.cjs.map +1 -1
  74. package/dist/cjs/utils/cursor.cjs +39 -0
  75. package/dist/cjs/utils/cursor.cjs.map +1 -0
  76. package/dist/cjs/utils/cursor.d.cts +18 -0
  77. package/dist/cjs/utils/index-optimization.cjs.map +1 -1
  78. package/dist/cjs/utils.cjs.map +1 -1
  79. package/dist/esm/SortedMap.d.ts +10 -15
  80. package/dist/esm/SortedMap.js +40 -26
  81. package/dist/esm/SortedMap.js.map +1 -1
  82. package/dist/esm/collection/change-events.js.map +1 -1
  83. package/dist/esm/collection/changes.js.map +1 -1
  84. package/dist/esm/collection/events.d.ts +12 -4
  85. package/dist/esm/collection/events.js.map +1 -1
  86. package/dist/esm/collection/index.js +2 -1
  87. package/dist/esm/collection/index.js.map +1 -1
  88. package/dist/esm/collection/indexes.js.map +1 -1
  89. package/dist/esm/collection/lifecycle.js.map +1 -1
  90. package/dist/esm/collection/mutations.js +6 -3
  91. package/dist/esm/collection/mutations.js.map +1 -1
  92. package/dist/esm/collection/state.d.ts +4 -1
  93. package/dist/esm/collection/state.js +6 -5
  94. package/dist/esm/collection/state.js.map +1 -1
  95. package/dist/esm/collection/subscription.d.ts +18 -4
  96. package/dist/esm/collection/subscription.js +61 -54
  97. package/dist/esm/collection/subscription.js.map +1 -1
  98. package/dist/esm/collection/sync.js.map +1 -1
  99. package/dist/esm/errors.d.ts +3 -0
  100. package/dist/esm/errors.js +9 -0
  101. package/dist/esm/errors.js.map +1 -1
  102. package/dist/esm/event-emitter.js.map +1 -1
  103. package/dist/esm/index.d.ts +1 -1
  104. package/dist/esm/index.js +4 -2
  105. package/dist/esm/indexes/auto-index.js.map +1 -1
  106. package/dist/esm/indexes/base-index.js.map +1 -1
  107. package/dist/esm/indexes/btree-index.js +8 -6
  108. package/dist/esm/indexes/btree-index.js.map +1 -1
  109. package/dist/esm/indexes/lazy-index.js.map +1 -1
  110. package/dist/esm/indexes/reverse-index.js.map +1 -1
  111. package/dist/esm/local-only.js.map +1 -1
  112. package/dist/esm/local-storage.js.map +1 -1
  113. package/dist/esm/optimistic-action.js.map +1 -1
  114. package/dist/esm/paced-mutations.js.map +1 -1
  115. package/dist/esm/proxy.js.map +1 -1
  116. package/dist/esm/query/builder/functions.js.map +1 -1
  117. package/dist/esm/query/builder/index.js.map +1 -1
  118. package/dist/esm/query/builder/ref-proxy.js.map +1 -1
  119. package/dist/esm/query/compiler/evaluators.js.map +1 -1
  120. package/dist/esm/query/compiler/expressions.js.map +1 -1
  121. package/dist/esm/query/compiler/group-by.js.map +1 -1
  122. package/dist/esm/query/compiler/index.js.map +1 -1
  123. package/dist/esm/query/compiler/joins.js.map +1 -1
  124. package/dist/esm/query/compiler/order-by.d.ts +6 -2
  125. package/dist/esm/query/compiler/order-by.js +91 -38
  126. package/dist/esm/query/compiler/order-by.js.map +1 -1
  127. package/dist/esm/query/compiler/select.js.map +1 -1
  128. package/dist/esm/query/expression-helpers.js.map +1 -1
  129. package/dist/esm/query/index.d.ts +1 -1
  130. package/dist/esm/query/ir.js.map +1 -1
  131. package/dist/esm/query/live/collection-config-builder.js.map +1 -1
  132. package/dist/esm/query/live/collection-registry.js.map +1 -1
  133. package/dist/esm/query/live/collection-subscriber.js +30 -15
  134. package/dist/esm/query/live/collection-subscriber.js.map +1 -1
  135. package/dist/esm/query/live/internal.js.map +1 -1
  136. package/dist/esm/query/live-query-collection.js.map +1 -1
  137. package/dist/esm/query/optimizer.js.map +1 -1
  138. package/dist/esm/query/predicate-utils.d.ts +32 -1
  139. package/dist/esm/query/predicate-utils.js +19 -2
  140. package/dist/esm/query/predicate-utils.js.map +1 -1
  141. package/dist/esm/query/subset-dedupe.js.map +1 -1
  142. package/dist/esm/scheduler.js.map +1 -1
  143. package/dist/esm/strategies/debounceStrategy.js.map +1 -1
  144. package/dist/esm/strategies/queueStrategy.js.map +1 -1
  145. package/dist/esm/strategies/throttleStrategy.js.map +1 -1
  146. package/dist/esm/transactions.js.map +1 -1
  147. package/dist/esm/types.d.ts +43 -5
  148. package/dist/esm/utils/browser-polyfills.js.map +1 -1
  149. package/dist/esm/utils/btree.js.map +1 -1
  150. package/dist/esm/utils/comparison.js.map +1 -1
  151. package/dist/esm/utils/cursor.d.ts +18 -0
  152. package/dist/esm/utils/cursor.js +39 -0
  153. package/dist/esm/utils/cursor.js.map +1 -0
  154. package/dist/esm/utils/index-optimization.js.map +1 -1
  155. package/dist/esm/utils.js.map +1 -1
  156. package/package.json +30 -28
  157. package/src/SortedMap.ts +50 -31
  158. package/src/collection/change-events.ts +20 -20
  159. package/src/collection/changes.ts +12 -12
  160. package/src/collection/events.ts +20 -10
  161. package/src/collection/index.ts +47 -46
  162. package/src/collection/indexes.ts +14 -14
  163. package/src/collection/lifecycle.ts +16 -16
  164. package/src/collection/mutations.ts +25 -20
  165. package/src/collection/state.ts +43 -36
  166. package/src/collection/subscription.ts +114 -83
  167. package/src/collection/sync.ts +13 -13
  168. package/src/duplicate-instance-check.ts +1 -1
  169. package/src/errors.ts +49 -40
  170. package/src/event-emitter.ts +5 -5
  171. package/src/index.ts +21 -21
  172. package/src/indexes/auto-index.ts +11 -11
  173. package/src/indexes/base-index.ts +13 -13
  174. package/src/indexes/btree-index.ts +21 -17
  175. package/src/indexes/index-options.ts +3 -3
  176. package/src/indexes/lazy-index.ts +8 -8
  177. package/src/indexes/reverse-index.ts +5 -5
  178. package/src/local-only.ts +12 -12
  179. package/src/local-storage.ts +17 -17
  180. package/src/optimistic-action.ts +5 -5
  181. package/src/paced-mutations.ts +6 -6
  182. package/src/proxy.ts +43 -43
  183. package/src/query/builder/functions.ts +28 -28
  184. package/src/query/builder/index.ts +22 -22
  185. package/src/query/builder/ref-proxy.ts +4 -4
  186. package/src/query/builder/types.ts +8 -8
  187. package/src/query/compiler/evaluators.ts +9 -9
  188. package/src/query/compiler/expressions.ts +6 -6
  189. package/src/query/compiler/group-by.ts +24 -24
  190. package/src/query/compiler/index.ts +44 -44
  191. package/src/query/compiler/joins.ts +37 -37
  192. package/src/query/compiler/order-by.ts +170 -77
  193. package/src/query/compiler/select.ts +13 -13
  194. package/src/query/compiler/types.ts +2 -2
  195. package/src/query/expression-helpers.ts +16 -16
  196. package/src/query/index.ts +10 -9
  197. package/src/query/ir.ts +13 -13
  198. package/src/query/live/collection-config-builder.ts +53 -53
  199. package/src/query/live/collection-registry.ts +6 -6
  200. package/src/query/live/collection-subscriber.ts +87 -48
  201. package/src/query/live/internal.ts +1 -1
  202. package/src/query/live/types.ts +4 -4
  203. package/src/query/live-query-collection.ts +15 -15
  204. package/src/query/optimizer.ts +29 -29
  205. package/src/query/predicate-utils.ts +105 -50
  206. package/src/query/subset-dedupe.ts +6 -6
  207. package/src/scheduler.ts +3 -3
  208. package/src/strategies/debounceStrategy.ts +6 -6
  209. package/src/strategies/index.ts +4 -4
  210. package/src/strategies/queueStrategy.ts +5 -5
  211. package/src/strategies/throttleStrategy.ts +6 -6
  212. package/src/strategies/types.ts +2 -2
  213. package/src/transactions.ts +9 -9
  214. package/src/types.ts +51 -12
  215. package/src/utils/array-utils.ts +1 -1
  216. package/src/utils/browser-polyfills.ts +2 -2
  217. package/src/utils/btree.ts +22 -22
  218. package/src/utils/comparison.ts +3 -3
  219. package/src/utils/cursor.ts +78 -0
  220. package/src/utils/index-optimization.ts +14 -14
  221. package/src/utils.ts +4 -4
@@ -1 +1 @@
1
- {"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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"}
1
+ {"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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"}
@@ -419,14 +419,30 @@ function isLimitSubset(subset, superset) {
419
419
  }
420
420
  return subset <= superset;
421
421
  }
422
+ function isOffsetLimitSubset(subset, superset) {
423
+ const subsetOffset = subset.offset ?? 0;
424
+ const supersetOffset = superset.offset ?? 0;
425
+ if (supersetOffset > subsetOffset) {
426
+ return false;
427
+ }
428
+ if (superset.limit === void 0) {
429
+ return true;
430
+ }
431
+ if (subset.limit === void 0) {
432
+ return false;
433
+ }
434
+ const subsetEnd = subsetOffset + subset.limit;
435
+ const supersetEnd = supersetOffset + superset.limit;
436
+ return subsetEnd <= supersetEnd;
437
+ }
422
438
  function isPredicateSubset(subset, superset) {
423
439
  if (superset.limit !== void 0) {
424
440
  if (!areWhereClausesEqual(subset.where, superset.where)) {
425
441
  return false;
426
442
  }
427
- return isOrderBySubset(subset.orderBy, superset.orderBy) && isLimitSubset(subset.limit, superset.limit);
443
+ return isOrderBySubset(subset.orderBy, superset.orderBy) && isOffsetLimitSubset(subset, superset);
428
444
  }
429
- return isWhereSubset(subset.where, superset.where) && isOrderBySubset(subset.orderBy, superset.orderBy) && isLimitSubset(subset.limit, superset.limit);
445
+ return isWhereSubset(subset.where, superset.where) && isOrderBySubset(subset.orderBy, superset.orderBy) && isOffsetLimitSubset(subset, superset);
430
446
  }
431
447
  function areWhereClausesEqual(a, b) {
432
448
  if (a === void 0 && b === void 0) {
@@ -823,6 +839,7 @@ function unionSameFieldPredicates(predicates) {
823
839
  };
824
840
  }
825
841
  exports.isLimitSubset = isLimitSubset;
842
+ exports.isOffsetLimitSubset = isOffsetLimitSubset;
826
843
  exports.isOrderBySubset = isOrderBySubset;
827
844
  exports.isPredicateSubset = isPredicateSubset;
828
845
  exports.isWhereSubset = isWhereSubset;
@@ -1 +1 @@
1
- {"version":3,"file":"predicate-utils.cjs","sources":["../../../src/query/predicate-utils.ts"],"sourcesContent":["import { Func, Value } from \"./ir.js\"\nimport type { BasicExpression, OrderBy, PropRef } from \"./ir.js\"\nimport type { LoadSubsetOptions } from \"../types.js\"\n\n/**\n * Check if one where clause is a logical subset of another.\n * Returns true if the subset predicate is more restrictive than (or equal to) the superset predicate.\n *\n * @example\n * // age > 20 is subset of age > 10 (more restrictive)\n * isWhereSubset(gt(ref('age'), val(20)), gt(ref('age'), val(10))) // true\n *\n * @example\n * // age > 10 AND name = 'X' is subset of age > 10 (more conditions)\n * isWhereSubset(and(gt(ref('age'), val(10)), eq(ref('name'), val('X'))), gt(ref('age'), val(10))) // true\n *\n * @param subset - The potentially more restrictive predicate\n * @param superset - The potentially less restrictive predicate\n * @returns true if subset logically implies superset\n */\nexport function isWhereSubset(\n subset: BasicExpression<boolean> | undefined,\n superset: BasicExpression<boolean> | undefined\n): boolean {\n // undefined/missing where clause means \"no filter\" (all data)\n // Both undefined means subset relationship holds (all data ⊆ all data)\n if (subset === undefined && superset === undefined) {\n return true\n }\n\n // If subset is undefined but superset is not, we're requesting ALL data\n // but have only loaded SOME data - subset relationship does NOT hold\n if (subset === undefined && superset !== undefined) {\n return false\n }\n\n // If superset is undefined (no filter = all data loaded),\n // then any constrained subset is contained\n if (superset === undefined && subset !== undefined) {\n return true\n }\n\n return isWhereSubsetInternal(subset!, superset!)\n}\n\nfunction makeDisjunction(\n preds: Array<BasicExpression<boolean>>\n): BasicExpression<boolean> {\n if (preds.length === 0) {\n return new Value(false)\n }\n if (preds.length === 1) {\n return preds[0]!\n }\n return new Func(`or`, preds)\n}\n\nfunction convertInToOr(inField: InField) {\n const equalities = inField.values.map(\n (value) => new Func(`eq`, [inField.ref, new Value(value)])\n )\n return makeDisjunction(equalities)\n}\n\nfunction isWhereSubsetInternal(\n subset: BasicExpression<boolean>,\n superset: BasicExpression<boolean>\n): boolean {\n // If subset is false it is requesting no data,\n // thus the result set is empty\n // and the empty set is a subset of any set\n if (subset.type === `val` && subset.value === false) {\n return true\n }\n\n // If expressions are structurally equal, subset relationship holds\n if (areExpressionsEqual(subset, superset)) {\n return true\n }\n\n // Handle superset being an AND: subset must imply ALL conjuncts\n // If superset is (A AND B), then subset ⊆ (A AND B) only if subset ⊆ A AND subset ⊆ B\n // Example: (age > 20) ⊆ (age > 10 AND status = 'active') is false (doesn't imply status condition)\n if (superset.type === `func` && superset.name === `and`) {\n return superset.args.every((arg) =>\n isWhereSubsetInternal(subset, arg as BasicExpression<boolean>)\n )\n }\n\n // Handle subset being an AND: (A AND B) implies both A and B\n if (subset.type === `func` && subset.name === `and`) {\n // For (A AND B) ⊆ C, since (A AND B) implies A, we check if any conjunct implies C\n return subset.args.some((arg) =>\n isWhereSubsetInternal(arg as BasicExpression<boolean>, superset)\n )\n }\n\n // Turn x IN [A, B, C] into x = A OR x = B OR x = C\n // for unified handling of IN and OR\n if (subset.type === `func` && subset.name === `in`) {\n const inField = extractInField(subset)\n if (inField) {\n return isWhereSubsetInternal(convertInToOr(inField), superset)\n }\n }\n\n if (superset.type === `func` && superset.name === `in`) {\n const inField = extractInField(superset)\n if (inField) {\n return isWhereSubsetInternal(subset, convertInToOr(inField))\n }\n }\n\n // Handle OR in subset: (A OR B) is subset of C only if both A and B are subsets of C\n if (subset.type === `func` && subset.name === `or`) {\n return subset.args.every((arg) =>\n isWhereSubsetInternal(arg as BasicExpression<boolean>, superset)\n )\n }\n\n // Handle OR in superset: subset ⊆ (A OR B) if subset ⊆ A or subset ⊆ B\n // (A OR B) as superset means data can satisfy A or B\n // If subset is contained in any disjunct, it's contained in the union\n if (superset.type === `func` && superset.name === `or`) {\n return superset.args.some((arg) =>\n isWhereSubsetInternal(subset, arg as BasicExpression<boolean>)\n )\n }\n\n // Handle comparison operators on the same field\n if (subset.type === `func` && superset.type === `func`) {\n const subsetFunc = subset as Func\n const supersetFunc = superset as Func\n\n // Check if both are comparisons on the same field\n const subsetField = extractComparisonField(subsetFunc)\n const supersetField = extractComparisonField(supersetFunc)\n\n if (\n subsetField &&\n supersetField &&\n areRefsEqual(subsetField.ref, supersetField.ref)\n ) {\n return isComparisonSubset(\n subsetFunc,\n subsetField.value,\n supersetFunc,\n supersetField.value\n )\n }\n\n /*\n // Handle eq vs in\n if (subsetFunc.name === `eq` && supersetFunc.name === `in`) {\n const subsetFieldEq = extractEqualityField(subsetFunc)\n const supersetFieldIn = extractInField(supersetFunc)\n if (\n subsetFieldEq &&\n supersetFieldIn &&\n areRefsEqual(subsetFieldEq.ref, supersetFieldIn.ref)\n ) {\n // field = X is subset of field IN [X, Y, Z] if X is in the array\n // Use cached primitive set and metadata from extraction\n return arrayIncludesWithSet(\n supersetFieldIn.values,\n subsetFieldEq.value,\n supersetFieldIn.primitiveSet ?? null,\n supersetFieldIn.areAllPrimitives\n )\n }\n }\n\n // Handle in vs in\n if (subsetFunc.name === `in` && supersetFunc.name === `in`) {\n const subsetFieldIn = extractInField(subsetFunc)\n const supersetFieldIn = extractInField(supersetFunc)\n if (\n subsetFieldIn &&\n supersetFieldIn &&\n areRefsEqual(subsetFieldIn.ref, supersetFieldIn.ref)\n ) {\n // field IN [A, B] is subset of field IN [A, B, C] if all values in subset are in superset\n // Use cached primitive set and metadata from extraction\n return subsetFieldIn.values.every((subVal) =>\n arrayIncludesWithSet(\n supersetFieldIn.values,\n subVal,\n supersetFieldIn.primitiveSet ?? null,\n supersetFieldIn.areAllPrimitives\n )\n )\n }\n }\n */\n }\n\n // Conservative: if we can't determine, return false\n return false\n}\n\n/**\n * Helper to combine where predicates with common logic for AND/OR operations\n */\nfunction combineWherePredicates(\n predicates: Array<BasicExpression<boolean>>,\n operation: `and` | `or`,\n simplifyFn: (\n preds: Array<BasicExpression<boolean>>\n ) => BasicExpression<boolean> | null\n): BasicExpression<boolean> {\n const emptyValue = operation === `and` ? true : false\n const identityValue = operation === `and` ? true : false\n\n if (predicates.length === 0) {\n return { type: `val`, value: emptyValue } as BasicExpression<boolean>\n }\n\n if (predicates.length === 1) {\n return predicates[0]!\n }\n\n // Flatten nested expressions of the same operation\n const flatPredicates: Array<BasicExpression<boolean>> = []\n for (const pred of predicates) {\n if (pred.type === `func` && pred.name === operation) {\n flatPredicates.push(...pred.args)\n } else {\n flatPredicates.push(pred)\n }\n }\n\n // Group predicates by field for simplification\n const grouped = groupPredicatesByField(flatPredicates)\n\n // Simplify each group\n const simplified: Array<BasicExpression<boolean>> = []\n for (const [field, preds] of grouped.entries()) {\n if (field === null) {\n // Complex predicates that we can't group by field\n simplified.push(...preds)\n } else {\n // Try to simplify same-field predicates\n const result = simplifyFn(preds)\n\n // For intersection: check for empty set (contradiction)\n if (\n operation === `and` &&\n result &&\n result.type === `val` &&\n result.value === false\n ) {\n // Intersection is empty (conflicting constraints) - entire AND is false\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // For union: result may be null if simplification failed\n if (result) {\n simplified.push(result)\n }\n }\n }\n\n if (simplified.length === 0) {\n return { type: `val`, value: identityValue } as BasicExpression<boolean>\n }\n\n if (simplified.length === 1) {\n return simplified[0]!\n }\n\n // Return combined predicate\n return {\n type: `func`,\n name: operation,\n args: simplified,\n } as BasicExpression<boolean>\n}\n\n/**\n * Combine multiple where predicates with OR logic (union).\n * Returns a predicate that is satisfied when any input predicate is satisfied.\n * Simplifies when possible (e.g., age > 10 OR age > 20 → age > 10).\n *\n * @example\n * // Take least restrictive\n * unionWherePredicates([gt(ref('age'), val(10)), gt(ref('age'), val(20))]) // age > 10\n *\n * @example\n * // Combine equals into IN\n * unionWherePredicates([eq(ref('age'), val(5)), eq(ref('age'), val(10))]) // age IN [5, 10]\n *\n * @param predicates - Array of where predicates to union\n * @returns Combined predicate representing the union\n */\nexport function unionWherePredicates(\n predicates: Array<BasicExpression<boolean>>\n): BasicExpression<boolean> {\n return combineWherePredicates(predicates, `or`, unionSameFieldPredicates)\n}\n\n/**\n * Compute the difference between two where predicates: `fromPredicate AND NOT(subtractPredicate)`.\n * Returns the simplified predicate, or null if the difference cannot be simplified\n * (in which case the caller should fetch the full fromPredicate).\n *\n * @example\n * // Range difference\n * minusWherePredicates(\n * gt(ref('age'), val(10)), // age > 10\n * gt(ref('age'), val(20)) // age > 20\n * ) // → age > 10 AND age <= 20\n *\n * @example\n * // Set difference\n * minusWherePredicates(\n * inOp(ref('status'), ['A', 'B', 'C', 'D']), // status IN ['A','B','C','D']\n * inOp(ref('status'), ['B', 'C']) // status IN ['B','C']\n * ) // → status IN ['A', 'D']\n *\n * @example\n * // Common conditions\n * minusWherePredicates(\n * and(gt(ref('age'), val(10)), eq(ref('status'), val('active'))), // age > 10 AND status = 'active'\n * and(gt(ref('age'), val(20)), eq(ref('status'), val('active'))) // age > 20 AND status = 'active'\n * ) // → age > 10 AND age <= 20 AND status = 'active'\n *\n * @example\n * // Complete overlap - empty result\n * minusWherePredicates(\n * gt(ref('age'), val(20)), // age > 20\n * gt(ref('age'), val(10)) // age > 10\n * ) // → {type: 'val', value: false} (empty set)\n *\n * @param fromPredicate - The predicate to subtract from\n * @param subtractPredicate - The predicate to subtract\n * @returns The simplified difference, or null if cannot be simplified\n */\nexport function minusWherePredicates(\n fromPredicate: BasicExpression<boolean> | undefined,\n subtractPredicate: BasicExpression<boolean> | undefined\n): BasicExpression<boolean> | null {\n // If nothing to subtract, return the original\n if (subtractPredicate === undefined) {\n return (\n fromPredicate ??\n ({ type: `val`, value: true } as BasicExpression<boolean>)\n )\n }\n\n // If from is undefined then we are asking for all data\n // so we need to load all data minus what we already loaded\n // i.e. we need to load NOT(subtractPredicate)\n if (fromPredicate === undefined) {\n return {\n type: `func`,\n name: `not`,\n args: [subtractPredicate],\n } as BasicExpression<boolean>\n }\n\n // Check if fromPredicate is entirely contained in subtractPredicate\n // In that case, fromPredicate AND NOT(subtractPredicate) = empty set\n if (isWhereSubset(fromPredicate, subtractPredicate)) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // Try to detect and handle common conditions\n const commonConditions = findCommonConditions(\n fromPredicate,\n subtractPredicate\n )\n if (commonConditions.length > 0) {\n // Extract predicates without common conditions\n const fromWithoutCommon = removeConditions(fromPredicate, commonConditions)\n const subtractWithoutCommon = removeConditions(\n subtractPredicate,\n commonConditions\n )\n\n // Recursively compute difference on simplified predicates\n const simplifiedDifference = minusWherePredicates(\n fromWithoutCommon,\n subtractWithoutCommon\n )\n\n if (simplifiedDifference !== null) {\n // Combine the simplified difference with common conditions\n return combineConditions([...commonConditions, simplifiedDifference])\n }\n }\n\n // Check if they are on the same field - if so, we can try to simplify\n if (fromPredicate.type === `func` && subtractPredicate.type === `func`) {\n const result = minusSameFieldPredicates(fromPredicate, subtractPredicate)\n if (result !== null) {\n return result\n }\n }\n\n // Can't simplify - return null to indicate caller should fetch full fromPredicate\n return null\n}\n\n/**\n * Helper function to compute difference for same-field predicates\n */\nfunction minusSameFieldPredicates(\n fromPred: Func,\n subtractPred: Func\n): BasicExpression<boolean> | null {\n // Extract field information\n const fromField =\n extractComparisonField(fromPred) ||\n extractEqualityField(fromPred) ||\n extractInField(fromPred)\n const subtractField =\n extractComparisonField(subtractPred) ||\n extractEqualityField(subtractPred) ||\n extractInField(subtractPred)\n\n // Must be on the same field\n if (\n !fromField ||\n !subtractField ||\n !areRefsEqual(fromField.ref, subtractField.ref)\n ) {\n return null\n }\n\n // Handle IN minus IN: status IN [A,B,C,D] - status IN [B,C] = status IN [A,D]\n if (fromPred.name === `in` && subtractPred.name === `in`) {\n const fromInField = fromField as InField\n const subtractInField = subtractField as InField\n\n // Filter out values that are in the subtract set\n const remainingValues = fromInField.values.filter(\n (v) =>\n !arrayIncludesWithSet(\n subtractInField.values,\n v,\n subtractInField.primitiveSet ?? null,\n subtractInField.areAllPrimitives\n )\n )\n\n if (remainingValues.length === 0) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n if (remainingValues.length === 1) {\n return {\n type: `func`,\n name: `eq`,\n args: [fromField.ref, { type: `val`, value: remainingValues[0] }],\n } as BasicExpression<boolean>\n }\n\n return {\n type: `func`,\n name: `in`,\n args: [fromField.ref, { type: `val`, value: remainingValues }],\n } as BasicExpression<boolean>\n }\n\n // Handle IN minus equality: status IN [A,B,C] - status = B = status IN [A,C]\n if (fromPred.name === `in` && subtractPred.name === `eq`) {\n const fromInField = fromField as InField\n const subtractValue = (subtractField as { ref: PropRef; value: any }).value\n\n const remainingValues = fromInField.values.filter(\n (v) => !areValuesEqual(v, subtractValue)\n )\n\n if (remainingValues.length === 0) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n if (remainingValues.length === 1) {\n return {\n type: `func`,\n name: `eq`,\n args: [fromField.ref, { type: `val`, value: remainingValues[0] }],\n } as BasicExpression<boolean>\n }\n\n return {\n type: `func`,\n name: `in`,\n args: [fromField.ref, { type: `val`, value: remainingValues }],\n } as BasicExpression<boolean>\n }\n\n // Handle equality minus equality: age = 15 - age = 15 = empty, age = 15 - age = 20 = age = 15\n if (fromPred.name === `eq` && subtractPred.name === `eq`) {\n const fromValue = (fromField as { ref: PropRef; value: any }).value\n const subtractValue = (subtractField as { ref: PropRef; value: any }).value\n\n if (areValuesEqual(fromValue, subtractValue)) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // No overlap - return original\n return fromPred as BasicExpression<boolean>\n }\n\n // Handle range minus range: age > 10 - age > 20 = age > 10 AND age <= 20\n const fromComp = extractComparisonField(fromPred)\n const subtractComp = extractComparisonField(subtractPred)\n\n if (\n fromComp &&\n subtractComp &&\n areRefsEqual(fromComp.ref, subtractComp.ref)\n ) {\n // Try to compute the difference using range logic\n const result = minusRangePredicates(\n fromPred,\n fromComp.value,\n subtractPred,\n subtractComp.value\n )\n return result\n }\n\n // Can't simplify\n return null\n}\n\n/**\n * Helper to compute difference between range predicates\n */\nfunction minusRangePredicates(\n fromFunc: Func,\n fromValue: any,\n subtractFunc: Func,\n subtractValue: any\n): BasicExpression<boolean> | null {\n const fromOp = fromFunc.name as `gt` | `gte` | `lt` | `lte` | `eq`\n const subtractOp = subtractFunc.name as `gt` | `gte` | `lt` | `lte` | `eq`\n const ref = (extractComparisonField(fromFunc) ||\n extractEqualityField(fromFunc))!.ref\n\n // age > 10 - age > 20 = (age > 10 AND age <= 20)\n if (fromOp === `gt` && subtractOp === `gt`) {\n if (fromValue < subtractValue) {\n // Result is: fromValue < field <= subtractValue\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n // fromValue >= subtractValue means no overlap\n return fromFunc as BasicExpression<boolean>\n }\n\n // age >= 10 - age >= 20 = (age >= 10 AND age < 20)\n if (fromOp === `gte` && subtractOp === `gte`) {\n if (fromValue < subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age > 10 - age >= 20 = (age > 10 AND age < 20)\n if (fromOp === `gt` && subtractOp === `gte`) {\n if (fromValue < subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age >= 10 - age > 20 = (age >= 10 AND age <= 20)\n if (fromOp === `gte` && subtractOp === `gt`) {\n if (fromValue <= subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age < 30 - age < 20 = (age >= 20 AND age < 30)\n if (fromOp === `lt` && subtractOp === `lt`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age <= 30 - age <= 20 = (age > 20 AND age <= 30)\n if (fromOp === `lte` && subtractOp === `lte`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age < 30 - age <= 20 = (age > 20 AND age < 30)\n if (fromOp === `lt` && subtractOp === `lte`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age <= 30 - age < 20 = (age >= 20 AND age <= 30)\n if (fromOp === `lte` && subtractOp === `lt`) {\n if (fromValue >= subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // Can't simplify other combinations\n return null\n}\n\n/**\n * Check if one orderBy clause is a subset of another.\n * Returns true if the subset ordering requirements are satisfied by the superset ordering.\n *\n * @example\n * // Subset is prefix of superset\n * isOrderBySubset([{expr: age, asc}], [{expr: age, asc}, {expr: name, desc}]) // true\n *\n * @param subset - The ordering requirements to check\n * @param superset - The ordering that might satisfy the requirements\n * @returns true if subset is satisfied by superset\n */\nexport function isOrderBySubset(\n subset: OrderBy | undefined,\n superset: OrderBy | undefined\n): boolean {\n // No ordering requirement is always satisfied\n if (!subset || subset.length === 0) {\n return true\n }\n\n // If there's no superset ordering but subset requires ordering, not satisfied\n if (!superset || superset.length === 0) {\n return false\n }\n\n // Check if subset is a prefix of superset with matching expressions and compare options\n if (subset.length > superset.length) {\n return false\n }\n\n for (let i = 0; i < subset.length; i++) {\n const subClause = subset[i]!\n const superClause = superset[i]!\n\n // Check if expressions match\n if (!areExpressionsEqual(subClause.expression, superClause.expression)) {\n return false\n }\n\n // Check if compare options match\n if (\n !areCompareOptionsEqual(\n subClause.compareOptions,\n superClause.compareOptions\n )\n ) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Check if one limit is a subset of another.\n * Returns true if the subset limit requirements are satisfied by the superset limit.\n *\n * @example\n * isLimitSubset(10, 20) // true (requesting 10 items when 20 are available)\n * isLimitSubset(20, 10) // false (requesting 20 items when only 10 are available)\n * isLimitSubset(10, undefined) // true (requesting 10 items when unlimited are available)\n *\n * @param subset - The limit requirement to check\n * @param superset - The limit that might satisfy the requirement\n * @returns true if subset is satisfied by superset\n */\nexport function isLimitSubset(\n subset: number | undefined,\n superset: number | undefined\n): boolean {\n // Unlimited superset satisfies any limit requirement\n if (superset === undefined) {\n return true\n }\n\n // If requesting all data (no limit), we need unlimited data to satisfy it\n // But we know superset is not unlimited so we return false\n if (subset === undefined) {\n return false\n }\n\n // Otherwise, subset must be less than or equal to superset\n return subset <= superset\n}\n\n/**\n * Check if one predicate (where + orderBy + limit) is a subset of another.\n * Returns true if all aspects of the subset predicate are satisfied by the superset.\n *\n * @example\n * isPredicateSubset(\n * { where: gt(ref('age'), val(20)), limit: 10 },\n * { where: gt(ref('age'), val(10)), limit: 20 }\n * ) // true\n *\n * @param subset - The predicate requirements to check\n * @param superset - The predicate that might satisfy the requirements\n * @returns true if subset is satisfied by superset\n */\nexport function isPredicateSubset(\n subset: LoadSubsetOptions,\n superset: LoadSubsetOptions\n): boolean {\n // When the superset has a limit, we can only determine subset relationship\n // if the where clauses are equal (not just subset relationship).\n //\n // This is because a limited query only loads a portion of the matching rows.\n // A more restrictive where clause might require rows outside that portion.\n //\n // Example: superset = {where: undefined, limit: 10, orderBy: desc}\n // subset = {where: LIKE 'search%', limit: 10, orderBy: desc}\n // The top 10 items matching 'search%' might include items outside the overall top 10.\n //\n // However, if the where clauses are equal, then the subset relationship can\n // be determined by orderBy and limit alone:\n // Example: superset = {where: status='active', limit: 10, orderBy: desc}\n // subset = {where: status='active', limit: 5, orderBy: desc}\n // The top 5 active items ARE contained in the top 10 active items.\n if (superset.limit !== undefined) {\n // For limited supersets, where clauses must be equal\n if (!areWhereClausesEqual(subset.where, superset.where)) {\n return false\n }\n return (\n isOrderBySubset(subset.orderBy, superset.orderBy) &&\n isLimitSubset(subset.limit, superset.limit)\n )\n }\n\n // For unlimited supersets, use the normal subset logic\n return (\n isWhereSubset(subset.where, superset.where) &&\n isOrderBySubset(subset.orderBy, superset.orderBy) &&\n isLimitSubset(subset.limit, superset.limit)\n )\n}\n\n/**\n * Check if two where clauses are structurally equal.\n * Used for limited query subset checks where subset relationship isn't sufficient.\n */\nfunction areWhereClausesEqual(\n a: BasicExpression<boolean> | undefined,\n b: BasicExpression<boolean> | undefined\n): boolean {\n if (a === undefined && b === undefined) {\n return true\n }\n if (a === undefined || b === undefined) {\n return false\n }\n return areExpressionsEqual(a, b)\n}\n\n// ============================================================================\n// Helper functions\n// ============================================================================\n\n/**\n * Find common conditions between two predicates.\n * Returns an array of conditions that appear in both predicates.\n */\nfunction findCommonConditions(\n predicate1: BasicExpression<boolean>,\n predicate2: BasicExpression<boolean>\n): Array<BasicExpression<boolean>> {\n const conditions1 = extractAllConditions(predicate1)\n const conditions2 = extractAllConditions(predicate2)\n\n const common: Array<BasicExpression<boolean>> = []\n\n for (const cond1 of conditions1) {\n for (const cond2 of conditions2) {\n if (areExpressionsEqual(cond1, cond2)) {\n // Avoid duplicates\n if (!common.some((c) => areExpressionsEqual(c, cond1))) {\n common.push(cond1)\n }\n break\n }\n }\n }\n\n return common\n}\n\n/**\n * Extract all individual conditions from a predicate, flattening AND operations.\n */\nfunction extractAllConditions(\n predicate: BasicExpression<boolean>\n): Array<BasicExpression<boolean>> {\n if (predicate.type === `func` && predicate.name === `and`) {\n const conditions: Array<BasicExpression<boolean>> = []\n for (const arg of predicate.args) {\n conditions.push(...extractAllConditions(arg as BasicExpression<boolean>))\n }\n return conditions\n }\n\n return [predicate]\n}\n\n/**\n * Remove specified conditions from a predicate.\n * Returns the predicate with the specified conditions removed, or undefined if all conditions are removed.\n */\nfunction removeConditions(\n predicate: BasicExpression<boolean>,\n conditionsToRemove: Array<BasicExpression<boolean>>\n): BasicExpression<boolean> | undefined {\n if (predicate.type === `func` && predicate.name === `and`) {\n const remainingArgs = predicate.args.filter(\n (arg) =>\n !conditionsToRemove.some((cond) =>\n areExpressionsEqual(arg as BasicExpression<boolean>, cond)\n )\n )\n\n if (remainingArgs.length === 0) {\n return undefined\n } else if (remainingArgs.length === 1) {\n return remainingArgs[0]!\n } else {\n return {\n type: `func`,\n name: `and`,\n args: remainingArgs,\n } as BasicExpression<boolean>\n }\n }\n\n // For non-AND predicates, don't remove anything\n return predicate\n}\n\n/**\n * Combine multiple conditions into a single predicate using AND logic.\n * Flattens nested AND operations to avoid unnecessary nesting.\n */\nfunction combineConditions(\n conditions: Array<BasicExpression<boolean>>\n): BasicExpression<boolean> {\n if (conditions.length === 0) {\n return { type: `val`, value: true } as BasicExpression<boolean>\n } else if (conditions.length === 1) {\n return conditions[0]!\n } else {\n // Flatten all conditions, including those that are already AND operations\n const flattenedConditions: Array<BasicExpression<boolean>> = []\n\n for (const condition of conditions) {\n if (condition.type === `func` && condition.name === `and`) {\n // Flatten nested AND operations\n flattenedConditions.push(...condition.args)\n } else {\n flattenedConditions.push(condition)\n }\n }\n\n if (flattenedConditions.length === 1) {\n return flattenedConditions[0]!\n } else {\n return {\n type: `func`,\n name: `and`,\n args: flattenedConditions,\n } as BasicExpression<boolean>\n }\n }\n}\n\n/**\n * Find a predicate with a specific operator and value\n */\nfunction findPredicateWithOperator(\n predicates: Array<BasicExpression<boolean>>,\n operator: string,\n value: any\n): BasicExpression<boolean> | undefined {\n return predicates.find((p) => {\n if (p.type === `func`) {\n const f = p as Func\n const field = extractComparisonField(f)\n return f.name === operator && field && areValuesEqual(field.value, value)\n }\n return false\n })\n}\n\nfunction areExpressionsEqual(a: BasicExpression, b: BasicExpression): boolean {\n if (a.type !== b.type) {\n return false\n }\n\n if (a.type === `val` && b.type === `val`) {\n return areValuesEqual(a.value, b.value)\n }\n\n if (a.type === `ref` && b.type === `ref`) {\n return areRefsEqual(a, b)\n }\n\n if (a.type === `func` && b.type === `func`) {\n const aFunc = a\n const bFunc = b\n if (aFunc.name !== bFunc.name) {\n return false\n }\n if (aFunc.args.length !== bFunc.args.length) {\n return false\n }\n return aFunc.args.every((arg, i) =>\n areExpressionsEqual(arg, bFunc.args[i]!)\n )\n }\n\n return false\n}\n\nfunction areValuesEqual(a: any, b: any): boolean {\n // Simple equality check - could be enhanced for deep object comparison\n if (a === b) {\n return true\n }\n\n // Handle NaN\n if (typeof a === `number` && typeof b === `number` && isNaN(a) && isNaN(b)) {\n return true\n }\n\n // Handle Date objects\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n // For arrays and objects, use reference equality\n // (In practice, we don't need deep equality for these cases -\n // same object reference means same value for our use case)\n if (\n typeof a === `object` &&\n typeof b === `object` &&\n a !== null &&\n b !== null\n ) {\n return a === b\n }\n\n return false\n}\n\nfunction areRefsEqual(a: PropRef, b: PropRef): boolean {\n if (a.path.length !== b.path.length) {\n return false\n }\n return a.path.every((segment, i) => segment === b.path[i])\n}\n\n/**\n * Check if a value is a primitive (string, number, boolean, null, undefined)\n * Primitives can use Set for fast lookups\n */\nfunction isPrimitive(value: any): boolean {\n return (\n value === null ||\n value === undefined ||\n typeof value === `string` ||\n typeof value === `number` ||\n typeof value === `boolean`\n )\n}\n\n/**\n * Check if all values in an array are primitives\n */\nfunction areAllPrimitives(values: Array<any>): boolean {\n return values.every(isPrimitive)\n}\n\n/**\n * Check if a value is in an array, with optional pre-built Set for optimization.\n * The primitiveSet is cached in InField during extraction and reused for all lookups.\n */\nfunction arrayIncludesWithSet(\n array: Array<any>,\n value: any,\n primitiveSet: Set<any> | null,\n arrayIsAllPrimitives?: boolean\n): boolean {\n // Fast path: use pre-built Set for O(1) lookup\n if (primitiveSet) {\n // Skip isPrimitive check if we know the value must be primitive for a match\n // (if array is all primitives, only primitives can match)\n if (arrayIsAllPrimitives || isPrimitive(value)) {\n return primitiveSet.has(value)\n }\n return false // Non-primitive can't be in primitive-only set\n }\n\n // Fallback: use areValuesEqual for Dates and objects\n return array.some((v) => areValuesEqual(v, value))\n}\n\n/**\n * Get the maximum of two values, handling both numbers and Dates\n */\nfunction maxValue(a: any, b: any): any {\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() > b.getTime() ? a : b\n }\n return Math.max(a, b)\n}\n\n/**\n * Get the minimum of two values, handling both numbers and Dates\n */\nfunction minValue(a: any, b: any): any {\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() < b.getTime() ? a : b\n }\n return Math.min(a, b)\n}\n\nfunction areCompareOptionsEqual(\n a: { direction?: `asc` | `desc`; [key: string]: any },\n b: { direction?: `asc` | `desc`; [key: string]: any }\n): boolean {\n // For now, just compare direction - could be enhanced for other options\n return a.direction === b.direction\n}\n\ninterface ComparisonField {\n ref: PropRef\n value: any\n}\n\nfunction extractComparisonField(func: Func): ComparisonField | null {\n // Handle comparison operators: eq, gt, gte, lt, lte\n if ([`eq`, `gt`, `gte`, `lt`, `lte`].includes(func.name)) {\n // Assume first arg is ref, second is value\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (firstArg?.type === `ref` && secondArg?.type === `val`) {\n return {\n ref: firstArg,\n value: secondArg.value,\n }\n }\n }\n\n return null\n}\n\nfunction extractEqualityField(func: Func): ComparisonField | null {\n if (func.name === `eq`) {\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (firstArg?.type === `ref` && secondArg?.type === `val`) {\n return {\n ref: firstArg,\n value: secondArg.value,\n }\n }\n }\n return null\n}\n\ninterface InField {\n ref: PropRef\n values: Array<any>\n // Cached optimization data (computed once, reused many times)\n areAllPrimitives?: boolean\n primitiveSet?: Set<any> | null\n}\n\nfunction extractInField(func: Func): InField | null {\n if (func.name === `in`) {\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (\n firstArg?.type === `ref` &&\n secondArg?.type === `val` &&\n Array.isArray(secondArg.value)\n ) {\n let values = secondArg.value\n // Precompute optimization metadata once\n const allPrimitives = areAllPrimitives(values)\n let primitiveSet: Set<any> | null = null\n\n if (allPrimitives && values.length > 10) {\n // Build Set and dedupe values at the same time\n primitiveSet = new Set(values)\n // If we found duplicates, use the deduped array going forward\n if (primitiveSet.size < values.length) {\n values = Array.from(primitiveSet)\n }\n }\n\n return {\n ref: firstArg,\n values,\n areAllPrimitives: allPrimitives,\n primitiveSet,\n }\n }\n }\n return null\n}\n\nfunction isComparisonSubset(\n subsetFunc: Func,\n subsetValue: any,\n supersetFunc: Func,\n supersetValue: any\n): boolean {\n const subOp = subsetFunc.name\n const superOp = supersetFunc.name\n\n // Handle same operator\n if (subOp === superOp) {\n if (subOp === `eq`) {\n // field = X is subset of field = X only\n // Fast path: primitives can use strict equality\n if (isPrimitive(subsetValue) && isPrimitive(supersetValue)) {\n return subsetValue === supersetValue\n }\n return areValuesEqual(subsetValue, supersetValue)\n } else if (subOp === `gt`) {\n // field > 20 is subset of field > 10 if 20 > 10\n return subsetValue >= supersetValue\n } else if (subOp === `gte`) {\n // field >= 20 is subset of field >= 10 if 20 >= 10\n return subsetValue >= supersetValue\n } else if (subOp === `lt`) {\n // field < 10 is subset of field < 20 if 10 <= 20\n return subsetValue <= supersetValue\n } else if (subOp === `lte`) {\n // field <= 10 is subset of field <= 20 if 10 <= 20\n return subsetValue <= supersetValue\n }\n }\n\n // Handle different operators on same field\n // eq vs gt/gte: field = 15 is subset of field > 10 if 15 > 10\n if (subOp === `eq` && superOp === `gt`) {\n return subsetValue > supersetValue\n }\n if (subOp === `eq` && superOp === `gte`) {\n return subsetValue >= supersetValue\n }\n if (subOp === `eq` && superOp === `lt`) {\n return subsetValue < supersetValue\n }\n if (subOp === `eq` && superOp === `lte`) {\n return subsetValue <= supersetValue\n }\n\n // gt/gte vs gte/gt\n if (subOp === `gt` && superOp === `gte`) {\n // field > 10 is subset of field >= 10 if 10 >= 10 (always true for same value)\n return subsetValue >= supersetValue\n }\n if (subOp === `gte` && superOp === `gt`) {\n // field >= 11 is subset of field > 10 if 11 > 10\n return subsetValue > supersetValue\n }\n\n // lt/lte vs lte/lt\n if (subOp === `lt` && superOp === `lte`) {\n // field < 10 is subset of field <= 10 if 10 <= 10\n return subsetValue <= supersetValue\n }\n if (subOp === `lte` && superOp === `lt`) {\n // field <= 9 is subset of field < 10 if 9 < 10\n return subsetValue < supersetValue\n }\n\n return false\n}\n\nfunction groupPredicatesByField(\n predicates: Array<BasicExpression<boolean>>\n): Map<string | null, Array<BasicExpression<boolean>>> {\n const groups = new Map<string | null, Array<BasicExpression<boolean>>>()\n\n for (const pred of predicates) {\n let fieldKey: string | null = null\n\n if (pred.type === `func`) {\n const func = pred as Func\n const field =\n extractComparisonField(func) ||\n extractEqualityField(func) ||\n extractInField(func)\n if (field) {\n fieldKey = field.ref.path.join(`.`)\n }\n }\n\n const group = groups.get(fieldKey) || []\n group.push(pred)\n groups.set(fieldKey, group)\n }\n\n return groups\n}\n\nfunction unionSameFieldPredicates(\n predicates: Array<BasicExpression<boolean>>\n): BasicExpression<boolean> | null {\n if (predicates.length === 1) {\n return predicates[0]!\n }\n\n // Try to extract range constraints\n let maxGt: number | null = null\n let maxGte: number | null = null\n let minLt: number | null = null\n let minLte: number | null = null\n const eqValues: Set<any> = new Set()\n const inValues: Set<any> = new Set()\n const otherPredicates: Array<BasicExpression<boolean>> = []\n\n for (const pred of predicates) {\n if (pred.type === `func`) {\n const func = pred as Func\n const field = extractComparisonField(func)\n\n if (field) {\n const value = field.value\n if (func.name === `gt`) {\n maxGt = maxGt === null ? value : minValue(maxGt, value)\n } else if (func.name === `gte`) {\n maxGte = maxGte === null ? value : minValue(maxGte, value)\n } else if (func.name === `lt`) {\n minLt = minLt === null ? value : maxValue(minLt, value)\n } else if (func.name === `lte`) {\n minLte = minLte === null ? value : maxValue(minLte, value)\n } else if (func.name === `eq`) {\n eqValues.add(value)\n } else {\n otherPredicates.push(pred)\n }\n } else {\n const inField = extractInField(func)\n if (inField) {\n for (const val of inField.values) {\n inValues.add(val)\n }\n } else {\n otherPredicates.push(pred)\n }\n }\n } else {\n otherPredicates.push(pred)\n }\n }\n\n // If we have multiple equality values, combine into IN\n if (eqValues.size > 1 || (eqValues.size > 0 && inValues.size > 0)) {\n const allValues = [...eqValues, ...inValues]\n const ref = predicates.find((p) => {\n if (p.type === `func`) {\n const field =\n extractComparisonField(p as Func) || extractInField(p as Func)\n return field !== null\n }\n return false\n })\n\n if (ref && ref.type === `func`) {\n const field =\n extractComparisonField(ref as Func) || extractInField(ref as Func)\n if (field) {\n return {\n type: `func`,\n name: `in`,\n args: [\n field.ref,\n { type: `val`, value: allValues } as BasicExpression,\n ],\n } as BasicExpression<boolean>\n }\n }\n }\n\n // Build the least restrictive range\n const result: Array<BasicExpression<boolean>> = []\n\n // Choose the least restrictive lower bound\n if (maxGt !== null && maxGte !== null) {\n // Take the smaller one (less restrictive)\n const pred =\n maxGte <= maxGt\n ? findPredicateWithOperator(predicates, `gte`, maxGte)\n : findPredicateWithOperator(predicates, `gt`, maxGt)\n if (pred) result.push(pred)\n } else if (maxGt !== null) {\n const pred = findPredicateWithOperator(predicates, `gt`, maxGt)\n if (pred) result.push(pred)\n } else if (maxGte !== null) {\n const pred = findPredicateWithOperator(predicates, `gte`, maxGte)\n if (pred) result.push(pred)\n }\n\n // Choose the least restrictive upper bound\n if (minLt !== null && minLte !== null) {\n const pred =\n minLte >= minLt\n ? findPredicateWithOperator(predicates, `lte`, minLte)\n : findPredicateWithOperator(predicates, `lt`, minLt)\n if (pred) result.push(pred)\n } else if (minLt !== null) {\n const pred = findPredicateWithOperator(predicates, `lt`, minLt)\n if (pred) result.push(pred)\n } else if (minLte !== null) {\n const pred = findPredicateWithOperator(predicates, `lte`, minLte)\n if (pred) result.push(pred)\n }\n\n // Add single eq value\n if (eqValues.size === 1 && inValues.size === 0) {\n const pred = findPredicateWithOperator(predicates, `eq`, [...eqValues][0])\n if (pred) result.push(pred)\n }\n\n // Add IN if only IN values\n if (eqValues.size === 0 && inValues.size > 0) {\n result.push(\n predicates.find((p) => {\n if (p.type === `func`) {\n return (p as Func).name === `in`\n }\n return false\n })!\n )\n }\n\n // Add other predicates\n result.push(...otherPredicates)\n\n if (result.length === 0) {\n return { type: `val`, value: true } as BasicExpression<boolean>\n }\n\n if (result.length === 1) {\n return result[0]!\n }\n\n return {\n type: `func`,\n name: `or`,\n args: result,\n } as BasicExpression<boolean>\n}\n"],"names":["Value","Func"],"mappings":";;;AAoBO,SAAS,cACd,QACA,UACS;AAGT,MAAI,WAAW,UAAa,aAAa,QAAW;AAClD,WAAO;AAAA,EACT;AAIA,MAAI,WAAW,UAAa,aAAa,QAAW;AAClD,WAAO;AAAA,EACT;AAIA,MAAI,aAAa,UAAa,WAAW,QAAW;AAClD,WAAO;AAAA,EACT;AAEA,SAAO,sBAAsB,QAAS,QAAS;AACjD;AAEA,SAAS,gBACP,OAC0B;AAC1B,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,IAAIA,GAAAA,MAAM,KAAK;AAAA,EACxB;AACA,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,MAAM,CAAC;AAAA,EAChB;AACA,SAAO,IAAIC,GAAAA,KAAK,MAAM,KAAK;AAC7B;AAEA,SAAS,cAAc,SAAkB;AACvC,QAAM,aAAa,QAAQ,OAAO;AAAA,IAChC,CAAC,UAAU,IAAIA,QAAK,MAAM,CAAC,QAAQ,KAAK,IAAID,SAAM,KAAK,CAAC,CAAC;AAAA,EAAA;AAE3D,SAAO,gBAAgB,UAAU;AACnC;AAEA,SAAS,sBACP,QACA,UACS;AAIT,MAAI,OAAO,SAAS,SAAS,OAAO,UAAU,OAAO;AACnD,WAAO;AAAA,EACT;AAGA,MAAI,oBAAoB,QAAQ,QAAQ,GAAG;AACzC,WAAO;AAAA,EACT;AAKA,MAAI,SAAS,SAAS,UAAU,SAAS,SAAS,OAAO;AACvD,WAAO,SAAS,KAAK;AAAA,MAAM,CAAC,QAC1B,sBAAsB,QAAQ,GAA+B;AAAA,IAAA;AAAA,EAEjE;AAGA,MAAI,OAAO,SAAS,UAAU,OAAO,SAAS,OAAO;AAEnD,WAAO,OAAO,KAAK;AAAA,MAAK,CAAC,QACvB,sBAAsB,KAAiC,QAAQ;AAAA,IAAA;AAAA,EAEnE;AAIA,MAAI,OAAO,SAAS,UAAU,OAAO,SAAS,MAAM;AAClD,UAAM,UAAU,eAAe,MAAM;AACrC,QAAI,SAAS;AACX,aAAO,sBAAsB,cAAc,OAAO,GAAG,QAAQ;AAAA,IAC/D;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,UAAU,SAAS,SAAS,MAAM;AACtD,UAAM,UAAU,eAAe,QAAQ;AACvC,QAAI,SAAS;AACX,aAAO,sBAAsB,QAAQ,cAAc,OAAO,CAAC;AAAA,IAC7D;AAAA,EACF;AAGA,MAAI,OAAO,SAAS,UAAU,OAAO,SAAS,MAAM;AAClD,WAAO,OAAO,KAAK;AAAA,MAAM,CAAC,QACxB,sBAAsB,KAAiC,QAAQ;AAAA,IAAA;AAAA,EAEnE;AAKA,MAAI,SAAS,SAAS,UAAU,SAAS,SAAS,MAAM;AACtD,WAAO,SAAS,KAAK;AAAA,MAAK,CAAC,QACzB,sBAAsB,QAAQ,GAA+B;AAAA,IAAA;AAAA,EAEjE;AAGA,MAAI,OAAO,SAAS,UAAU,SAAS,SAAS,QAAQ;AACtD,UAAM,aAAa;AACnB,UAAM,eAAe;AAGrB,UAAM,cAAc,uBAAuB,UAAU;AACrD,UAAM,gBAAgB,uBAAuB,YAAY;AAEzD,QACE,eACA,iBACA,aAAa,YAAY,KAAK,cAAc,GAAG,GAC/C;AACA,aAAO;AAAA,QACL;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,QACA,cAAc;AAAA,MAAA;AAAA,IAElB;AAAA,EA6CF;AAGA,SAAO;AACT;AAKA,SAAS,uBACP,YACA,WACA,YAG0B;AAC1B,QAAM,aAAa,cAAc,QAAQ,OAAO;AAChD,QAAM,gBAAgB,cAAc,QAAQ,OAAO;AAEnD,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,EAAE,MAAM,OAAO,OAAO,WAAA;AAAA,EAC/B;AAEA,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,WAAW,CAAC;AAAA,EACrB;AAGA,QAAM,iBAAkD,CAAA;AACxD,aAAW,QAAQ,YAAY;AAC7B,QAAI,KAAK,SAAS,UAAU,KAAK,SAAS,WAAW;AACnD,qBAAe,KAAK,GAAG,KAAK,IAAI;AAAA,IAClC,OAAO;AACL,qBAAe,KAAK,IAAI;AAAA,IAC1B;AAAA,EACF;AAGA,QAAM,UAAU,uBAAuB,cAAc;AAGrD,QAAM,aAA8C,CAAA;AACpD,aAAW,CAAC,OAAO,KAAK,KAAK,QAAQ,WAAW;AAC9C,QAAI,UAAU,MAAM;AAElB,iBAAW,KAAK,GAAG,KAAK;AAAA,IAC1B,OAAO;AAEL,YAAM,SAAS,WAAW,KAAK;AAc/B,UAAI,QAAQ;AACV,mBAAW,KAAK,MAAM;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,EAAE,MAAM,OAAO,OAAO,cAAA;AAAA,EAC/B;AAEA,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,WAAW,CAAC;AAAA,EACrB;AAGA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,EAAA;AAEV;AAkBO,SAAS,qBACd,YAC0B;AAC1B,SAAO,uBAAuB,YAAY,MAAM,wBAAwB;AAC1E;AAuCO,SAAS,qBACd,eACA,mBACiC;AAEjC,MAAI,sBAAsB,QAAW;AACnC,WACE,iBACC,EAAE,MAAM,OAAO,OAAO,KAAA;AAAA,EAE3B;AAKA,MAAI,kBAAkB,QAAW;AAC/B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,CAAC,iBAAiB;AAAA,IAAA;AAAA,EAE5B;AAIA,MAAI,cAAc,eAAe,iBAAiB,GAAG;AACnD,WAAO,EAAE,MAAM,OAAO,OAAO,MAAA;AAAA,EAC/B;AAGA,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,EAAA;AAEF,MAAI,iBAAiB,SAAS,GAAG;AAE/B,UAAM,oBAAoB,iBAAiB,eAAe,gBAAgB;AAC1E,UAAM,wBAAwB;AAAA,MAC5B;AAAA,MACA;AAAA,IAAA;AAIF,UAAM,uBAAuB;AAAA,MAC3B;AAAA,MACA;AAAA,IAAA;AAGF,QAAI,yBAAyB,MAAM;AAEjC,aAAO,kBAAkB,CAAC,GAAG,kBAAkB,oBAAoB,CAAC;AAAA,IACtE;AAAA,EACF;AAGA,MAAI,cAAc,SAAS,UAAU,kBAAkB,SAAS,QAAQ;AACtE,UAAM,SAAS,yBAAyB,eAAe,iBAAiB;AACxE,QAAI,WAAW,MAAM;AACnB,aAAO;AAAA,IACT;AAAA,EACF;AAGA,SAAO;AACT;AAKA,SAAS,yBACP,UACA,cACiC;AAEjC,QAAM,YACJ,uBAAuB,QAAQ,KAC/B,qBAAqB,QAAQ,KAC7B,eAAe,QAAQ;AACzB,QAAM,gBACJ,uBAAuB,YAAY,KACnC,qBAAqB,YAAY,KACjC,eAAe,YAAY;AAG7B,MACE,CAAC,aACD,CAAC,iBACD,CAAC,aAAa,UAAU,KAAK,cAAc,GAAG,GAC9C;AACA,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,SAAS,QAAQ,aAAa,SAAS,MAAM;AACxD,UAAM,cAAc;AACpB,UAAM,kBAAkB;AAGxB,UAAM,kBAAkB,YAAY,OAAO;AAAA,MACzC,CAAC,MACC,CAAC;AAAA,QACC,gBAAgB;AAAA,QAChB;AAAA,QACA,gBAAgB,gBAAgB;AAAA,QAChC,gBAAgB;AAAA,MAAA;AAAA,IAClB;AAGJ,QAAI,gBAAgB,WAAW,GAAG;AAChC,aAAO,EAAE,MAAM,OAAO,OAAO,MAAA;AAAA,IAC/B;AAEA,QAAI,gBAAgB,WAAW,GAAG;AAChC,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM,CAAC,UAAU,KAAK,EAAE,MAAM,OAAO,OAAO,gBAAgB,CAAC,EAAA,CAAG;AAAA,MAAA;AAAA,IAEpE;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,CAAC,UAAU,KAAK,EAAE,MAAM,OAAO,OAAO,gBAAA,CAAiB;AAAA,IAAA;AAAA,EAEjE;AAGA,MAAI,SAAS,SAAS,QAAQ,aAAa,SAAS,MAAM;AACxD,UAAM,cAAc;AACpB,UAAM,gBAAiB,cAA+C;AAEtE,UAAM,kBAAkB,YAAY,OAAO;AAAA,MACzC,CAAC,MAAM,CAAC,eAAe,GAAG,aAAa;AAAA,IAAA;AAGzC,QAAI,gBAAgB,WAAW,GAAG;AAChC,aAAO,EAAE,MAAM,OAAO,OAAO,MAAA;AAAA,IAC/B;AAEA,QAAI,gBAAgB,WAAW,GAAG;AAChC,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM,CAAC,UAAU,KAAK,EAAE,MAAM,OAAO,OAAO,gBAAgB,CAAC,EAAA,CAAG;AAAA,MAAA;AAAA,IAEpE;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,CAAC,UAAU,KAAK,EAAE,MAAM,OAAO,OAAO,gBAAA,CAAiB;AAAA,IAAA;AAAA,EAEjE;AAGA,MAAI,SAAS,SAAS,QAAQ,aAAa,SAAS,MAAM;AACxD,UAAM,YAAa,UAA2C;AAC9D,UAAM,gBAAiB,cAA+C;AAEtE,QAAI,eAAe,WAAW,aAAa,GAAG;AAC5C,aAAO,EAAE,MAAM,OAAO,OAAO,MAAA;AAAA,IAC/B;AAGA,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,uBAAuB,QAAQ;AAChD,QAAM,eAAe,uBAAuB,YAAY;AAExD,MACE,YACA,gBACA,aAAa,SAAS,KAAK,aAAa,GAAG,GAC3C;AAEA,UAAM,SAAS;AAAA,MACb;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,aAAa;AAAA,IAAA;AAEf,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAKA,SAAS,qBACP,UACA,WACA,cACA,eACiC;AACjC,QAAM,SAAS,SAAS;AACxB,QAAM,aAAa,aAAa;AAChC,QAAM,OAAO,uBAAuB,QAAQ,KAC1C,qBAAqB,QAAQ,GAAI;AAGnC,MAAI,WAAW,QAAQ,eAAe,MAAM;AAC1C,QAAI,YAAY,eAAe;AAE7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,QACnD;AAAA,MACF;AAAA,IAEJ;AAEA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,SAAS,eAAe,OAAO;AAC5C,QAAI,YAAY,eAAe;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,QACnD;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,QAAQ,eAAe,OAAO;AAC3C,QAAI,YAAY,eAAe;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,QACnD;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,SAAS,eAAe,MAAM;AAC3C,QAAI,aAAa,eAAe;AAC9B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,QACnD;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,QAAQ,eAAe,MAAM;AAC1C,QAAI,YAAY,eAAe;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,UAEnD;AAAA,QAAA;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,SAAS,eAAe,OAAO;AAC5C,QAAI,YAAY,eAAe;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,UAEnD;AAAA,QAAA;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,QAAQ,eAAe,OAAO;AAC3C,QAAI,YAAY,eAAe;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,UAEnD;AAAA,QAAA;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,SAAS,eAAe,MAAM;AAC3C,QAAI,aAAa,eAAe;AAC9B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,UAEnD;AAAA,QAAA;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAcO,SAAS,gBACd,QACA,UACS;AAET,MAAI,CAAC,UAAU,OAAO,WAAW,GAAG;AAClC,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,SAAS,SAAS,QAAQ;AACnC,WAAO;AAAA,EACT;AAEA,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,YAAY,OAAO,CAAC;AAC1B,UAAM,cAAc,SAAS,CAAC;AAG9B,QAAI,CAAC,oBAAoB,UAAU,YAAY,YAAY,UAAU,GAAG;AACtE,aAAO;AAAA,IACT;AAGA,QACE,CAAC;AAAA,MACC,UAAU;AAAA,MACV,YAAY;AAAA,IAAA,GAEd;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAeO,SAAS,cACd,QACA,UACS;AAET,MAAI,aAAa,QAAW;AAC1B,WAAO;AAAA,EACT;AAIA,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AAGA,SAAO,UAAU;AACnB;AAgBO,SAAS,kBACd,QACA,UACS;AAgBT,MAAI,SAAS,UAAU,QAAW;AAEhC,QAAI,CAAC,qBAAqB,OAAO,OAAO,SAAS,KAAK,GAAG;AACvD,aAAO;AAAA,IACT;AACA,WACE,gBAAgB,OAAO,SAAS,SAAS,OAAO,KAChD,cAAc,OAAO,OAAO,SAAS,KAAK;AAAA,EAE9C;AAGA,SACE,cAAc,OAAO,OAAO,SAAS,KAAK,KAC1C,gBAAgB,OAAO,SAAS,SAAS,OAAO,KAChD,cAAc,OAAO,OAAO,SAAS,KAAK;AAE9C;AAMA,SAAS,qBACP,GACA,GACS;AACT,MAAI,MAAM,UAAa,MAAM,QAAW;AACtC,WAAO;AAAA,EACT;AACA,MAAI,MAAM,UAAa,MAAM,QAAW;AACtC,WAAO;AAAA,EACT;AACA,SAAO,oBAAoB,GAAG,CAAC;AACjC;AAUA,SAAS,qBACP,YACA,YACiC;AACjC,QAAM,cAAc,qBAAqB,UAAU;AACnD,QAAM,cAAc,qBAAqB,UAAU;AAEnD,QAAM,SAA0C,CAAA;AAEhD,aAAW,SAAS,aAAa;AAC/B,eAAW,SAAS,aAAa;AAC/B,UAAI,oBAAoB,OAAO,KAAK,GAAG;AAErC,YAAI,CAAC,OAAO,KAAK,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC,GAAG;AACtD,iBAAO,KAAK,KAAK;AAAA,QACnB;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,qBACP,WACiC;AACjC,MAAI,UAAU,SAAS,UAAU,UAAU,SAAS,OAAO;AACzD,UAAM,aAA8C,CAAA;AACpD,eAAW,OAAO,UAAU,MAAM;AAChC,iBAAW,KAAK,GAAG,qBAAqB,GAA+B,CAAC;AAAA,IAC1E;AACA,WAAO;AAAA,EACT;AAEA,SAAO,CAAC,SAAS;AACnB;AAMA,SAAS,iBACP,WACA,oBACsC;AACtC,MAAI,UAAU,SAAS,UAAU,UAAU,SAAS,OAAO;AACzD,UAAM,gBAAgB,UAAU,KAAK;AAAA,MACnC,CAAC,QACC,CAAC,mBAAmB;AAAA,QAAK,CAAC,SACxB,oBAAoB,KAAiC,IAAI;AAAA,MAAA;AAAA,IAC3D;AAGJ,QAAI,cAAc,WAAW,GAAG;AAC9B,aAAO;AAAA,IACT,WAAW,cAAc,WAAW,GAAG;AACrC,aAAO,cAAc,CAAC;AAAA,IACxB,OAAO;AACL,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,MAAA;AAAA,IAEV;AAAA,EACF;AAGA,SAAO;AACT;AAMA,SAAS,kBACP,YAC0B;AAC1B,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,EAAE,MAAM,OAAO,OAAO,KAAA;AAAA,EAC/B,WAAW,WAAW,WAAW,GAAG;AAClC,WAAO,WAAW,CAAC;AAAA,EACrB,OAAO;AAEL,UAAM,sBAAuD,CAAA;AAE7D,eAAW,aAAa,YAAY;AAClC,UAAI,UAAU,SAAS,UAAU,UAAU,SAAS,OAAO;AAEzD,4BAAoB,KAAK,GAAG,UAAU,IAAI;AAAA,MAC5C,OAAO;AACL,4BAAoB,KAAK,SAAS;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,oBAAoB,WAAW,GAAG;AACpC,aAAO,oBAAoB,CAAC;AAAA,IAC9B,OAAO;AACL,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,MAAA;AAAA,IAEV;AAAA,EACF;AACF;AAKA,SAAS,0BACP,YACA,UACA,OACsC;AACtC,SAAO,WAAW,KAAK,CAAC,MAAM;AAC5B,QAAI,EAAE,SAAS,QAAQ;AACrB,YAAM,IAAI;AACV,YAAM,QAAQ,uBAAuB,CAAC;AACtC,aAAO,EAAE,SAAS,YAAY,SAAS,eAAe,MAAM,OAAO,KAAK;AAAA,IAC1E;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAEA,SAAS,oBAAoB,GAAoB,GAA6B;AAC5E,MAAI,EAAE,SAAS,EAAE,MAAM;AACrB,WAAO;AAAA,EACT;AAEA,MAAI,EAAE,SAAS,SAAS,EAAE,SAAS,OAAO;AACxC,WAAO,eAAe,EAAE,OAAO,EAAE,KAAK;AAAA,EACxC;AAEA,MAAI,EAAE,SAAS,SAAS,EAAE,SAAS,OAAO;AACxC,WAAO,aAAa,GAAG,CAAC;AAAA,EAC1B;AAEA,MAAI,EAAE,SAAS,UAAU,EAAE,SAAS,QAAQ;AAC1C,UAAM,QAAQ;AACd,UAAM,QAAQ;AACd,QAAI,MAAM,SAAS,MAAM,MAAM;AAC7B,aAAO;AAAA,IACT;AACA,QAAI,MAAM,KAAK,WAAW,MAAM,KAAK,QAAQ;AAC3C,aAAO;AAAA,IACT;AACA,WAAO,MAAM,KAAK;AAAA,MAAM,CAAC,KAAK,MAC5B,oBAAoB,KAAK,MAAM,KAAK,CAAC,CAAE;AAAA,IAAA;AAAA,EAE3C;AAEA,SAAO;AACT;AAEA,SAAS,eAAe,GAAQ,GAAiB;AAE/C,MAAI,MAAM,GAAG;AACX,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,MAAM,CAAC,KAAK,MAAM,CAAC,GAAG;AAC1E,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,QAAQ,aAAa,MAAM;AAC1C,WAAO,EAAE,cAAc,EAAE,QAAA;AAAA,EAC3B;AAKA,MACE,OAAO,MAAM,YACb,OAAO,MAAM,YACb,MAAM,QACN,MAAM,MACN;AACA,WAAO,MAAM;AAAA,EACf;AAEA,SAAO;AACT;AAEA,SAAS,aAAa,GAAY,GAAqB;AACrD,MAAI,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ;AACnC,WAAO;AAAA,EACT;AACA,SAAO,EAAE,KAAK,MAAM,CAAC,SAAS,MAAM,YAAY,EAAE,KAAK,CAAC,CAAC;AAC3D;AAMA,SAAS,YAAY,OAAqB;AACxC,SACE,UAAU,QACV,UAAU,UACV,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU;AAErB;AAKA,SAAS,iBAAiB,QAA6B;AACrD,SAAO,OAAO,MAAM,WAAW;AACjC;AAMA,SAAS,qBACP,OACA,OACA,cACA,sBACS;AAET,MAAI,cAAc;AAGhB,QAAI,wBAAwB,YAAY,KAAK,GAAG;AAC9C,aAAO,aAAa,IAAI,KAAK;AAAA,IAC/B;AACA,WAAO;AAAA,EACT;AAGA,SAAO,MAAM,KAAK,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AACnD;AAKA,SAAS,SAAS,GAAQ,GAAa;AACrC,MAAI,aAAa,QAAQ,aAAa,MAAM;AAC1C,WAAO,EAAE,QAAA,IAAY,EAAE,QAAA,IAAY,IAAI;AAAA,EACzC;AACA,SAAO,KAAK,IAAI,GAAG,CAAC;AACtB;AAKA,SAAS,SAAS,GAAQ,GAAa;AACrC,MAAI,aAAa,QAAQ,aAAa,MAAM;AAC1C,WAAO,EAAE,QAAA,IAAY,EAAE,QAAA,IAAY,IAAI;AAAA,EACzC;AACA,SAAO,KAAK,IAAI,GAAG,CAAC;AACtB;AAEA,SAAS,uBACP,GACA,GACS;AAET,SAAO,EAAE,cAAc,EAAE;AAC3B;AAOA,SAAS,uBAAuB,MAAoC;AAElE,MAAI,CAAC,MAAM,MAAM,OAAO,MAAM,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG;AAExD,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,KAAK,KAAK,CAAC;AAE7B,QAAI,UAAU,SAAS,SAAS,WAAW,SAAS,OAAO;AACzD,aAAO;AAAA,QACL,KAAK;AAAA,QACL,OAAO,UAAU;AAAA,MAAA;AAAA,IAErB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,qBAAqB,MAAoC;AAChE,MAAI,KAAK,SAAS,MAAM;AACtB,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,KAAK,KAAK,CAAC;AAE7B,QAAI,UAAU,SAAS,SAAS,WAAW,SAAS,OAAO;AACzD,aAAO;AAAA,QACL,KAAK;AAAA,QACL,OAAO,UAAU;AAAA,MAAA;AAAA,IAErB;AAAA,EACF;AACA,SAAO;AACT;AAUA,SAAS,eAAe,MAA4B;AAClD,MAAI,KAAK,SAAS,MAAM;AACtB,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,KAAK,KAAK,CAAC;AAE7B,QACE,UAAU,SAAS,SACnB,WAAW,SAAS,SACpB,MAAM,QAAQ,UAAU,KAAK,GAC7B;AACA,UAAI,SAAS,UAAU;AAEvB,YAAM,gBAAgB,iBAAiB,MAAM;AAC7C,UAAI,eAAgC;AAEpC,UAAI,iBAAiB,OAAO,SAAS,IAAI;AAEvC,uBAAe,IAAI,IAAI,MAAM;AAE7B,YAAI,aAAa,OAAO,OAAO,QAAQ;AACrC,mBAAS,MAAM,KAAK,YAAY;AAAA,QAClC;AAAA,MACF;AAEA,aAAO;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA,kBAAkB;AAAA,QAClB;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,mBACP,YACA,aACA,cACA,eACS;AACT,QAAM,QAAQ,WAAW;AACzB,QAAM,UAAU,aAAa;AAG7B,MAAI,UAAU,SAAS;AACrB,QAAI,UAAU,MAAM;AAGlB,UAAI,YAAY,WAAW,KAAK,YAAY,aAAa,GAAG;AAC1D,eAAO,gBAAgB;AAAA,MACzB;AACA,aAAO,eAAe,aAAa,aAAa;AAAA,IAClD,WAAW,UAAU,MAAM;AAEzB,aAAO,eAAe;AAAA,IACxB,WAAW,UAAU,OAAO;AAE1B,aAAO,eAAe;AAAA,IACxB,WAAW,UAAU,MAAM;AAEzB,aAAO,eAAe;AAAA,IACxB,WAAW,UAAU,OAAO;AAE1B,aAAO,eAAe;AAAA,IACxB;AAAA,EACF;AAIA,MAAI,UAAU,QAAQ,YAAY,MAAM;AACtC,WAAO,cAAc;AAAA,EACvB;AACA,MAAI,UAAU,QAAQ,YAAY,OAAO;AACvC,WAAO,eAAe;AAAA,EACxB;AACA,MAAI,UAAU,QAAQ,YAAY,MAAM;AACtC,WAAO,cAAc;AAAA,EACvB;AACA,MAAI,UAAU,QAAQ,YAAY,OAAO;AACvC,WAAO,eAAe;AAAA,EACxB;AAGA,MAAI,UAAU,QAAQ,YAAY,OAAO;AAEvC,WAAO,eAAe;AAAA,EACxB;AACA,MAAI,UAAU,SAAS,YAAY,MAAM;AAEvC,WAAO,cAAc;AAAA,EACvB;AAGA,MAAI,UAAU,QAAQ,YAAY,OAAO;AAEvC,WAAO,eAAe;AAAA,EACxB;AACA,MAAI,UAAU,SAAS,YAAY,MAAM;AAEvC,WAAO,cAAc;AAAA,EACvB;AAEA,SAAO;AACT;AAEA,SAAS,uBACP,YACqD;AACrD,QAAM,6BAAa,IAAA;AAEnB,aAAW,QAAQ,YAAY;AAC7B,QAAI,WAA0B;AAE9B,QAAI,KAAK,SAAS,QAAQ;AACxB,YAAM,OAAO;AACb,YAAM,QACJ,uBAAuB,IAAI,KAC3B,qBAAqB,IAAI,KACzB,eAAe,IAAI;AACrB,UAAI,OAAO;AACT,mBAAW,MAAM,IAAI,KAAK,KAAK,GAAG;AAAA,MACpC;AAAA,IACF;AAEA,UAAM,QAAQ,OAAO,IAAI,QAAQ,KAAK,CAAA;AACtC,UAAM,KAAK,IAAI;AACf,WAAO,IAAI,UAAU,KAAK;AAAA,EAC5B;AAEA,SAAO;AACT;AAEA,SAAS,yBACP,YACiC;AACjC,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,WAAW,CAAC;AAAA,EACrB;AAGA,MAAI,QAAuB;AAC3B,MAAI,SAAwB;AAC5B,MAAI,QAAuB;AAC3B,MAAI,SAAwB;AAC5B,QAAM,+BAAyB,IAAA;AAC/B,QAAM,+BAAyB,IAAA;AAC/B,QAAM,kBAAmD,CAAA;AAEzD,aAAW,QAAQ,YAAY;AAC7B,QAAI,KAAK,SAAS,QAAQ;AACxB,YAAM,OAAO;AACb,YAAM,QAAQ,uBAAuB,IAAI;AAEzC,UAAI,OAAO;AACT,cAAM,QAAQ,MAAM;AACpB,YAAI,KAAK,SAAS,MAAM;AACtB,kBAAQ,UAAU,OAAO,QAAQ,SAAS,OAAO,KAAK;AAAA,QACxD,WAAW,KAAK,SAAS,OAAO;AAC9B,mBAAS,WAAW,OAAO,QAAQ,SAAS,QAAQ,KAAK;AAAA,QAC3D,WAAW,KAAK,SAAS,MAAM;AAC7B,kBAAQ,UAAU,OAAO,QAAQ,SAAS,OAAO,KAAK;AAAA,QACxD,WAAW,KAAK,SAAS,OAAO;AAC9B,mBAAS,WAAW,OAAO,QAAQ,SAAS,QAAQ,KAAK;AAAA,QAC3D,WAAW,KAAK,SAAS,MAAM;AAC7B,mBAAS,IAAI,KAAK;AAAA,QACpB,OAAO;AACL,0BAAgB,KAAK,IAAI;AAAA,QAC3B;AAAA,MACF,OAAO;AACL,cAAM,UAAU,eAAe,IAAI;AACnC,YAAI,SAAS;AACX,qBAAW,OAAO,QAAQ,QAAQ;AAChC,qBAAS,IAAI,GAAG;AAAA,UAClB;AAAA,QACF,OAAO;AACL,0BAAgB,KAAK,IAAI;AAAA,QAC3B;AAAA,MACF;AAAA,IACF,OAAO;AACL,sBAAgB,KAAK,IAAI;AAAA,IAC3B;AAAA,EACF;AAGA,MAAI,SAAS,OAAO,KAAM,SAAS,OAAO,KAAK,SAAS,OAAO,GAAI;AACjE,UAAM,YAAY,CAAC,GAAG,UAAU,GAAG,QAAQ;AAC3C,UAAM,MAAM,WAAW,KAAK,CAAC,MAAM;AACjC,UAAI,EAAE,SAAS,QAAQ;AACrB,cAAM,QACJ,uBAAuB,CAAS,KAAK,eAAe,CAAS;AAC/D,eAAO,UAAU;AAAA,MACnB;AACA,aAAO;AAAA,IACT,CAAC;AAED,QAAI,OAAO,IAAI,SAAS,QAAQ;AAC9B,YAAM,QACJ,uBAAuB,GAAW,KAAK,eAAe,GAAW;AACnE,UAAI,OAAO;AACT,eAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,YACJ,MAAM;AAAA,YACN,EAAE,MAAM,OAAO,OAAO,UAAA;AAAA,UAAU;AAAA,QAClC;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAA0C,CAAA;AAGhD,MAAI,UAAU,QAAQ,WAAW,MAAM;AAErC,UAAM,OACJ,UAAU,QACN,0BAA0B,YAAY,OAAO,MAAM,IACnD,0BAA0B,YAAY,MAAM,KAAK;AACvD,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B,WAAW,UAAU,MAAM;AACzB,UAAM,OAAO,0BAA0B,YAAY,MAAM,KAAK;AAC9D,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B,WAAW,WAAW,MAAM;AAC1B,UAAM,OAAO,0BAA0B,YAAY,OAAO,MAAM;AAChE,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B;AAGA,MAAI,UAAU,QAAQ,WAAW,MAAM;AACrC,UAAM,OACJ,UAAU,QACN,0BAA0B,YAAY,OAAO,MAAM,IACnD,0BAA0B,YAAY,MAAM,KAAK;AACvD,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B,WAAW,UAAU,MAAM;AACzB,UAAM,OAAO,0BAA0B,YAAY,MAAM,KAAK;AAC9D,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B,WAAW,WAAW,MAAM;AAC1B,UAAM,OAAO,0BAA0B,YAAY,OAAO,MAAM;AAChE,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B;AAGA,MAAI,SAAS,SAAS,KAAK,SAAS,SAAS,GAAG;AAC9C,UAAM,OAAO,0BAA0B,YAAY,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC,CAAC;AACzE,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B;AAGA,MAAI,SAAS,SAAS,KAAK,SAAS,OAAO,GAAG;AAC5C,WAAO;AAAA,MACL,WAAW,KAAK,CAAC,MAAM;AACrB,YAAI,EAAE,SAAS,QAAQ;AACrB,iBAAQ,EAAW,SAAS;AAAA,QAC9B;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IAAA;AAAA,EAEL;AAGA,SAAO,KAAK,GAAG,eAAe;AAE9B,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,EAAE,MAAM,OAAO,OAAO,KAAA;AAAA,EAC/B;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,OAAO,CAAC;AAAA,EACjB;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,EAAA;AAEV;;;;;;;"}
1
+ {"version":3,"file":"predicate-utils.cjs","sources":["../../../src/query/predicate-utils.ts"],"sourcesContent":["import { Func, Value } from './ir.js'\nimport type { BasicExpression, OrderBy, PropRef } from './ir.js'\nimport type { LoadSubsetOptions } from '../types.js'\n\n/**\n * Check if one where clause is a logical subset of another.\n * Returns true if the subset predicate is more restrictive than (or equal to) the superset predicate.\n *\n * @example\n * // age > 20 is subset of age > 10 (more restrictive)\n * isWhereSubset(gt(ref('age'), val(20)), gt(ref('age'), val(10))) // true\n *\n * @example\n * // age > 10 AND name = 'X' is subset of age > 10 (more conditions)\n * isWhereSubset(and(gt(ref('age'), val(10)), eq(ref('name'), val('X'))), gt(ref('age'), val(10))) // true\n *\n * @param subset - The potentially more restrictive predicate\n * @param superset - The potentially less restrictive predicate\n * @returns true if subset logically implies superset\n */\nexport function isWhereSubset(\n subset: BasicExpression<boolean> | undefined,\n superset: BasicExpression<boolean> | undefined,\n): boolean {\n // undefined/missing where clause means \"no filter\" (all data)\n // Both undefined means subset relationship holds (all data ⊆ all data)\n if (subset === undefined && superset === undefined) {\n return true\n }\n\n // If subset is undefined but superset is not, we're requesting ALL data\n // but have only loaded SOME data - subset relationship does NOT hold\n if (subset === undefined && superset !== undefined) {\n return false\n }\n\n // If superset is undefined (no filter = all data loaded),\n // then any constrained subset is contained\n if (superset === undefined && subset !== undefined) {\n return true\n }\n\n return isWhereSubsetInternal(subset!, superset!)\n}\n\nfunction makeDisjunction(\n preds: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> {\n if (preds.length === 0) {\n return new Value(false)\n }\n if (preds.length === 1) {\n return preds[0]!\n }\n return new Func(`or`, preds)\n}\n\nfunction convertInToOr(inField: InField) {\n const equalities = inField.values.map(\n (value) => new Func(`eq`, [inField.ref, new Value(value)]),\n )\n return makeDisjunction(equalities)\n}\n\nfunction isWhereSubsetInternal(\n subset: BasicExpression<boolean>,\n superset: BasicExpression<boolean>,\n): boolean {\n // If subset is false it is requesting no data,\n // thus the result set is empty\n // and the empty set is a subset of any set\n if (subset.type === `val` && subset.value === false) {\n return true\n }\n\n // If expressions are structurally equal, subset relationship holds\n if (areExpressionsEqual(subset, superset)) {\n return true\n }\n\n // Handle superset being an AND: subset must imply ALL conjuncts\n // If superset is (A AND B), then subset ⊆ (A AND B) only if subset ⊆ A AND subset ⊆ B\n // Example: (age > 20) ⊆ (age > 10 AND status = 'active') is false (doesn't imply status condition)\n if (superset.type === `func` && superset.name === `and`) {\n return superset.args.every((arg) =>\n isWhereSubsetInternal(subset, arg as BasicExpression<boolean>),\n )\n }\n\n // Handle subset being an AND: (A AND B) implies both A and B\n if (subset.type === `func` && subset.name === `and`) {\n // For (A AND B) ⊆ C, since (A AND B) implies A, we check if any conjunct implies C\n return subset.args.some((arg) =>\n isWhereSubsetInternal(arg as BasicExpression<boolean>, superset),\n )\n }\n\n // Turn x IN [A, B, C] into x = A OR x = B OR x = C\n // for unified handling of IN and OR\n if (subset.type === `func` && subset.name === `in`) {\n const inField = extractInField(subset)\n if (inField) {\n return isWhereSubsetInternal(convertInToOr(inField), superset)\n }\n }\n\n if (superset.type === `func` && superset.name === `in`) {\n const inField = extractInField(superset)\n if (inField) {\n return isWhereSubsetInternal(subset, convertInToOr(inField))\n }\n }\n\n // Handle OR in subset: (A OR B) is subset of C only if both A and B are subsets of C\n if (subset.type === `func` && subset.name === `or`) {\n return subset.args.every((arg) =>\n isWhereSubsetInternal(arg as BasicExpression<boolean>, superset),\n )\n }\n\n // Handle OR in superset: subset ⊆ (A OR B) if subset ⊆ A or subset ⊆ B\n // (A OR B) as superset means data can satisfy A or B\n // If subset is contained in any disjunct, it's contained in the union\n if (superset.type === `func` && superset.name === `or`) {\n return superset.args.some((arg) =>\n isWhereSubsetInternal(subset, arg as BasicExpression<boolean>),\n )\n }\n\n // Handle comparison operators on the same field\n if (subset.type === `func` && superset.type === `func`) {\n const subsetFunc = subset as Func\n const supersetFunc = superset as Func\n\n // Check if both are comparisons on the same field\n const subsetField = extractComparisonField(subsetFunc)\n const supersetField = extractComparisonField(supersetFunc)\n\n if (\n subsetField &&\n supersetField &&\n areRefsEqual(subsetField.ref, supersetField.ref)\n ) {\n return isComparisonSubset(\n subsetFunc,\n subsetField.value,\n supersetFunc,\n supersetField.value,\n )\n }\n\n /*\n // Handle eq vs in\n if (subsetFunc.name === `eq` && supersetFunc.name === `in`) {\n const subsetFieldEq = extractEqualityField(subsetFunc)\n const supersetFieldIn = extractInField(supersetFunc)\n if (\n subsetFieldEq &&\n supersetFieldIn &&\n areRefsEqual(subsetFieldEq.ref, supersetFieldIn.ref)\n ) {\n // field = X is subset of field IN [X, Y, Z] if X is in the array\n // Use cached primitive set and metadata from extraction\n return arrayIncludesWithSet(\n supersetFieldIn.values,\n subsetFieldEq.value,\n supersetFieldIn.primitiveSet ?? null,\n supersetFieldIn.areAllPrimitives\n )\n }\n }\n\n // Handle in vs in\n if (subsetFunc.name === `in` && supersetFunc.name === `in`) {\n const subsetFieldIn = extractInField(subsetFunc)\n const supersetFieldIn = extractInField(supersetFunc)\n if (\n subsetFieldIn &&\n supersetFieldIn &&\n areRefsEqual(subsetFieldIn.ref, supersetFieldIn.ref)\n ) {\n // field IN [A, B] is subset of field IN [A, B, C] if all values in subset are in superset\n // Use cached primitive set and metadata from extraction\n return subsetFieldIn.values.every((subVal) =>\n arrayIncludesWithSet(\n supersetFieldIn.values,\n subVal,\n supersetFieldIn.primitiveSet ?? null,\n supersetFieldIn.areAllPrimitives\n )\n )\n }\n }\n */\n }\n\n // Conservative: if we can't determine, return false\n return false\n}\n\n/**\n * Helper to combine where predicates with common logic for AND/OR operations\n */\nfunction combineWherePredicates(\n predicates: Array<BasicExpression<boolean>>,\n operation: `and` | `or`,\n simplifyFn: (\n preds: Array<BasicExpression<boolean>>,\n ) => BasicExpression<boolean> | null,\n): BasicExpression<boolean> {\n const emptyValue = operation === `and` ? true : false\n const identityValue = operation === `and` ? true : false\n\n if (predicates.length === 0) {\n return { type: `val`, value: emptyValue } as BasicExpression<boolean>\n }\n\n if (predicates.length === 1) {\n return predicates[0]!\n }\n\n // Flatten nested expressions of the same operation\n const flatPredicates: Array<BasicExpression<boolean>> = []\n for (const pred of predicates) {\n if (pred.type === `func` && pred.name === operation) {\n flatPredicates.push(...pred.args)\n } else {\n flatPredicates.push(pred)\n }\n }\n\n // Group predicates by field for simplification\n const grouped = groupPredicatesByField(flatPredicates)\n\n // Simplify each group\n const simplified: Array<BasicExpression<boolean>> = []\n for (const [field, preds] of grouped.entries()) {\n if (field === null) {\n // Complex predicates that we can't group by field\n simplified.push(...preds)\n } else {\n // Try to simplify same-field predicates\n const result = simplifyFn(preds)\n\n // For intersection: check for empty set (contradiction)\n if (\n operation === `and` &&\n result &&\n result.type === `val` &&\n result.value === false\n ) {\n // Intersection is empty (conflicting constraints) - entire AND is false\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // For union: result may be null if simplification failed\n if (result) {\n simplified.push(result)\n }\n }\n }\n\n if (simplified.length === 0) {\n return { type: `val`, value: identityValue } as BasicExpression<boolean>\n }\n\n if (simplified.length === 1) {\n return simplified[0]!\n }\n\n // Return combined predicate\n return {\n type: `func`,\n name: operation,\n args: simplified,\n } as BasicExpression<boolean>\n}\n\n/**\n * Combine multiple where predicates with OR logic (union).\n * Returns a predicate that is satisfied when any input predicate is satisfied.\n * Simplifies when possible (e.g., age > 10 OR age > 20 → age > 10).\n *\n * @example\n * // Take least restrictive\n * unionWherePredicates([gt(ref('age'), val(10)), gt(ref('age'), val(20))]) // age > 10\n *\n * @example\n * // Combine equals into IN\n * unionWherePredicates([eq(ref('age'), val(5)), eq(ref('age'), val(10))]) // age IN [5, 10]\n *\n * @param predicates - Array of where predicates to union\n * @returns Combined predicate representing the union\n */\nexport function unionWherePredicates(\n predicates: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> {\n return combineWherePredicates(predicates, `or`, unionSameFieldPredicates)\n}\n\n/**\n * Compute the difference between two where predicates: `fromPredicate AND NOT(subtractPredicate)`.\n * Returns the simplified predicate, or null if the difference cannot be simplified\n * (in which case the caller should fetch the full fromPredicate).\n *\n * @example\n * // Range difference\n * minusWherePredicates(\n * gt(ref('age'), val(10)), // age > 10\n * gt(ref('age'), val(20)) // age > 20\n * ) // → age > 10 AND age <= 20\n *\n * @example\n * // Set difference\n * minusWherePredicates(\n * inOp(ref('status'), ['A', 'B', 'C', 'D']), // status IN ['A','B','C','D']\n * inOp(ref('status'), ['B', 'C']) // status IN ['B','C']\n * ) // → status IN ['A', 'D']\n *\n * @example\n * // Common conditions\n * minusWherePredicates(\n * and(gt(ref('age'), val(10)), eq(ref('status'), val('active'))), // age > 10 AND status = 'active'\n * and(gt(ref('age'), val(20)), eq(ref('status'), val('active'))) // age > 20 AND status = 'active'\n * ) // → age > 10 AND age <= 20 AND status = 'active'\n *\n * @example\n * // Complete overlap - empty result\n * minusWherePredicates(\n * gt(ref('age'), val(20)), // age > 20\n * gt(ref('age'), val(10)) // age > 10\n * ) // → {type: 'val', value: false} (empty set)\n *\n * @param fromPredicate - The predicate to subtract from\n * @param subtractPredicate - The predicate to subtract\n * @returns The simplified difference, or null if cannot be simplified\n */\nexport function minusWherePredicates(\n fromPredicate: BasicExpression<boolean> | undefined,\n subtractPredicate: BasicExpression<boolean> | undefined,\n): BasicExpression<boolean> | null {\n // If nothing to subtract, return the original\n if (subtractPredicate === undefined) {\n return (\n fromPredicate ??\n ({ type: `val`, value: true } as BasicExpression<boolean>)\n )\n }\n\n // If from is undefined then we are asking for all data\n // so we need to load all data minus what we already loaded\n // i.e. we need to load NOT(subtractPredicate)\n if (fromPredicate === undefined) {\n return {\n type: `func`,\n name: `not`,\n args: [subtractPredicate],\n } as BasicExpression<boolean>\n }\n\n // Check if fromPredicate is entirely contained in subtractPredicate\n // In that case, fromPredicate AND NOT(subtractPredicate) = empty set\n if (isWhereSubset(fromPredicate, subtractPredicate)) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // Try to detect and handle common conditions\n const commonConditions = findCommonConditions(\n fromPredicate,\n subtractPredicate,\n )\n if (commonConditions.length > 0) {\n // Extract predicates without common conditions\n const fromWithoutCommon = removeConditions(fromPredicate, commonConditions)\n const subtractWithoutCommon = removeConditions(\n subtractPredicate,\n commonConditions,\n )\n\n // Recursively compute difference on simplified predicates\n const simplifiedDifference = minusWherePredicates(\n fromWithoutCommon,\n subtractWithoutCommon,\n )\n\n if (simplifiedDifference !== null) {\n // Combine the simplified difference with common conditions\n return combineConditions([...commonConditions, simplifiedDifference])\n }\n }\n\n // Check if they are on the same field - if so, we can try to simplify\n if (fromPredicate.type === `func` && subtractPredicate.type === `func`) {\n const result = minusSameFieldPredicates(fromPredicate, subtractPredicate)\n if (result !== null) {\n return result\n }\n }\n\n // Can't simplify - return null to indicate caller should fetch full fromPredicate\n return null\n}\n\n/**\n * Helper function to compute difference for same-field predicates\n */\nfunction minusSameFieldPredicates(\n fromPred: Func,\n subtractPred: Func,\n): BasicExpression<boolean> | null {\n // Extract field information\n const fromField =\n extractComparisonField(fromPred) ||\n extractEqualityField(fromPred) ||\n extractInField(fromPred)\n const subtractField =\n extractComparisonField(subtractPred) ||\n extractEqualityField(subtractPred) ||\n extractInField(subtractPred)\n\n // Must be on the same field\n if (\n !fromField ||\n !subtractField ||\n !areRefsEqual(fromField.ref, subtractField.ref)\n ) {\n return null\n }\n\n // Handle IN minus IN: status IN [A,B,C,D] - status IN [B,C] = status IN [A,D]\n if (fromPred.name === `in` && subtractPred.name === `in`) {\n const fromInField = fromField as InField\n const subtractInField = subtractField as InField\n\n // Filter out values that are in the subtract set\n const remainingValues = fromInField.values.filter(\n (v) =>\n !arrayIncludesWithSet(\n subtractInField.values,\n v,\n subtractInField.primitiveSet ?? null,\n subtractInField.areAllPrimitives,\n ),\n )\n\n if (remainingValues.length === 0) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n if (remainingValues.length === 1) {\n return {\n type: `func`,\n name: `eq`,\n args: [fromField.ref, { type: `val`, value: remainingValues[0] }],\n } as BasicExpression<boolean>\n }\n\n return {\n type: `func`,\n name: `in`,\n args: [fromField.ref, { type: `val`, value: remainingValues }],\n } as BasicExpression<boolean>\n }\n\n // Handle IN minus equality: status IN [A,B,C] - status = B = status IN [A,C]\n if (fromPred.name === `in` && subtractPred.name === `eq`) {\n const fromInField = fromField as InField\n const subtractValue = (subtractField as { ref: PropRef; value: any }).value\n\n const remainingValues = fromInField.values.filter(\n (v) => !areValuesEqual(v, subtractValue),\n )\n\n if (remainingValues.length === 0) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n if (remainingValues.length === 1) {\n return {\n type: `func`,\n name: `eq`,\n args: [fromField.ref, { type: `val`, value: remainingValues[0] }],\n } as BasicExpression<boolean>\n }\n\n return {\n type: `func`,\n name: `in`,\n args: [fromField.ref, { type: `val`, value: remainingValues }],\n } as BasicExpression<boolean>\n }\n\n // Handle equality minus equality: age = 15 - age = 15 = empty, age = 15 - age = 20 = age = 15\n if (fromPred.name === `eq` && subtractPred.name === `eq`) {\n const fromValue = (fromField as { ref: PropRef; value: any }).value\n const subtractValue = (subtractField as { ref: PropRef; value: any }).value\n\n if (areValuesEqual(fromValue, subtractValue)) {\n return { type: `val`, value: false } as BasicExpression<boolean>\n }\n\n // No overlap - return original\n return fromPred as BasicExpression<boolean>\n }\n\n // Handle range minus range: age > 10 - age > 20 = age > 10 AND age <= 20\n const fromComp = extractComparisonField(fromPred)\n const subtractComp = extractComparisonField(subtractPred)\n\n if (\n fromComp &&\n subtractComp &&\n areRefsEqual(fromComp.ref, subtractComp.ref)\n ) {\n // Try to compute the difference using range logic\n const result = minusRangePredicates(\n fromPred,\n fromComp.value,\n subtractPred,\n subtractComp.value,\n )\n return result\n }\n\n // Can't simplify\n return null\n}\n\n/**\n * Helper to compute difference between range predicates\n */\nfunction minusRangePredicates(\n fromFunc: Func,\n fromValue: any,\n subtractFunc: Func,\n subtractValue: any,\n): BasicExpression<boolean> | null {\n const fromOp = fromFunc.name as `gt` | `gte` | `lt` | `lte` | `eq`\n const subtractOp = subtractFunc.name as `gt` | `gte` | `lt` | `lte` | `eq`\n const ref = (extractComparisonField(fromFunc) ||\n extractEqualityField(fromFunc))!.ref\n\n // age > 10 - age > 20 = (age > 10 AND age <= 20)\n if (fromOp === `gt` && subtractOp === `gt`) {\n if (fromValue < subtractValue) {\n // Result is: fromValue < field <= subtractValue\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n // fromValue >= subtractValue means no overlap\n return fromFunc as BasicExpression<boolean>\n }\n\n // age >= 10 - age >= 20 = (age >= 10 AND age < 20)\n if (fromOp === `gte` && subtractOp === `gte`) {\n if (fromValue < subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age > 10 - age >= 20 = (age > 10 AND age < 20)\n if (fromOp === `gt` && subtractOp === `gte`) {\n if (fromValue < subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age >= 10 - age > 20 = (age >= 10 AND age <= 20)\n if (fromOp === `gte` && subtractOp === `gt`) {\n if (fromValue <= subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n fromFunc as BasicExpression<boolean>,\n {\n type: `func`,\n name: `lte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age < 30 - age < 20 = (age >= 20 AND age < 30)\n if (fromOp === `lt` && subtractOp === `lt`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age <= 30 - age <= 20 = (age > 20 AND age <= 30)\n if (fromOp === `lte` && subtractOp === `lte`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age < 30 - age <= 20 = (age > 20 AND age < 30)\n if (fromOp === `lt` && subtractOp === `lte`) {\n if (fromValue > subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gt`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // age <= 30 - age < 20 = (age >= 20 AND age <= 30)\n if (fromOp === `lte` && subtractOp === `lt`) {\n if (fromValue >= subtractValue) {\n return {\n type: `func`,\n name: `and`,\n args: [\n {\n type: `func`,\n name: `gte`,\n args: [ref, { type: `val`, value: subtractValue }],\n } as BasicExpression<boolean>,\n fromFunc as BasicExpression<boolean>,\n ],\n } as BasicExpression<boolean>\n }\n return fromFunc as BasicExpression<boolean>\n }\n\n // Can't simplify other combinations\n return null\n}\n\n/**\n * Check if one orderBy clause is a subset of another.\n * Returns true if the subset ordering requirements are satisfied by the superset ordering.\n *\n * @example\n * // Subset is prefix of superset\n * isOrderBySubset([{expr: age, asc}], [{expr: age, asc}, {expr: name, desc}]) // true\n *\n * @param subset - The ordering requirements to check\n * @param superset - The ordering that might satisfy the requirements\n * @returns true if subset is satisfied by superset\n */\nexport function isOrderBySubset(\n subset: OrderBy | undefined,\n superset: OrderBy | undefined,\n): boolean {\n // No ordering requirement is always satisfied\n if (!subset || subset.length === 0) {\n return true\n }\n\n // If there's no superset ordering but subset requires ordering, not satisfied\n if (!superset || superset.length === 0) {\n return false\n }\n\n // Check if subset is a prefix of superset with matching expressions and compare options\n if (subset.length > superset.length) {\n return false\n }\n\n for (let i = 0; i < subset.length; i++) {\n const subClause = subset[i]!\n const superClause = superset[i]!\n\n // Check if expressions match\n if (!areExpressionsEqual(subClause.expression, superClause.expression)) {\n return false\n }\n\n // Check if compare options match\n if (\n !areCompareOptionsEqual(\n subClause.compareOptions,\n superClause.compareOptions,\n )\n ) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * Check if one limit is a subset of another.\n * Returns true if the subset limit requirements are satisfied by the superset limit.\n *\n * Note: This function does NOT consider offset. For offset-aware subset checking,\n * use `isOffsetLimitSubset` instead.\n *\n * @example\n * isLimitSubset(10, 20) // true (requesting 10 items when 20 are available)\n * isLimitSubset(20, 10) // false (requesting 20 items when only 10 are available)\n * isLimitSubset(10, undefined) // true (requesting 10 items when unlimited are available)\n *\n * @param subset - The limit requirement to check\n * @param superset - The limit that might satisfy the requirement\n * @returns true if subset is satisfied by superset\n */\nexport function isLimitSubset(\n subset: number | undefined,\n superset: number | undefined,\n): boolean {\n // Unlimited superset satisfies any limit requirement\n if (superset === undefined) {\n return true\n }\n\n // If requesting all data (no limit), we need unlimited data to satisfy it\n // But we know superset is not unlimited so we return false\n if (subset === undefined) {\n return false\n }\n\n // Otherwise, subset must be less than or equal to superset\n return subset <= superset\n}\n\n/**\n * Check if one offset+limit range is a subset of another.\n * Returns true if the subset range is fully contained within the superset range.\n *\n * A query with `{limit: 10, offset: 0}` loads rows [0, 10).\n * A query with `{limit: 10, offset: 20}` loads rows [20, 30).\n *\n * For subset to be satisfied by superset:\n * - Superset must start at or before subset (superset.offset <= subset.offset)\n * - Superset must end at or after subset (superset.offset + superset.limit >= subset.offset + subset.limit)\n *\n * @example\n * isOffsetLimitSubset({ offset: 0, limit: 5 }, { offset: 0, limit: 10 }) // true\n * isOffsetLimitSubset({ offset: 5, limit: 5 }, { offset: 0, limit: 10 }) // true (rows 5-9 within 0-9)\n * isOffsetLimitSubset({ offset: 5, limit: 10 }, { offset: 0, limit: 10 }) // false (rows 5-14 exceed 0-9)\n * isOffsetLimitSubset({ offset: 20, limit: 10 }, { offset: 0, limit: 10 }) // false (rows 20-29 outside 0-9)\n *\n * @param subset - The offset+limit requirements to check\n * @param superset - The offset+limit that might satisfy the requirements\n * @returns true if subset range is fully contained within superset range\n */\nexport function isOffsetLimitSubset(\n subset: { offset?: number; limit?: number },\n superset: { offset?: number; limit?: number },\n): boolean {\n const subsetOffset = subset.offset ?? 0\n const supersetOffset = superset.offset ?? 0\n\n // Superset must start at or before subset\n if (supersetOffset > subsetOffset) {\n return false\n }\n\n // If superset is unlimited, it covers everything from its offset onwards\n if (superset.limit === undefined) {\n return true\n }\n\n // If subset is unlimited but superset has a limit, subset can't be satisfied\n if (subset.limit === undefined) {\n return false\n }\n\n // Both have limits - check if subset range is within superset range\n const subsetEnd = subsetOffset + subset.limit\n const supersetEnd = supersetOffset + superset.limit\n\n return subsetEnd <= supersetEnd\n}\n\n/**\n * Check if one predicate (where + orderBy + limit + offset) is a subset of another.\n * Returns true if all aspects of the subset predicate are satisfied by the superset.\n *\n * @example\n * isPredicateSubset(\n * { where: gt(ref('age'), val(20)), limit: 10 },\n * { where: gt(ref('age'), val(10)), limit: 20 }\n * ) // true\n *\n * @param subset - The predicate requirements to check\n * @param superset - The predicate that might satisfy the requirements\n * @returns true if subset is satisfied by superset\n */\nexport function isPredicateSubset(\n subset: LoadSubsetOptions,\n superset: LoadSubsetOptions,\n): boolean {\n // When the superset has a limit, we can only determine subset relationship\n // if the where clauses are equal (not just subset relationship).\n //\n // This is because a limited query only loads a portion of the matching rows.\n // A more restrictive where clause might require rows outside that portion.\n //\n // Example: superset = {where: undefined, limit: 10, orderBy: desc}\n // subset = {where: LIKE 'search%', limit: 10, orderBy: desc}\n // The top 10 items matching 'search%' might include items outside the overall top 10.\n //\n // However, if the where clauses are equal, then the subset relationship can\n // be determined by orderBy, limit, and offset:\n // Example: superset = {where: status='active', limit: 10, offset: 0, orderBy: desc}\n // subset = {where: status='active', limit: 5, offset: 0, orderBy: desc}\n // The top 5 active items ARE contained in the top 10 active items.\n if (superset.limit !== undefined) {\n // For limited supersets, where clauses must be equal\n if (!areWhereClausesEqual(subset.where, superset.where)) {\n return false\n }\n return (\n isOrderBySubset(subset.orderBy, superset.orderBy) &&\n isOffsetLimitSubset(subset, superset)\n )\n }\n\n // For unlimited supersets, use the normal subset logic\n // Still need to consider offset - an unlimited query with offset only covers\n // rows from that offset onwards\n return (\n isWhereSubset(subset.where, superset.where) &&\n isOrderBySubset(subset.orderBy, superset.orderBy) &&\n isOffsetLimitSubset(subset, superset)\n )\n}\n\n/**\n * Check if two where clauses are structurally equal.\n * Used for limited query subset checks where subset relationship isn't sufficient.\n */\nfunction areWhereClausesEqual(\n a: BasicExpression<boolean> | undefined,\n b: BasicExpression<boolean> | undefined,\n): boolean {\n if (a === undefined && b === undefined) {\n return true\n }\n if (a === undefined || b === undefined) {\n return false\n }\n return areExpressionsEqual(a, b)\n}\n\n// ============================================================================\n// Helper functions\n// ============================================================================\n\n/**\n * Find common conditions between two predicates.\n * Returns an array of conditions that appear in both predicates.\n */\nfunction findCommonConditions(\n predicate1: BasicExpression<boolean>,\n predicate2: BasicExpression<boolean>,\n): Array<BasicExpression<boolean>> {\n const conditions1 = extractAllConditions(predicate1)\n const conditions2 = extractAllConditions(predicate2)\n\n const common: Array<BasicExpression<boolean>> = []\n\n for (const cond1 of conditions1) {\n for (const cond2 of conditions2) {\n if (areExpressionsEqual(cond1, cond2)) {\n // Avoid duplicates\n if (!common.some((c) => areExpressionsEqual(c, cond1))) {\n common.push(cond1)\n }\n break\n }\n }\n }\n\n return common\n}\n\n/**\n * Extract all individual conditions from a predicate, flattening AND operations.\n */\nfunction extractAllConditions(\n predicate: BasicExpression<boolean>,\n): Array<BasicExpression<boolean>> {\n if (predicate.type === `func` && predicate.name === `and`) {\n const conditions: Array<BasicExpression<boolean>> = []\n for (const arg of predicate.args) {\n conditions.push(...extractAllConditions(arg as BasicExpression<boolean>))\n }\n return conditions\n }\n\n return [predicate]\n}\n\n/**\n * Remove specified conditions from a predicate.\n * Returns the predicate with the specified conditions removed, or undefined if all conditions are removed.\n */\nfunction removeConditions(\n predicate: BasicExpression<boolean>,\n conditionsToRemove: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> | undefined {\n if (predicate.type === `func` && predicate.name === `and`) {\n const remainingArgs = predicate.args.filter(\n (arg) =>\n !conditionsToRemove.some((cond) =>\n areExpressionsEqual(arg as BasicExpression<boolean>, cond),\n ),\n )\n\n if (remainingArgs.length === 0) {\n return undefined\n } else if (remainingArgs.length === 1) {\n return remainingArgs[0]!\n } else {\n return {\n type: `func`,\n name: `and`,\n args: remainingArgs,\n } as BasicExpression<boolean>\n }\n }\n\n // For non-AND predicates, don't remove anything\n return predicate\n}\n\n/**\n * Combine multiple conditions into a single predicate using AND logic.\n * Flattens nested AND operations to avoid unnecessary nesting.\n */\nfunction combineConditions(\n conditions: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> {\n if (conditions.length === 0) {\n return { type: `val`, value: true } as BasicExpression<boolean>\n } else if (conditions.length === 1) {\n return conditions[0]!\n } else {\n // Flatten all conditions, including those that are already AND operations\n const flattenedConditions: Array<BasicExpression<boolean>> = []\n\n for (const condition of conditions) {\n if (condition.type === `func` && condition.name === `and`) {\n // Flatten nested AND operations\n flattenedConditions.push(...condition.args)\n } else {\n flattenedConditions.push(condition)\n }\n }\n\n if (flattenedConditions.length === 1) {\n return flattenedConditions[0]!\n } else {\n return {\n type: `func`,\n name: `and`,\n args: flattenedConditions,\n } as BasicExpression<boolean>\n }\n }\n}\n\n/**\n * Find a predicate with a specific operator and value\n */\nfunction findPredicateWithOperator(\n predicates: Array<BasicExpression<boolean>>,\n operator: string,\n value: any,\n): BasicExpression<boolean> | undefined {\n return predicates.find((p) => {\n if (p.type === `func`) {\n const f = p as Func\n const field = extractComparisonField(f)\n return f.name === operator && field && areValuesEqual(field.value, value)\n }\n return false\n })\n}\n\nfunction areExpressionsEqual(a: BasicExpression, b: BasicExpression): boolean {\n if (a.type !== b.type) {\n return false\n }\n\n if (a.type === `val` && b.type === `val`) {\n return areValuesEqual(a.value, b.value)\n }\n\n if (a.type === `ref` && b.type === `ref`) {\n return areRefsEqual(a, b)\n }\n\n if (a.type === `func` && b.type === `func`) {\n const aFunc = a\n const bFunc = b\n if (aFunc.name !== bFunc.name) {\n return false\n }\n if (aFunc.args.length !== bFunc.args.length) {\n return false\n }\n return aFunc.args.every((arg, i) =>\n areExpressionsEqual(arg, bFunc.args[i]!),\n )\n }\n\n return false\n}\n\nfunction areValuesEqual(a: any, b: any): boolean {\n // Simple equality check - could be enhanced for deep object comparison\n if (a === b) {\n return true\n }\n\n // Handle NaN\n if (typeof a === `number` && typeof b === `number` && isNaN(a) && isNaN(b)) {\n return true\n }\n\n // Handle Date objects\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n }\n\n // For arrays and objects, use reference equality\n // (In practice, we don't need deep equality for these cases -\n // same object reference means same value for our use case)\n if (\n typeof a === `object` &&\n typeof b === `object` &&\n a !== null &&\n b !== null\n ) {\n return a === b\n }\n\n return false\n}\n\nfunction areRefsEqual(a: PropRef, b: PropRef): boolean {\n if (a.path.length !== b.path.length) {\n return false\n }\n return a.path.every((segment, i) => segment === b.path[i])\n}\n\n/**\n * Check if a value is a primitive (string, number, boolean, null, undefined)\n * Primitives can use Set for fast lookups\n */\nfunction isPrimitive(value: any): boolean {\n return (\n value === null ||\n value === undefined ||\n typeof value === `string` ||\n typeof value === `number` ||\n typeof value === `boolean`\n )\n}\n\n/**\n * Check if all values in an array are primitives\n */\nfunction areAllPrimitives(values: Array<any>): boolean {\n return values.every(isPrimitive)\n}\n\n/**\n * Check if a value is in an array, with optional pre-built Set for optimization.\n * The primitiveSet is cached in InField during extraction and reused for all lookups.\n */\nfunction arrayIncludesWithSet(\n array: Array<any>,\n value: any,\n primitiveSet: Set<any> | null,\n arrayIsAllPrimitives?: boolean,\n): boolean {\n // Fast path: use pre-built Set for O(1) lookup\n if (primitiveSet) {\n // Skip isPrimitive check if we know the value must be primitive for a match\n // (if array is all primitives, only primitives can match)\n if (arrayIsAllPrimitives || isPrimitive(value)) {\n return primitiveSet.has(value)\n }\n return false // Non-primitive can't be in primitive-only set\n }\n\n // Fallback: use areValuesEqual for Dates and objects\n return array.some((v) => areValuesEqual(v, value))\n}\n\n/**\n * Get the maximum of two values, handling both numbers and Dates\n */\nfunction maxValue(a: any, b: any): any {\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() > b.getTime() ? a : b\n }\n return Math.max(a, b)\n}\n\n/**\n * Get the minimum of two values, handling both numbers and Dates\n */\nfunction minValue(a: any, b: any): any {\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() < b.getTime() ? a : b\n }\n return Math.min(a, b)\n}\n\nfunction areCompareOptionsEqual(\n a: { direction?: `asc` | `desc`; [key: string]: any },\n b: { direction?: `asc` | `desc`; [key: string]: any },\n): boolean {\n // For now, just compare direction - could be enhanced for other options\n return a.direction === b.direction\n}\n\ninterface ComparisonField {\n ref: PropRef\n value: any\n}\n\nfunction extractComparisonField(func: Func): ComparisonField | null {\n // Handle comparison operators: eq, gt, gte, lt, lte\n if ([`eq`, `gt`, `gte`, `lt`, `lte`].includes(func.name)) {\n // Assume first arg is ref, second is value\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (firstArg?.type === `ref` && secondArg?.type === `val`) {\n return {\n ref: firstArg,\n value: secondArg.value,\n }\n }\n }\n\n return null\n}\n\nfunction extractEqualityField(func: Func): ComparisonField | null {\n if (func.name === `eq`) {\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (firstArg?.type === `ref` && secondArg?.type === `val`) {\n return {\n ref: firstArg,\n value: secondArg.value,\n }\n }\n }\n return null\n}\n\ninterface InField {\n ref: PropRef\n values: Array<any>\n // Cached optimization data (computed once, reused many times)\n areAllPrimitives?: boolean\n primitiveSet?: Set<any> | null\n}\n\nfunction extractInField(func: Func): InField | null {\n if (func.name === `in`) {\n const firstArg = func.args[0]\n const secondArg = func.args[1]\n\n if (\n firstArg?.type === `ref` &&\n secondArg?.type === `val` &&\n Array.isArray(secondArg.value)\n ) {\n let values = secondArg.value\n // Precompute optimization metadata once\n const allPrimitives = areAllPrimitives(values)\n let primitiveSet: Set<any> | null = null\n\n if (allPrimitives && values.length > 10) {\n // Build Set and dedupe values at the same time\n primitiveSet = new Set(values)\n // If we found duplicates, use the deduped array going forward\n if (primitiveSet.size < values.length) {\n values = Array.from(primitiveSet)\n }\n }\n\n return {\n ref: firstArg,\n values,\n areAllPrimitives: allPrimitives,\n primitiveSet,\n }\n }\n }\n return null\n}\n\nfunction isComparisonSubset(\n subsetFunc: Func,\n subsetValue: any,\n supersetFunc: Func,\n supersetValue: any,\n): boolean {\n const subOp = subsetFunc.name\n const superOp = supersetFunc.name\n\n // Handle same operator\n if (subOp === superOp) {\n if (subOp === `eq`) {\n // field = X is subset of field = X only\n // Fast path: primitives can use strict equality\n if (isPrimitive(subsetValue) && isPrimitive(supersetValue)) {\n return subsetValue === supersetValue\n }\n return areValuesEqual(subsetValue, supersetValue)\n } else if (subOp === `gt`) {\n // field > 20 is subset of field > 10 if 20 > 10\n return subsetValue >= supersetValue\n } else if (subOp === `gte`) {\n // field >= 20 is subset of field >= 10 if 20 >= 10\n return subsetValue >= supersetValue\n } else if (subOp === `lt`) {\n // field < 10 is subset of field < 20 if 10 <= 20\n return subsetValue <= supersetValue\n } else if (subOp === `lte`) {\n // field <= 10 is subset of field <= 20 if 10 <= 20\n return subsetValue <= supersetValue\n }\n }\n\n // Handle different operators on same field\n // eq vs gt/gte: field = 15 is subset of field > 10 if 15 > 10\n if (subOp === `eq` && superOp === `gt`) {\n return subsetValue > supersetValue\n }\n if (subOp === `eq` && superOp === `gte`) {\n return subsetValue >= supersetValue\n }\n if (subOp === `eq` && superOp === `lt`) {\n return subsetValue < supersetValue\n }\n if (subOp === `eq` && superOp === `lte`) {\n return subsetValue <= supersetValue\n }\n\n // gt/gte vs gte/gt\n if (subOp === `gt` && superOp === `gte`) {\n // field > 10 is subset of field >= 10 if 10 >= 10 (always true for same value)\n return subsetValue >= supersetValue\n }\n if (subOp === `gte` && superOp === `gt`) {\n // field >= 11 is subset of field > 10 if 11 > 10\n return subsetValue > supersetValue\n }\n\n // lt/lte vs lte/lt\n if (subOp === `lt` && superOp === `lte`) {\n // field < 10 is subset of field <= 10 if 10 <= 10\n return subsetValue <= supersetValue\n }\n if (subOp === `lte` && superOp === `lt`) {\n // field <= 9 is subset of field < 10 if 9 < 10\n return subsetValue < supersetValue\n }\n\n return false\n}\n\nfunction groupPredicatesByField(\n predicates: Array<BasicExpression<boolean>>,\n): Map<string | null, Array<BasicExpression<boolean>>> {\n const groups = new Map<string | null, Array<BasicExpression<boolean>>>()\n\n for (const pred of predicates) {\n let fieldKey: string | null = null\n\n if (pred.type === `func`) {\n const func = pred as Func\n const field =\n extractComparisonField(func) ||\n extractEqualityField(func) ||\n extractInField(func)\n if (field) {\n fieldKey = field.ref.path.join(`.`)\n }\n }\n\n const group = groups.get(fieldKey) || []\n group.push(pred)\n groups.set(fieldKey, group)\n }\n\n return groups\n}\n\nfunction unionSameFieldPredicates(\n predicates: Array<BasicExpression<boolean>>,\n): BasicExpression<boolean> | null {\n if (predicates.length === 1) {\n return predicates[0]!\n }\n\n // Try to extract range constraints\n let maxGt: number | null = null\n let maxGte: number | null = null\n let minLt: number | null = null\n let minLte: number | null = null\n const eqValues: Set<any> = new Set()\n const inValues: Set<any> = new Set()\n const otherPredicates: Array<BasicExpression<boolean>> = []\n\n for (const pred of predicates) {\n if (pred.type === `func`) {\n const func = pred as Func\n const field = extractComparisonField(func)\n\n if (field) {\n const value = field.value\n if (func.name === `gt`) {\n maxGt = maxGt === null ? value : minValue(maxGt, value)\n } else if (func.name === `gte`) {\n maxGte = maxGte === null ? value : minValue(maxGte, value)\n } else if (func.name === `lt`) {\n minLt = minLt === null ? value : maxValue(minLt, value)\n } else if (func.name === `lte`) {\n minLte = minLte === null ? value : maxValue(minLte, value)\n } else if (func.name === `eq`) {\n eqValues.add(value)\n } else {\n otherPredicates.push(pred)\n }\n } else {\n const inField = extractInField(func)\n if (inField) {\n for (const val of inField.values) {\n inValues.add(val)\n }\n } else {\n otherPredicates.push(pred)\n }\n }\n } else {\n otherPredicates.push(pred)\n }\n }\n\n // If we have multiple equality values, combine into IN\n if (eqValues.size > 1 || (eqValues.size > 0 && inValues.size > 0)) {\n const allValues = [...eqValues, ...inValues]\n const ref = predicates.find((p) => {\n if (p.type === `func`) {\n const field =\n extractComparisonField(p as Func) || extractInField(p as Func)\n return field !== null\n }\n return false\n })\n\n if (ref && ref.type === `func`) {\n const field =\n extractComparisonField(ref as Func) || extractInField(ref as Func)\n if (field) {\n return {\n type: `func`,\n name: `in`,\n args: [\n field.ref,\n { type: `val`, value: allValues } as BasicExpression,\n ],\n } as BasicExpression<boolean>\n }\n }\n }\n\n // Build the least restrictive range\n const result: Array<BasicExpression<boolean>> = []\n\n // Choose the least restrictive lower bound\n if (maxGt !== null && maxGte !== null) {\n // Take the smaller one (less restrictive)\n const pred =\n maxGte <= maxGt\n ? findPredicateWithOperator(predicates, `gte`, maxGte)\n : findPredicateWithOperator(predicates, `gt`, maxGt)\n if (pred) result.push(pred)\n } else if (maxGt !== null) {\n const pred = findPredicateWithOperator(predicates, `gt`, maxGt)\n if (pred) result.push(pred)\n } else if (maxGte !== null) {\n const pred = findPredicateWithOperator(predicates, `gte`, maxGte)\n if (pred) result.push(pred)\n }\n\n // Choose the least restrictive upper bound\n if (minLt !== null && minLte !== null) {\n const pred =\n minLte >= minLt\n ? findPredicateWithOperator(predicates, `lte`, minLte)\n : findPredicateWithOperator(predicates, `lt`, minLt)\n if (pred) result.push(pred)\n } else if (minLt !== null) {\n const pred = findPredicateWithOperator(predicates, `lt`, minLt)\n if (pred) result.push(pred)\n } else if (minLte !== null) {\n const pred = findPredicateWithOperator(predicates, `lte`, minLte)\n if (pred) result.push(pred)\n }\n\n // Add single eq value\n if (eqValues.size === 1 && inValues.size === 0) {\n const pred = findPredicateWithOperator(predicates, `eq`, [...eqValues][0])\n if (pred) result.push(pred)\n }\n\n // Add IN if only IN values\n if (eqValues.size === 0 && inValues.size > 0) {\n result.push(\n predicates.find((p) => {\n if (p.type === `func`) {\n return (p as Func).name === `in`\n }\n return false\n })!,\n )\n }\n\n // Add other predicates\n result.push(...otherPredicates)\n\n if (result.length === 0) {\n return { type: `val`, value: true } as BasicExpression<boolean>\n }\n\n if (result.length === 1) {\n return result[0]!\n }\n\n return {\n type: `func`,\n name: `or`,\n args: result,\n } as BasicExpression<boolean>\n}\n"],"names":["Value","Func"],"mappings":";;;AAoBO,SAAS,cACd,QACA,UACS;AAGT,MAAI,WAAW,UAAa,aAAa,QAAW;AAClD,WAAO;AAAA,EACT;AAIA,MAAI,WAAW,UAAa,aAAa,QAAW;AAClD,WAAO;AAAA,EACT;AAIA,MAAI,aAAa,UAAa,WAAW,QAAW;AAClD,WAAO;AAAA,EACT;AAEA,SAAO,sBAAsB,QAAS,QAAS;AACjD;AAEA,SAAS,gBACP,OAC0B;AAC1B,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,IAAIA,GAAAA,MAAM,KAAK;AAAA,EACxB;AACA,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,MAAM,CAAC;AAAA,EAChB;AACA,SAAO,IAAIC,GAAAA,KAAK,MAAM,KAAK;AAC7B;AAEA,SAAS,cAAc,SAAkB;AACvC,QAAM,aAAa,QAAQ,OAAO;AAAA,IAChC,CAAC,UAAU,IAAIA,QAAK,MAAM,CAAC,QAAQ,KAAK,IAAID,SAAM,KAAK,CAAC,CAAC;AAAA,EAAA;AAE3D,SAAO,gBAAgB,UAAU;AACnC;AAEA,SAAS,sBACP,QACA,UACS;AAIT,MAAI,OAAO,SAAS,SAAS,OAAO,UAAU,OAAO;AACnD,WAAO;AAAA,EACT;AAGA,MAAI,oBAAoB,QAAQ,QAAQ,GAAG;AACzC,WAAO;AAAA,EACT;AAKA,MAAI,SAAS,SAAS,UAAU,SAAS,SAAS,OAAO;AACvD,WAAO,SAAS,KAAK;AAAA,MAAM,CAAC,QAC1B,sBAAsB,QAAQ,GAA+B;AAAA,IAAA;AAAA,EAEjE;AAGA,MAAI,OAAO,SAAS,UAAU,OAAO,SAAS,OAAO;AAEnD,WAAO,OAAO,KAAK;AAAA,MAAK,CAAC,QACvB,sBAAsB,KAAiC,QAAQ;AAAA,IAAA;AAAA,EAEnE;AAIA,MAAI,OAAO,SAAS,UAAU,OAAO,SAAS,MAAM;AAClD,UAAM,UAAU,eAAe,MAAM;AACrC,QAAI,SAAS;AACX,aAAO,sBAAsB,cAAc,OAAO,GAAG,QAAQ;AAAA,IAC/D;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,UAAU,SAAS,SAAS,MAAM;AACtD,UAAM,UAAU,eAAe,QAAQ;AACvC,QAAI,SAAS;AACX,aAAO,sBAAsB,QAAQ,cAAc,OAAO,CAAC;AAAA,IAC7D;AAAA,EACF;AAGA,MAAI,OAAO,SAAS,UAAU,OAAO,SAAS,MAAM;AAClD,WAAO,OAAO,KAAK;AAAA,MAAM,CAAC,QACxB,sBAAsB,KAAiC,QAAQ;AAAA,IAAA;AAAA,EAEnE;AAKA,MAAI,SAAS,SAAS,UAAU,SAAS,SAAS,MAAM;AACtD,WAAO,SAAS,KAAK;AAAA,MAAK,CAAC,QACzB,sBAAsB,QAAQ,GAA+B;AAAA,IAAA;AAAA,EAEjE;AAGA,MAAI,OAAO,SAAS,UAAU,SAAS,SAAS,QAAQ;AACtD,UAAM,aAAa;AACnB,UAAM,eAAe;AAGrB,UAAM,cAAc,uBAAuB,UAAU;AACrD,UAAM,gBAAgB,uBAAuB,YAAY;AAEzD,QACE,eACA,iBACA,aAAa,YAAY,KAAK,cAAc,GAAG,GAC/C;AACA,aAAO;AAAA,QACL;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,QACA,cAAc;AAAA,MAAA;AAAA,IAElB;AAAA,EA6CF;AAGA,SAAO;AACT;AAKA,SAAS,uBACP,YACA,WACA,YAG0B;AAC1B,QAAM,aAAa,cAAc,QAAQ,OAAO;AAChD,QAAM,gBAAgB,cAAc,QAAQ,OAAO;AAEnD,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,EAAE,MAAM,OAAO,OAAO,WAAA;AAAA,EAC/B;AAEA,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,WAAW,CAAC;AAAA,EACrB;AAGA,QAAM,iBAAkD,CAAA;AACxD,aAAW,QAAQ,YAAY;AAC7B,QAAI,KAAK,SAAS,UAAU,KAAK,SAAS,WAAW;AACnD,qBAAe,KAAK,GAAG,KAAK,IAAI;AAAA,IAClC,OAAO;AACL,qBAAe,KAAK,IAAI;AAAA,IAC1B;AAAA,EACF;AAGA,QAAM,UAAU,uBAAuB,cAAc;AAGrD,QAAM,aAA8C,CAAA;AACpD,aAAW,CAAC,OAAO,KAAK,KAAK,QAAQ,WAAW;AAC9C,QAAI,UAAU,MAAM;AAElB,iBAAW,KAAK,GAAG,KAAK;AAAA,IAC1B,OAAO;AAEL,YAAM,SAAS,WAAW,KAAK;AAc/B,UAAI,QAAQ;AACV,mBAAW,KAAK,MAAM;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,EAAE,MAAM,OAAO,OAAO,cAAA;AAAA,EAC/B;AAEA,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,WAAW,CAAC;AAAA,EACrB;AAGA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,EAAA;AAEV;AAkBO,SAAS,qBACd,YAC0B;AAC1B,SAAO,uBAAuB,YAAY,MAAM,wBAAwB;AAC1E;AAuCO,SAAS,qBACd,eACA,mBACiC;AAEjC,MAAI,sBAAsB,QAAW;AACnC,WACE,iBACC,EAAE,MAAM,OAAO,OAAO,KAAA;AAAA,EAE3B;AAKA,MAAI,kBAAkB,QAAW;AAC/B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,CAAC,iBAAiB;AAAA,IAAA;AAAA,EAE5B;AAIA,MAAI,cAAc,eAAe,iBAAiB,GAAG;AACnD,WAAO,EAAE,MAAM,OAAO,OAAO,MAAA;AAAA,EAC/B;AAGA,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,EAAA;AAEF,MAAI,iBAAiB,SAAS,GAAG;AAE/B,UAAM,oBAAoB,iBAAiB,eAAe,gBAAgB;AAC1E,UAAM,wBAAwB;AAAA,MAC5B;AAAA,MACA;AAAA,IAAA;AAIF,UAAM,uBAAuB;AAAA,MAC3B;AAAA,MACA;AAAA,IAAA;AAGF,QAAI,yBAAyB,MAAM;AAEjC,aAAO,kBAAkB,CAAC,GAAG,kBAAkB,oBAAoB,CAAC;AAAA,IACtE;AAAA,EACF;AAGA,MAAI,cAAc,SAAS,UAAU,kBAAkB,SAAS,QAAQ;AACtE,UAAM,SAAS,yBAAyB,eAAe,iBAAiB;AACxE,QAAI,WAAW,MAAM;AACnB,aAAO;AAAA,IACT;AAAA,EACF;AAGA,SAAO;AACT;AAKA,SAAS,yBACP,UACA,cACiC;AAEjC,QAAM,YACJ,uBAAuB,QAAQ,KAC/B,qBAAqB,QAAQ,KAC7B,eAAe,QAAQ;AACzB,QAAM,gBACJ,uBAAuB,YAAY,KACnC,qBAAqB,YAAY,KACjC,eAAe,YAAY;AAG7B,MACE,CAAC,aACD,CAAC,iBACD,CAAC,aAAa,UAAU,KAAK,cAAc,GAAG,GAC9C;AACA,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,SAAS,QAAQ,aAAa,SAAS,MAAM;AACxD,UAAM,cAAc;AACpB,UAAM,kBAAkB;AAGxB,UAAM,kBAAkB,YAAY,OAAO;AAAA,MACzC,CAAC,MACC,CAAC;AAAA,QACC,gBAAgB;AAAA,QAChB;AAAA,QACA,gBAAgB,gBAAgB;AAAA,QAChC,gBAAgB;AAAA,MAAA;AAAA,IAClB;AAGJ,QAAI,gBAAgB,WAAW,GAAG;AAChC,aAAO,EAAE,MAAM,OAAO,OAAO,MAAA;AAAA,IAC/B;AAEA,QAAI,gBAAgB,WAAW,GAAG;AAChC,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM,CAAC,UAAU,KAAK,EAAE,MAAM,OAAO,OAAO,gBAAgB,CAAC,EAAA,CAAG;AAAA,MAAA;AAAA,IAEpE;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,CAAC,UAAU,KAAK,EAAE,MAAM,OAAO,OAAO,gBAAA,CAAiB;AAAA,IAAA;AAAA,EAEjE;AAGA,MAAI,SAAS,SAAS,QAAQ,aAAa,SAAS,MAAM;AACxD,UAAM,cAAc;AACpB,UAAM,gBAAiB,cAA+C;AAEtE,UAAM,kBAAkB,YAAY,OAAO;AAAA,MACzC,CAAC,MAAM,CAAC,eAAe,GAAG,aAAa;AAAA,IAAA;AAGzC,QAAI,gBAAgB,WAAW,GAAG;AAChC,aAAO,EAAE,MAAM,OAAO,OAAO,MAAA;AAAA,IAC/B;AAEA,QAAI,gBAAgB,WAAW,GAAG;AAChC,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM,CAAC,UAAU,KAAK,EAAE,MAAM,OAAO,OAAO,gBAAgB,CAAC,EAAA,CAAG;AAAA,MAAA;AAAA,IAEpE;AAEA,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,CAAC,UAAU,KAAK,EAAE,MAAM,OAAO,OAAO,gBAAA,CAAiB;AAAA,IAAA;AAAA,EAEjE;AAGA,MAAI,SAAS,SAAS,QAAQ,aAAa,SAAS,MAAM;AACxD,UAAM,YAAa,UAA2C;AAC9D,UAAM,gBAAiB,cAA+C;AAEtE,QAAI,eAAe,WAAW,aAAa,GAAG;AAC5C,aAAO,EAAE,MAAM,OAAO,OAAO,MAAA;AAAA,IAC/B;AAGA,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,uBAAuB,QAAQ;AAChD,QAAM,eAAe,uBAAuB,YAAY;AAExD,MACE,YACA,gBACA,aAAa,SAAS,KAAK,aAAa,GAAG,GAC3C;AAEA,UAAM,SAAS;AAAA,MACb;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,aAAa;AAAA,IAAA;AAEf,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAKA,SAAS,qBACP,UACA,WACA,cACA,eACiC;AACjC,QAAM,SAAS,SAAS;AACxB,QAAM,aAAa,aAAa;AAChC,QAAM,OAAO,uBAAuB,QAAQ,KAC1C,qBAAqB,QAAQ,GAAI;AAGnC,MAAI,WAAW,QAAQ,eAAe,MAAM;AAC1C,QAAI,YAAY,eAAe;AAE7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,QACnD;AAAA,MACF;AAAA,IAEJ;AAEA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,SAAS,eAAe,OAAO;AAC5C,QAAI,YAAY,eAAe;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,QACnD;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,QAAQ,eAAe,OAAO;AAC3C,QAAI,YAAY,eAAe;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,QACnD;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,SAAS,eAAe,MAAM;AAC3C,QAAI,aAAa,eAAe;AAC9B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,QACnD;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,QAAQ,eAAe,MAAM;AAC1C,QAAI,YAAY,eAAe;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,UAEnD;AAAA,QAAA;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,SAAS,eAAe,OAAO;AAC5C,QAAI,YAAY,eAAe;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,UAEnD;AAAA,QAAA;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,QAAQ,eAAe,OAAO;AAC3C,QAAI,YAAY,eAAe;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,UAEnD;AAAA,QAAA;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,MAAI,WAAW,SAAS,eAAe,MAAM;AAC3C,QAAI,aAAa,eAAe;AAC9B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,UACJ;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,YACN,MAAM,CAAC,KAAK,EAAE,MAAM,OAAO,OAAO,eAAe;AAAA,UAAA;AAAA,UAEnD;AAAA,QAAA;AAAA,MACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAcO,SAAS,gBACd,QACA,UACS;AAET,MAAI,CAAC,UAAU,OAAO,WAAW,GAAG;AAClC,WAAO;AAAA,EACT;AAGA,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,SAAS,SAAS,QAAQ;AACnC,WAAO;AAAA,EACT;AAEA,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,YAAY,OAAO,CAAC;AAC1B,UAAM,cAAc,SAAS,CAAC;AAG9B,QAAI,CAAC,oBAAoB,UAAU,YAAY,YAAY,UAAU,GAAG;AACtE,aAAO;AAAA,IACT;AAGA,QACE,CAAC;AAAA,MACC,UAAU;AAAA,MACV,YAAY;AAAA,IAAA,GAEd;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAkBO,SAAS,cACd,QACA,UACS;AAET,MAAI,aAAa,QAAW;AAC1B,WAAO;AAAA,EACT;AAIA,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AAGA,SAAO,UAAU;AACnB;AAuBO,SAAS,oBACd,QACA,UACS;AACT,QAAM,eAAe,OAAO,UAAU;AACtC,QAAM,iBAAiB,SAAS,UAAU;AAG1C,MAAI,iBAAiB,cAAc;AACjC,WAAO;AAAA,EACT;AAGA,MAAI,SAAS,UAAU,QAAW;AAChC,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,UAAU,QAAW;AAC9B,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,eAAe,OAAO;AACxC,QAAM,cAAc,iBAAiB,SAAS;AAE9C,SAAO,aAAa;AACtB;AAgBO,SAAS,kBACd,QACA,UACS;AAgBT,MAAI,SAAS,UAAU,QAAW;AAEhC,QAAI,CAAC,qBAAqB,OAAO,OAAO,SAAS,KAAK,GAAG;AACvD,aAAO;AAAA,IACT;AACA,WACE,gBAAgB,OAAO,SAAS,SAAS,OAAO,KAChD,oBAAoB,QAAQ,QAAQ;AAAA,EAExC;AAKA,SACE,cAAc,OAAO,OAAO,SAAS,KAAK,KAC1C,gBAAgB,OAAO,SAAS,SAAS,OAAO,KAChD,oBAAoB,QAAQ,QAAQ;AAExC;AAMA,SAAS,qBACP,GACA,GACS;AACT,MAAI,MAAM,UAAa,MAAM,QAAW;AACtC,WAAO;AAAA,EACT;AACA,MAAI,MAAM,UAAa,MAAM,QAAW;AACtC,WAAO;AAAA,EACT;AACA,SAAO,oBAAoB,GAAG,CAAC;AACjC;AAUA,SAAS,qBACP,YACA,YACiC;AACjC,QAAM,cAAc,qBAAqB,UAAU;AACnD,QAAM,cAAc,qBAAqB,UAAU;AAEnD,QAAM,SAA0C,CAAA;AAEhD,aAAW,SAAS,aAAa;AAC/B,eAAW,SAAS,aAAa;AAC/B,UAAI,oBAAoB,OAAO,KAAK,GAAG;AAErC,YAAI,CAAC,OAAO,KAAK,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC,GAAG;AACtD,iBAAO,KAAK,KAAK;AAAA,QACnB;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,qBACP,WACiC;AACjC,MAAI,UAAU,SAAS,UAAU,UAAU,SAAS,OAAO;AACzD,UAAM,aAA8C,CAAA;AACpD,eAAW,OAAO,UAAU,MAAM;AAChC,iBAAW,KAAK,GAAG,qBAAqB,GAA+B,CAAC;AAAA,IAC1E;AACA,WAAO;AAAA,EACT;AAEA,SAAO,CAAC,SAAS;AACnB;AAMA,SAAS,iBACP,WACA,oBACsC;AACtC,MAAI,UAAU,SAAS,UAAU,UAAU,SAAS,OAAO;AACzD,UAAM,gBAAgB,UAAU,KAAK;AAAA,MACnC,CAAC,QACC,CAAC,mBAAmB;AAAA,QAAK,CAAC,SACxB,oBAAoB,KAAiC,IAAI;AAAA,MAAA;AAAA,IAC3D;AAGJ,QAAI,cAAc,WAAW,GAAG;AAC9B,aAAO;AAAA,IACT,WAAW,cAAc,WAAW,GAAG;AACrC,aAAO,cAAc,CAAC;AAAA,IACxB,OAAO;AACL,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,MAAA;AAAA,IAEV;AAAA,EACF;AAGA,SAAO;AACT;AAMA,SAAS,kBACP,YAC0B;AAC1B,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,EAAE,MAAM,OAAO,OAAO,KAAA;AAAA,EAC/B,WAAW,WAAW,WAAW,GAAG;AAClC,WAAO,WAAW,CAAC;AAAA,EACrB,OAAO;AAEL,UAAM,sBAAuD,CAAA;AAE7D,eAAW,aAAa,YAAY;AAClC,UAAI,UAAU,SAAS,UAAU,UAAU,SAAS,OAAO;AAEzD,4BAAoB,KAAK,GAAG,UAAU,IAAI;AAAA,MAC5C,OAAO;AACL,4BAAoB,KAAK,SAAS;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,oBAAoB,WAAW,GAAG;AACpC,aAAO,oBAAoB,CAAC;AAAA,IAC9B,OAAO;AACL,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,MAAA;AAAA,IAEV;AAAA,EACF;AACF;AAKA,SAAS,0BACP,YACA,UACA,OACsC;AACtC,SAAO,WAAW,KAAK,CAAC,MAAM;AAC5B,QAAI,EAAE,SAAS,QAAQ;AACrB,YAAM,IAAI;AACV,YAAM,QAAQ,uBAAuB,CAAC;AACtC,aAAO,EAAE,SAAS,YAAY,SAAS,eAAe,MAAM,OAAO,KAAK;AAAA,IAC1E;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAEA,SAAS,oBAAoB,GAAoB,GAA6B;AAC5E,MAAI,EAAE,SAAS,EAAE,MAAM;AACrB,WAAO;AAAA,EACT;AAEA,MAAI,EAAE,SAAS,SAAS,EAAE,SAAS,OAAO;AACxC,WAAO,eAAe,EAAE,OAAO,EAAE,KAAK;AAAA,EACxC;AAEA,MAAI,EAAE,SAAS,SAAS,EAAE,SAAS,OAAO;AACxC,WAAO,aAAa,GAAG,CAAC;AAAA,EAC1B;AAEA,MAAI,EAAE,SAAS,UAAU,EAAE,SAAS,QAAQ;AAC1C,UAAM,QAAQ;AACd,UAAM,QAAQ;AACd,QAAI,MAAM,SAAS,MAAM,MAAM;AAC7B,aAAO;AAAA,IACT;AACA,QAAI,MAAM,KAAK,WAAW,MAAM,KAAK,QAAQ;AAC3C,aAAO;AAAA,IACT;AACA,WAAO,MAAM,KAAK;AAAA,MAAM,CAAC,KAAK,MAC5B,oBAAoB,KAAK,MAAM,KAAK,CAAC,CAAE;AAAA,IAAA;AAAA,EAE3C;AAEA,SAAO;AACT;AAEA,SAAS,eAAe,GAAQ,GAAiB;AAE/C,MAAI,MAAM,GAAG;AACX,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,YAAY,MAAM,CAAC,KAAK,MAAM,CAAC,GAAG;AAC1E,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,QAAQ,aAAa,MAAM;AAC1C,WAAO,EAAE,cAAc,EAAE,QAAA;AAAA,EAC3B;AAKA,MACE,OAAO,MAAM,YACb,OAAO,MAAM,YACb,MAAM,QACN,MAAM,MACN;AACA,WAAO,MAAM;AAAA,EACf;AAEA,SAAO;AACT;AAEA,SAAS,aAAa,GAAY,GAAqB;AACrD,MAAI,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ;AACnC,WAAO;AAAA,EACT;AACA,SAAO,EAAE,KAAK,MAAM,CAAC,SAAS,MAAM,YAAY,EAAE,KAAK,CAAC,CAAC;AAC3D;AAMA,SAAS,YAAY,OAAqB;AACxC,SACE,UAAU,QACV,UAAU,UACV,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU;AAErB;AAKA,SAAS,iBAAiB,QAA6B;AACrD,SAAO,OAAO,MAAM,WAAW;AACjC;AAMA,SAAS,qBACP,OACA,OACA,cACA,sBACS;AAET,MAAI,cAAc;AAGhB,QAAI,wBAAwB,YAAY,KAAK,GAAG;AAC9C,aAAO,aAAa,IAAI,KAAK;AAAA,IAC/B;AACA,WAAO;AAAA,EACT;AAGA,SAAO,MAAM,KAAK,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AACnD;AAKA,SAAS,SAAS,GAAQ,GAAa;AACrC,MAAI,aAAa,QAAQ,aAAa,MAAM;AAC1C,WAAO,EAAE,QAAA,IAAY,EAAE,QAAA,IAAY,IAAI;AAAA,EACzC;AACA,SAAO,KAAK,IAAI,GAAG,CAAC;AACtB;AAKA,SAAS,SAAS,GAAQ,GAAa;AACrC,MAAI,aAAa,QAAQ,aAAa,MAAM;AAC1C,WAAO,EAAE,QAAA,IAAY,EAAE,QAAA,IAAY,IAAI;AAAA,EACzC;AACA,SAAO,KAAK,IAAI,GAAG,CAAC;AACtB;AAEA,SAAS,uBACP,GACA,GACS;AAET,SAAO,EAAE,cAAc,EAAE;AAC3B;AAOA,SAAS,uBAAuB,MAAoC;AAElE,MAAI,CAAC,MAAM,MAAM,OAAO,MAAM,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG;AAExD,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,KAAK,KAAK,CAAC;AAE7B,QAAI,UAAU,SAAS,SAAS,WAAW,SAAS,OAAO;AACzD,aAAO;AAAA,QACL,KAAK;AAAA,QACL,OAAO,UAAU;AAAA,MAAA;AAAA,IAErB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,qBAAqB,MAAoC;AAChE,MAAI,KAAK,SAAS,MAAM;AACtB,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,KAAK,KAAK,CAAC;AAE7B,QAAI,UAAU,SAAS,SAAS,WAAW,SAAS,OAAO;AACzD,aAAO;AAAA,QACL,KAAK;AAAA,QACL,OAAO,UAAU;AAAA,MAAA;AAAA,IAErB;AAAA,EACF;AACA,SAAO;AACT;AAUA,SAAS,eAAe,MAA4B;AAClD,MAAI,KAAK,SAAS,MAAM;AACtB,UAAM,WAAW,KAAK,KAAK,CAAC;AAC5B,UAAM,YAAY,KAAK,KAAK,CAAC;AAE7B,QACE,UAAU,SAAS,SACnB,WAAW,SAAS,SACpB,MAAM,QAAQ,UAAU,KAAK,GAC7B;AACA,UAAI,SAAS,UAAU;AAEvB,YAAM,gBAAgB,iBAAiB,MAAM;AAC7C,UAAI,eAAgC;AAEpC,UAAI,iBAAiB,OAAO,SAAS,IAAI;AAEvC,uBAAe,IAAI,IAAI,MAAM;AAE7B,YAAI,aAAa,OAAO,OAAO,QAAQ;AACrC,mBAAS,MAAM,KAAK,YAAY;AAAA,QAClC;AAAA,MACF;AAEA,aAAO;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA,kBAAkB;AAAA,QAClB;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,mBACP,YACA,aACA,cACA,eACS;AACT,QAAM,QAAQ,WAAW;AACzB,QAAM,UAAU,aAAa;AAG7B,MAAI,UAAU,SAAS;AACrB,QAAI,UAAU,MAAM;AAGlB,UAAI,YAAY,WAAW,KAAK,YAAY,aAAa,GAAG;AAC1D,eAAO,gBAAgB;AAAA,MACzB;AACA,aAAO,eAAe,aAAa,aAAa;AAAA,IAClD,WAAW,UAAU,MAAM;AAEzB,aAAO,eAAe;AAAA,IACxB,WAAW,UAAU,OAAO;AAE1B,aAAO,eAAe;AAAA,IACxB,WAAW,UAAU,MAAM;AAEzB,aAAO,eAAe;AAAA,IACxB,WAAW,UAAU,OAAO;AAE1B,aAAO,eAAe;AAAA,IACxB;AAAA,EACF;AAIA,MAAI,UAAU,QAAQ,YAAY,MAAM;AACtC,WAAO,cAAc;AAAA,EACvB;AACA,MAAI,UAAU,QAAQ,YAAY,OAAO;AACvC,WAAO,eAAe;AAAA,EACxB;AACA,MAAI,UAAU,QAAQ,YAAY,MAAM;AACtC,WAAO,cAAc;AAAA,EACvB;AACA,MAAI,UAAU,QAAQ,YAAY,OAAO;AACvC,WAAO,eAAe;AAAA,EACxB;AAGA,MAAI,UAAU,QAAQ,YAAY,OAAO;AAEvC,WAAO,eAAe;AAAA,EACxB;AACA,MAAI,UAAU,SAAS,YAAY,MAAM;AAEvC,WAAO,cAAc;AAAA,EACvB;AAGA,MAAI,UAAU,QAAQ,YAAY,OAAO;AAEvC,WAAO,eAAe;AAAA,EACxB;AACA,MAAI,UAAU,SAAS,YAAY,MAAM;AAEvC,WAAO,cAAc;AAAA,EACvB;AAEA,SAAO;AACT;AAEA,SAAS,uBACP,YACqD;AACrD,QAAM,6BAAa,IAAA;AAEnB,aAAW,QAAQ,YAAY;AAC7B,QAAI,WAA0B;AAE9B,QAAI,KAAK,SAAS,QAAQ;AACxB,YAAM,OAAO;AACb,YAAM,QACJ,uBAAuB,IAAI,KAC3B,qBAAqB,IAAI,KACzB,eAAe,IAAI;AACrB,UAAI,OAAO;AACT,mBAAW,MAAM,IAAI,KAAK,KAAK,GAAG;AAAA,MACpC;AAAA,IACF;AAEA,UAAM,QAAQ,OAAO,IAAI,QAAQ,KAAK,CAAA;AACtC,UAAM,KAAK,IAAI;AACf,WAAO,IAAI,UAAU,KAAK;AAAA,EAC5B;AAEA,SAAO;AACT;AAEA,SAAS,yBACP,YACiC;AACjC,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,WAAW,CAAC;AAAA,EACrB;AAGA,MAAI,QAAuB;AAC3B,MAAI,SAAwB;AAC5B,MAAI,QAAuB;AAC3B,MAAI,SAAwB;AAC5B,QAAM,+BAAyB,IAAA;AAC/B,QAAM,+BAAyB,IAAA;AAC/B,QAAM,kBAAmD,CAAA;AAEzD,aAAW,QAAQ,YAAY;AAC7B,QAAI,KAAK,SAAS,QAAQ;AACxB,YAAM,OAAO;AACb,YAAM,QAAQ,uBAAuB,IAAI;AAEzC,UAAI,OAAO;AACT,cAAM,QAAQ,MAAM;AACpB,YAAI,KAAK,SAAS,MAAM;AACtB,kBAAQ,UAAU,OAAO,QAAQ,SAAS,OAAO,KAAK;AAAA,QACxD,WAAW,KAAK,SAAS,OAAO;AAC9B,mBAAS,WAAW,OAAO,QAAQ,SAAS,QAAQ,KAAK;AAAA,QAC3D,WAAW,KAAK,SAAS,MAAM;AAC7B,kBAAQ,UAAU,OAAO,QAAQ,SAAS,OAAO,KAAK;AAAA,QACxD,WAAW,KAAK,SAAS,OAAO;AAC9B,mBAAS,WAAW,OAAO,QAAQ,SAAS,QAAQ,KAAK;AAAA,QAC3D,WAAW,KAAK,SAAS,MAAM;AAC7B,mBAAS,IAAI,KAAK;AAAA,QACpB,OAAO;AACL,0BAAgB,KAAK,IAAI;AAAA,QAC3B;AAAA,MACF,OAAO;AACL,cAAM,UAAU,eAAe,IAAI;AACnC,YAAI,SAAS;AACX,qBAAW,OAAO,QAAQ,QAAQ;AAChC,qBAAS,IAAI,GAAG;AAAA,UAClB;AAAA,QACF,OAAO;AACL,0BAAgB,KAAK,IAAI;AAAA,QAC3B;AAAA,MACF;AAAA,IACF,OAAO;AACL,sBAAgB,KAAK,IAAI;AAAA,IAC3B;AAAA,EACF;AAGA,MAAI,SAAS,OAAO,KAAM,SAAS,OAAO,KAAK,SAAS,OAAO,GAAI;AACjE,UAAM,YAAY,CAAC,GAAG,UAAU,GAAG,QAAQ;AAC3C,UAAM,MAAM,WAAW,KAAK,CAAC,MAAM;AACjC,UAAI,EAAE,SAAS,QAAQ;AACrB,cAAM,QACJ,uBAAuB,CAAS,KAAK,eAAe,CAAS;AAC/D,eAAO,UAAU;AAAA,MACnB;AACA,aAAO;AAAA,IACT,CAAC;AAED,QAAI,OAAO,IAAI,SAAS,QAAQ;AAC9B,YAAM,QACJ,uBAAuB,GAAW,KAAK,eAAe,GAAW;AACnE,UAAI,OAAO;AACT,eAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,YACJ,MAAM;AAAA,YACN,EAAE,MAAM,OAAO,OAAO,UAAA;AAAA,UAAU;AAAA,QAClC;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAA0C,CAAA;AAGhD,MAAI,UAAU,QAAQ,WAAW,MAAM;AAErC,UAAM,OACJ,UAAU,QACN,0BAA0B,YAAY,OAAO,MAAM,IACnD,0BAA0B,YAAY,MAAM,KAAK;AACvD,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B,WAAW,UAAU,MAAM;AACzB,UAAM,OAAO,0BAA0B,YAAY,MAAM,KAAK;AAC9D,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B,WAAW,WAAW,MAAM;AAC1B,UAAM,OAAO,0BAA0B,YAAY,OAAO,MAAM;AAChE,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B;AAGA,MAAI,UAAU,QAAQ,WAAW,MAAM;AACrC,UAAM,OACJ,UAAU,QACN,0BAA0B,YAAY,OAAO,MAAM,IACnD,0BAA0B,YAAY,MAAM,KAAK;AACvD,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B,WAAW,UAAU,MAAM;AACzB,UAAM,OAAO,0BAA0B,YAAY,MAAM,KAAK;AAC9D,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B,WAAW,WAAW,MAAM;AAC1B,UAAM,OAAO,0BAA0B,YAAY,OAAO,MAAM;AAChE,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B;AAGA,MAAI,SAAS,SAAS,KAAK,SAAS,SAAS,GAAG;AAC9C,UAAM,OAAO,0BAA0B,YAAY,MAAM,CAAC,GAAG,QAAQ,EAAE,CAAC,CAAC;AACzE,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B;AAGA,MAAI,SAAS,SAAS,KAAK,SAAS,OAAO,GAAG;AAC5C,WAAO;AAAA,MACL,WAAW,KAAK,CAAC,MAAM;AACrB,YAAI,EAAE,SAAS,QAAQ;AACrB,iBAAQ,EAAW,SAAS;AAAA,QAC9B;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IAAA;AAAA,EAEL;AAGA,SAAO,KAAK,GAAG,eAAe;AAE9B,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,EAAE,MAAM,OAAO,OAAO,KAAA;AAAA,EAC/B;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,OAAO,CAAC;AAAA,EACjB;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,EAAA;AAEV;;;;;;;;"}