Skip to content

Give the value-accumulating aggregations the query's null handling option, and add their missing multi-value paths - #19326

Merged
Jackie-Jiang merged 1 commit into
apache:masterfrom
Jackie-Jiang:null_contract_value_aggregations
Aug 21, 2026
Merged

Give the value-accumulating aggregations the query's null handling option, and add their missing multi-value paths#19326
Jackie-Jiang merged 1 commit into
apache:masterfrom
Jackie-Jiang:null_contract_value_aggregations

Conversation

@Jackie-Jiang

Copy link
Copy Markdown
Contributor

Summary

Part of #19218. Follows #19316, which did the sketch-backed distinct counts.

SKEWNESS, KURTOSIS, STUNION, SUMARRAYLONG, SUMARRAYDOUBLE, HISTOGRAM and IDSET never received
the query's null handling option, so a null row was aggregated as the column default whatever the query
asked for. They now extend NullableSingleInputAggregationFunction and skip null rows when the option is
enabled, across aggregate, aggregateGroupBySV and aggregateGroupByMV. Five of the seven were not even
being passed the flag by AggregationFunctionFactory.

The empty answer is per-function, and each one is a constraint

These seven disagree on what "nothing aggregated" renders to with the option disabled: NaN for the moments,
the empty point for STUNION, an all-zero histogram, an empty id set, and NULL for the array sums. Each is
the answer that mode has always given, so the substitution of an empty accumulator for an untouched holder is
kept and guarded on the flag rather than removed. The disabled path — including the intermediate result that
crosses the wire — is unchanged; only the enabled path yields the null that means nothing was aggregated.

SUMARRAYLONG / SUMARRAYDOUBLE need no such guard, because NULL is already their answer in both modes.
That is now pinned by a test rather than left to inspection.

Multi-value support

  • HISTOGRAM carried a //TODO: Add MV support for histogram, rejected multi-value columns in two of its
    three methods, and failed with a different error in the third. It now supports them, and also BIG_DECIMAL,
    which it previously rejected as a "non-numeric type".
  • STUNION read the single-value representation in all three paths. A multi-value row now folds every
    geometry into the same union.
  • IDSET already handled both, but was missing the multi-value BYTES case even though IdSets supports
    BYTES and the single-value case worked. SV and MV are now symmetric across all six types it supports.

All three are restructured into three entry points dispatching to private SV/MV helpers, matching the
sketch-backed distinct counts.

A bug the restructure exposed

forEachNotNull invokes its consumer once per contiguous non-null range. HISTOGRAM accumulates into a
local buffer and publishes it, so publishing from inside the lambda re-added the earlier ranges' counts on
every later range — for rows [v0, null, v2], v0 was counted twice. It is now accumulated with
foldNotNull and published once, guarded on a row count so an all-null block still leaves the holder
untouched. The other six accumulate directly into the holder and were never affected.

What is deliberately left out

SKEWNESS and KURTOSIS keep their single-value, non-BIG_DECIMAL restriction. Both limits live in the
shared StatisticalAggregationFunctionUtils.getValSet, which VARPOP, VARSAMP, STDDEVPOP, STDDEVSAMP
and both covariances also route through, and which carries its own MV TODO. Widening it silently changes six
other functions, so it belongs in its own change.

Testing

ValueAggregationNullHandlingTest is new. It covers what AggregationFunctionNullContractTest cannot: that
harness drives one synthetic single-value block through aggregate only, and cannot construct most of these
from its shared argument shapes at all.

Every case was verified by mutation rather than by inspection. One mutation survived the first pass —
removing the empty-range guard from the array sums — which is what the zero-length block test now pins.
AggregationFunctionNullContractTest gains SKEWNESS, KURTOSIS, STUNION, HISTOGRAM, IDSET and both
array sums in the set that must answer NULL when nothing was aggregated, and pins SKEWNESS/KURTOSIS/
IDSET/HISTOGRAM as newly honouring the option. SyntheticBlockValSets gains the Int, Float, Double
and BigDecimal multi-value fixtures the new paths need.

…tion, and add their missing multi-value paths

SKEWNESS, KURTOSIS, STUNION, SUMARRAYLONG, SUMARRAYDOUBLE, HISTOGRAM and IDSET
never received the query's null handling option, so a null row was aggregated as
the column default no matter what the query asked for. They now extend
NullableSingleInputAggregationFunction and skip null rows when the option is
enabled, across aggregate, aggregateGroupBySV and aggregateGroupByMV. Five of
the seven were not even being passed the flag by the factory.

