[fix](nereids) Respect aggregate null-row semantics in CASE rewrite - #67818
Open
morrySnow wants to merge 1 commit into
Open
[fix](nereids) Respect aggregate null-row semantics in CASE rewrite#67818morrySnow wants to merge 1 commit into
morrySnow wants to merge 1 commit into
Conversation
morrySnow
requested review from
924060929,
englefly and
starocean999
as code owners
September 10, 2026 13:47
Contributor
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
Contributor
Author
|
run buildall |
Contributor
TPC-H: Total hot run time: 17081 ms |
Contributor
TPC-DS: Total hot run time: 83795 ms |
Contributor
ClickBench: Total hot run time: 14.7 s |
morrySnow
marked this pull request as draft
September 12, 2026 07:44
### What problem does this PR solve? Issue Number: None Related PR: None Problem Summary: Rewriting an aggregate over a CASE expression into the same aggregate over a filtered input is correct only when the aggregate ignores every row containing a NULL aggregate argument. The previous rule did not encode this semantic requirement, so NULL-preserving functions such as array_agg could return a different number of elements after the rewrite. Require an audited null-row trait before applying the rewrite. Classify built-in aggregates from their backend nullable-wrapper or explicit add-path behavior, keep NULL-preserving and incompletely proven functions unmarked, and document that the contract applies to every argument, signature, mode, and DISTINCT execution. Add plan and result coverage for both CASE simplification and aggregate-driven not-null inference. ### Release note Fix incorrect results when simplifying CASE expressions inside NULL-preserving aggregate functions. ### Check List (For Author) - Test: Regression test and Unit Test - InferAggNotNullTest - infer_agg_not_null - eliminate_aggregate_casewhen - Full FE build and Checkstyle - Behavior changed: Yes. Aggregate CASE simplification now runs only for functions proven to ignore NULL-argument rows. - Does this need documentation: No
morrySnow
force-pushed
the
fix/null-ignoring-aggregate-rewrite
branch
from
September 12, 2026 10:58
8b17809 to
90b73ef
Compare
morrySnow
marked this pull request as ready for review
September 12, 2026 10:58
Contributor
Author
|
run buildall |
Contributor
TPC-H: Total hot run time: 16925 ms |
Contributor
TPC-DS: Total hot run time: 82041 ms |
Contributor
ClickBench: Total hot run time: 14.84 s |
Contributor
FE UT Coverage ReportIncrement line coverage |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What problem does this PR solve?
Issue Number: None
Related PR: None
Problem
agg(CASE WHEN predicate THEN value END)cannot always be replaced byagg(value)overFILTER predicate. Aggregates such asarray_aggpreserve aNULL element for an unmatched row, so filtering that row changes the result.
Root cause
The CASE rewrite did not require a semantic guarantee that the aggregate
ignores a complete input row whenever any aggregate argument is SQL NULL. The
same guarantee is also consumed when inferring not-null predicates below a
global aggregate, but it was not represented as an explicit class-wide
contract.
Reproduction
For an input containing one matching row and one non-matching row,
array_size(array_agg(CASE WHEN predicate THEN value END))is2: one valueand one NULL element. The old rewrite filtered the non-matching row and returned
1.Fix
least one aggregate argument is NULL. Consequently, an input consisting only
of such rows must produce the same result as empty input; this is independent
of whether the result type itself is nullable.
multi-argument, and variadic backend wrappers skip a row before nested
addwhen any nullable argument is NULL. Manually constructed implementations are
marked only when their
addpath has the same behavior.two-argument
collect_list,intersect_count, sequence functions,window-funnel functions, and
retention; their complete argument lists passthrough the nullable wrapper.
original nullable argument types, so NULL tuples are skipped before entering
the distinct set. Multi-phase DISTINCT retains the same invariant.
ArrayAgg,MapAgg, andMapAggV2unmarked. Leave
CountByEnumunmarked because it has different NULL handling.Leave
GroupConcatandMultiDistinctGroupConcatunmarked because theirclass-wide ORDER BY signatures are not proven by the ordinary argument
wrapper contract.
Tests
CUSTOM_MVN=/usr/local/bin/mvn ./run-fe-ut.sh --run org.apache.doris.nereids.rules.rewrite.InferAggNotNullTestCUSTOM_MVN=/usr/local/bin/mvn DISABLE_BUILD_UI=ON ./build.sh --feinfer_agg_not_nulleliminate_aggregate_casewhenfunctions, and positive/negative DISTINCT behavior
Release note
Fix incorrect results when simplifying CASE expressions inside NULL-preserving
aggregate functions.
Check List (For Author)
functions proven to ignore NULL-argument rows.