waterWang opened a new pull request, #19350:
URL: https://github.com/apache/pinot/pull/19350

   Fixes #19339
   
   ## Description
   
   When a filter has the shape `P AND (A OR B)`, Pinot evaluates the `(A OR B)` 
subtree independently of `P`. If a branch of the OR contains a predicate with 
no index (e.g. `IN_SUBQUERY` evaluated via `ExpressionScanDocIdIterator`), that 
predicate is evaluated for every document matching the branch, not only for the 
documents that satisfy `P`.
   
   This PR adds a new `DistributeConjunctsIntoOrFilterOptimizer` that 
distributes selective conjuncts (EQUALS/IN on single columns) from an enclosing 
AND into each OR branch:
   
   ```
   P AND (A OR B)  →  P AND ((P AND A) OR (P AND B))
   ```
   
   This rewrite is sound because in three-valued logic, `P ∧ (A ∨ B) ≡ P ∧ ((P 
∧ A) ∨ (P ∧ B))` when the filter only passes TRUE (SQL WHERE semantics).
   
   ## The bug in detail
   
   The `AndDocIdSet#iterator()` method chooses between two strategies at the 
eager/lazy threshold. When an OR subtree contains an AND with both index-based 
and scan-based children, the eager path materializes the index-based children 
into a bitmap over the **entire segment** before applying the scan-based 
predicate. This means expensive predicates like `IN_SUBQUERY` are evaluated for 
every document in the segment, not just those matching the enclosing AND's 
selective predicate `P`.
   
   Adding an index to a column inside an OR branch **makes the query slower** 
because it triggers the eager path:
   
   > With no index-based child in the AND under the OR: lazy path, outer merged 
bitmap drives it. The expensive predicate is only evaluated at documents that 
already match P.
   > With one or more index-based children: eager path, branch is fully 
materialized over the whole segment, ignoring P.
   
   In production, the same query returning the same two rows went from ~500 to 
~17,000,000 `numEntriesScannedInFilter` after adding indexes to two columns in 
the OR branch.
   
   ## Test plan
   
   - Added `DistributeConjunctsIntoOrFilterOptimizerTest` with 5 test cases:
     - Basic distribution: `P AND (A OR B)` → `P AND ((P AND A) OR (P AND B))`
     - No distribution when there is no OR child
     - No distribution when the conjunct is not a simple column predicate
     - Branch already contains the conjunct → no redundant AND(P, P) created
     - No rewrite when there is no AND child (only OR)
   - All existing optimizer tests should continue to pass (the optimizer is 
added at the end of the pipeline, after all existing optimizers)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to