Fix GROUP BY DISTINCTCOUNT ClassCastException in mergeDataTablesOnly - #18842
Conversation
02d46d8 to
af6f235
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #18842 +/- ##
============================================
+ Coverage 64.77% 64.82% +0.04%
- Complexity 1322 1347 +25
============================================
Files 3393 3396 +3
Lines 211022 212504 +1482
Branches 33135 33484 +349
============================================
+ Hits 136687 137746 +1059
- Misses 63322 63597 +275
- Partials 11013 11161 +148
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Fixes a ClassCastException in the broker-side merge-only reduce path for GROUP BY queries containing OBJECT-typed intermediate aggregates (e.g. DISTINCTCOUNT), ensuring merge-only output remains re-mergeable as an intermediate DataTable.
Changes:
- Adjust
GroupByDataTableReducersoIndexedTable.finish(...)is invoked by callers with the correct semantics: merge-only keeps intermediate aggregate state, while normal reduce finalizes scalars. - Add a regression test covering
GROUP BY+DISTINCTCOUNTOBJECT-intermediate round-tripping throughmergeDataTablesOnlyand then the normal reduce path.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java |
Moves finish(...) responsibility to callers and uses finish(true, false) for merge-only to preserve intermediate aggregate values. |
pinot-core/src/test/java/org/apache/pinot/core/query/reduce/MergeDataTablesOnlyTest.java |
Adds a regression test and helper to build OBJECT-aggregate group-by DataTables that emulate server intermediate encoding. |
27b3b99 to
1854a54
Compare
mergeDataTablesOnly reused getIndexedTable, which unconditionally called indexedTable.finish(true, true). For a GROUP BY query with an OBJECT-typed intermediate aggregate (DISTINCTCOUNT, DISTINCTCOUNTHLL, etc.), that storeFinalResult=true call: - mutated DataSchema._columnDataTypes from OBJECT to the final-result type in place (IndexedTable.java:170-174), and - replaced each row's value with extractFinalResult(value) (Set → Integer for DISTINCTCOUNT) at IndexedTable.java:195. The subsequent buildIntermediateDataTable read DataSchema's cached _storedColumnDataTypes — populated earlier from the pre-finalize OBJECT schema and never invalidated by finish — so the OBJECT branch fired and handed the now-Integer value into BaseDistinctAggregateAggregationFunction.serializeIntermediateResult(Set), which threw ClassCastException. Fix: move finish() out of getIndexedTable and let each caller pick the right mode. reduceResult keeps finish(true, true) (downstream consumers expect final scalars). mergeDataTablesOnly calls finish(true, false) so aggregate values stay as intermediates and round-trip through buildIntermediateDataTable correctly. Adds a testGroupByDistinctCountObjectRoundTrip regression: server emits intermediate OBJECT-encoded Sets, merge produces a re-injectable intermediate DataTable, and reducing that intermediate matches a direct reduce of the same servers.
1854a54 to
cdfb22b
Compare
Background
BrokerReduceServiceexposes two reduce paths:reduceResult(...)— the normal path: merge per-server results and finalize.mergeDataTablesOnly(...)— merge-only: produce an intermediateDataTablethat can be re-merged later via the normal reduce path. Used by callers that need to combine partial scatters without finalizing.The merge-only path's contract is that aggregate values stay as intermediates end-to-end.
What's wrong
OSS's single-stage broker request handler auto-sets
SERVER_RETURN_FINAL_RESULT=truewhen the routing table targets exactly one server (BaseSingleStageBrokerRequestHandler.java:844). That optimization causes servers to return finalized scalars (e.g.IntegerforDISTINCTCOUNT,LongforDISTINCTCOUNTHLL) instead of intermediate sketches — which directly contradicts the merge-only contract: the input is already final-typed, not intermediate.Two ways this manifested in
GroupByDataTableReducer.mergeDataTablesOnly:No guard for the single-server-final-result mode. When
_queryContext.isServerReturnFinalResult()is true, the input DataTables hold final values, so merge-only cannot honor its contract. The previous code silently proceeded and produced a malformed intermediate.getIndexedTableover-finalized internally. Even when servers correctly returned intermediates, the shared helper unconditionally calledindexedTable.finish(true, true). For aGROUP BYquery with an OBJECT-typed intermediate aggregate (DISTINCTCOUNT,DISTINCTCOUNTHLL, etc.),storeFinalResult=true:DataSchema._columnDataTypesfromOBJECTto the final-result type in place (IndexedTable.java:170-174), andextractFinalResult(value)(Set → IntegerforDISTINCTCOUNT) atIndexedTable.java:195.The subsequent
buildIntermediateDataTablereadDataSchema's lazily-populated_storedColumnDataTypes(populated earlier from the pre-finalize OBJECT schema and never invalidated byfinish), so the OBJECT branch fired and handed the now-Integervalue intoBaseDistinctAggregateAggregationFunction.serializeIntermediateResult(Set), which threwClassCastException.Fix
mergeDataTablesOnly: throwUnsupportedOperationExceptionwith a clear message namingSERVER_RETURN_FINAL_RESULT. Callers expecting an intermediate must either disable this option or usereduceResultinstead.finish()out ofgetIndexedTableand let each caller pick the right mode:reduceResultkeepsfinish(true, true)— downstream consumers expect final scalars.mergeDataTablesOnlycallsfinish(true, false)so aggregate values stay as intermediates and round-trip throughbuildIntermediateDataTablecorrectly.Tests
Adds
testGroupByDistinctCountObjectRoundTripinMergeDataTablesOnlyTest: servers emit intermediate OBJECT-encodedSets, merge produces a re-injectable intermediateDataTable, and reducing that intermediate matches a direct reduce of the same per-server inputs.Release Notes
Fixes a
ClassCastExceptioninmergeDataTablesOnlywhen mergingGROUP BYresults that include OBJECT-typed intermediate aggregates (e.g.DISTINCTCOUNT,DISTINCTCOUNTHLL).mergeDataTablesOnlynow also throwsUnsupportedOperationExceptionup front when called against a query whose servers will return final results (e.g. the single-serverSERVER_RETURN_FINAL_RESULTauto-set), instead of silently producing a malformed intermediate.