Jackie-Jiang opened a new pull request, #19684: URL: https://github.com/apache/pinot/pull/19684
## Summary `BlockDocIdSet#getOptimizedDocIdSet` let a parent ask whether a child was empty, but only `AND` / `OR` `getTrues`, `CombinedFilterOperator` and `deriveNulls` asked. The not-false / false paths used by `NOT` and null handling checked `instanceof EmptyDocIdSet` directly and never saw an empty bitmap as empty. This PR normalizes emptiness when a doc id set is built instead, so an empty result is always an `EmptyDocIdSet` and every parent can recognize it by type: - `BitmapDocIdSet`, `RangelessBitmapDocIdSet` and `SortedDocIdSet` get `create` factories that return an `EmptyDocIdSet` for an empty input. The index-based filter operators and `BlockDocIdSet#toNonScanDocIdSet` build their results through them. - `ShortCircuitingDocIdSet` is merged into `EmptyDocIdSet`, which now keeps the number of entries scanned before the result was found empty. `EmptyDocIdSet.unscanned()` (was `getInstance()`) is the shared instance for a result found without scanning. It is no longer the only instance, so emptiness is tested with `instanceof`, not `==`. - `BitmapDocIdSet` takes an optional scan count, replacing the anonymous subclasses in `H3IndexFilterOperator`, `H3InclusionIndexFilterOperator` and the legacy path of `RangeIndexBasedFilterOperator`. - `BaseColumnFilterOperator` returns an empty match as is from `excludeNulls`, and returns the null bitmap directly from `getNotFalses` when nothing matches. The result stays recognizable as empty, and `AndDocIdSet` / `OrDocIdSet` keep merging their bitmap children instead of falling back to iterator intersection. - `TestFilterOperator` moves from main to test sources. ## Behavior Query results do not change: an `EmptyDocIdSet` iterates exactly like an empty bitmap. Two things do: - More short-circuits fire. `NOT` over an index-based predicate that matches nothing becomes a match-all instead of iterating the complement of an empty set, and with null handling enabled an empty column predicate no longer hides inside an `AndDocIdSet`, so a parent `AND` stops early. - `numEntriesScannedInFilter` can be lower on the `NOT` path. When a filter that scans eagerly (H3, legacy range index) matches nothing, `NOT` short-circuits to a match-all, which carries no scan count. This matches how `OR` already drops the count when it short-circuits on a match-all child. ## API changes For code outside Pinot that implements or builds these classes: - `BlockDocIdSet#getOptimizedDocIdSet` is removed. - `ShortCircuitingDocIdSet` is removed; use `new EmptyDocIdSet(numEntriesScannedInFilter)`. - `EmptyDocIdSet.getInstance()` is renamed to `EmptyDocIdSet.unscanned()`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) -- 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]
