Jackie-Jiang opened a new pull request, #19440:
URL: https://github.com/apache/pinot/pull/19440

   ## Summary
   
   With null handling enabled, a null row is UNKNOWN to a predicate on its 
column: neither the predicate nor its negation selects it. Several filter paths 
were still two-valued and let such rows through. All of them are fixed here by 
making the bitmap shortcut layer three-valued rather than by declining 
shortcuts, so no null-handling query gets slower and the null-handling-disabled 
path is unchanged.
   
   ### What was wrong
   - **Always-true / always-false predicates.** An evaluator's verdict is over 
the dictionary's real values. The leaf collapsed to `MatchAllFilterOperator` / 
`EmptyFilterOperator` (or a two-valued bitmap), so `c <> 99999` selected the 
null rows and `NOT (c = 99999)` selected them too. `StarTreeUtils` dropped such 
predicates from its map, so the column escaped the null check that decides 
whether a star-tree may serve the query, and the star-tree answered with the 
null row folded into the column's default value.
   - **Index-based shortcuts.** `InvertedIndexFilterOperator`, 
`SortedIndexBasedFilterOperator` and `RangeIndexBasedFilterOperator` computed 
`getNumMatchingDocs()` and `getBitmaps()` from postings alone, while their 
`getTrues()` excluded the null vector. `COUNT(*)` on its own takes the fast 
filtered count whenever the filter can count, so `SELECT COUNT(*) FROM t WHERE 
c <> 1` counted the null rows and `WHERE c = <default>` matched them. 
Index-based `DISTINCT` and the vector pre-filter consumed the same bitmaps.
   - **Negation and composition.** `NotFilterOperator` counted `numDocs - 
child` and inverted the child's bitmaps as if no document were UNKNOWN, and NOT 
/ AND / OR reported no UNKNOWN documents through `getNulls()`, so a nested 
boolean tree under a negation treated UNKNOWN rows as false on the iterator 
path.
   
   ### What changes
   - `BitmapCollection` gains an optional null bitmap via `excludingNulls`. 
True documents are the union (or its complement when inverted) minus the nulls; 
`invert()` keeps the null bitmap since NOT of UNKNOWN is UNKNOWN; `reduce()`, 
`andCardinality`, `orCardinality` and the new `getCardinality()` honor it. The 
two-valued fast paths are untouched when no null bitmap is present.
   - `BaseColumnFilterOperator` reads the column's null bitmap once at 
construction, only when null handling is enabled and the vector is non-empty. 
`getTrues()`, `getNulls()`, `mayHaveNulls()` and the leaves' shortcuts share 
it, so a consuming segment's vector (which hands out a copy per read) is copied 
once and every view of the operator sees the same rows.
   - The inverted, sorted and range leaves and `BitmapBasedFilterOperator` 
attach that bitmap to their collection and count through it when nulls exist. 
The no-null path is the previous code.
   - `BaseFilterOperator.mayHaveNulls()` is a metadata-only, possibly 
pessimistic answer. `NotFilterOperator` counts through the child's inverted 
bitmaps only when the child may have nulls, and propagates its child's 
`getNulls()`. `AndFilterOperator` / `OrFilterOperator` compute their own null 
bitmap when a child has one and implement `getNulls()` as "neither true nor 
false".
   - `FilterOperatorUtils.getLeafFilterOperator` builds a three-valued 
`BitmapBasedFilterOperator` for an always-true / always-false predicate on a 
column with nulls, instead of a constant. `StarTreeUtils` keeps such predicates 
in its map so the null check sees the column. 
`FilterOperatorUtils.getNullBitmap` / `hasNulls` are the shared helpers.
   - `PredicateEvaluatorProvider` throws `IllegalStateException` rather than 
`UnsupportedOperationException` for an unsupported predicate type.
   
   ### Behavior change
   Query answers change only with null handling enabled, and only where they 
were wrong: counts, distinct values and star-tree aggregates no longer include 
null rows that the predicate or its negation cannot select. Leaves that never 
model nulls (text, JSON, H3, vector) are unchanged.
   
   ### Tests
   `BitmapCollectionTest` covers the null cases including inversion. 
`NotFilterOperatorTest`, `AndFilterOperatorTest` and `OrFilterOperatorTest` 
check count, bitmaps, `getNulls()` and the iterator path agree under nulls, 
including nested trees under a negation. `InvertedIndexFilterOperatorTest` 
mocks a null vector on both sides of exclusive. 
`NullHandlingEnabledQueriesTest` runs `COUNT(*)` with `<>`, equality on the 
stored default, `NOT`, `IN` and ranges over inverted, sorted and range-indexed 
columns, a multi-value column, a consuming segment, and index-based `DISTINCT`. 
`StarTreeNullHandlingQueriesTest` checks the star-tree is refused for 
always-true / negated always-false predicates and OR clauses on a nullable 
column and kept on a non-nullable one.
   


-- 
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