Each of these renders "nothing aggregated" differently with the option disabled
- NaN for the moments, the empty point for STUNION, an all-zero histogram, an
empty id set, NULL for the array sums - and every one of those is a
backward-compatibility constraint. The substitution of an empty accumulator for
an untouched holder is therefore kept, guarded on the flag, so the disabled path
including the intermediate that crosses the wire is unchanged. Only the enabled
path yields the null that means nothing was aggregated.

HISTOGRAM and STUNION gain multi-value support. HISTOGRAM carried a
"TODO: Add MV support for histogram" and rejected multi-value columns in two of
its three methods while the third failed with a different error; STUNION read
the single-value representation everywhere. IDSET already handled both but was
missing the multi-value BYTES case, although IdSets supports BYTES and the
single-value case worked. HISTOGRAM also gains BIG_DECIMAL, which it previously
rejected as a "non-numeric type".

All three are restructured into three entry points that dispatch to private
SV/MV helpers, matching the sketch-backed distinct counts.

One bug is fixed along the way that the wrapping introduced and the restructure
exposed: forEachNotNull invokes its consumer once per contiguous non-null range,
so publishing a locally accumulated histogram from inside the lambda re-added
the earlier ranges' counts on every later range. It is accumulated with
foldNotNull and published once, guarded on a row count so an all-null block
still leaves the holder untouched.

SKEWNESS and KURTOSIS keep their single-value, non-BIG_DECIMAL restriction. Both
limits live in the shared StatisticalAggregationFunctionUtils.getValSet, which
VARPOP, VARSAMP, STDDEVPOP, STDDEVSAMP and the covariances also route through
and which carries its own MV TODO, so widening it belongs in its own change.

ValueAggregationNullHandlingTest is new and covers what
AggregationFunctionNullContractTest cannot: it drives one synthetic single-value
block through aggregate only, and cannot construct most of these from its shared
argument shapes at all. Every case was verified by mutation rather than by
inspection. One mutation survived the first pass - removing the empty-range
guard from the array sums - which is what the zero-length block test now pins.
SyntheticBlockValSets gains the Int, Float, Double and BigDecimal multi-value
fixtures the new paths need.
@Jackie-Jiang Jackie-Jiang added query Related to query processing functions Related to scalar or aggregation functions null support Related to NULL value handling backward-incompat Introduces a backward-incompatible API or behavior change release-notes Referenced by PRs that need attention when compiling the next release notes labels Aug 21, 2026
@codecov-commenter

codecov-commenter commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 48.36795% with 348 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.00%. Comparing base (f8352be) to head (fa65116).

Files with missing lines Patch % Lines
...egation/function/HistogramAggregationFunction.java 31.51% 148 Missing and 15 partials ⚠️
...aggregation/function/IdSetAggregationFunction.java 56.55% 99 Missing and 17 partials ⚠️
...gregation/function/StUnionAggregationFunction.java 65.33% 23 Missing and 3 partials ⚠️
...ction/array/SumArrayDoubleAggregationFunction.java 40.74% 15 Missing and 1 partial ⚠️
...unction/array/SumArrayLongAggregationFunction.java 48.14% 14 Missing ⚠️
...tion/function/FourthMomentAggregationFunction.java 62.50% 10 Missing and 2 partials ⚠️
...gregation/function/AggregationFunctionFactory.java 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             master   #19326    +/-   ##
==========================================
  Coverage     67.00%   67.00%            
  Complexity     1424     1424            
==========================================
  Files          3463     3463            
  Lines        221671   222076   +405     
  Branches      34954    34956     +2     
==========================================
+ Hits         148524   148800   +276     
- Misses        61310    61393    +83     
- Partials      11837    11883    +46     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 67.00% <48.36%> (+<0.01%) ⬆️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 67.00% <48.36%> (+<0.01%) ⬆️
unittests 67.00% <48.36%> (+<0.01%) ⬆️
unittests1 57.68% <48.36%> (-0.03%) ⬇️
unittests2 38.91% <0.00%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Jackie-Jiang
Jackie-Jiang merged commit 3c92923 into apache:master Aug 21, 2026
12 checks passed
@Jackie-Jiang
Jackie-Jiang deleted the null_contract_value_aggregations branch August 21, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backward-incompat Introduces a backward-incompatible API or behavior change functions Related to scalar or aggregation functions null support Related to NULL value handling query Related to query processing release-notes Referenced by PRs that need attention when compiling the next release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants