[fix](be) Reject incompatible aggregate state parameters - #67805
[fix](be) Reject incompatible aggregate state parameters#67805HappenLee wants to merge 10 commits into
Conversation
### What problem does this PR solve?
Issue Number: N/A
Related PR: N/A
Problem Summary:
AggState types describe argument types but do not include constant parameter values. For example, `topn_merge` over a `UNION ALL` of `topn_state('a', 1)` and `topn_state('a', 3)` can combine incompatible states. TopN previously overwrote the destination N/capacity, making results depend on merge order. Other configurable aggregates silently adopted incompatible settings; legacy percentile arrays could index beyond the destination state when quantile counts differed.
Reject incompatible populated states with `INVALID_ARGUMENT`, using `UNLIKELY` for the mismatch paths. Apply the same invariant to TopN variants, histograms, percentile variants, limited collect, group_concat, intersect_count, exponential moving average, sequence functions and both window_funnel implementations. Preserve empty-state identity and reset behavior, initialize approximate percentile digests with the source compression, and keep serialization formats unchanged.
### Release note
Merging aggregate states with incompatible parameters now returns an error instead of producing incorrect results or risking an out-of-bounds access.
### Check List (For Author)
- Test:
- [x] Regression test: `test_agg_state_parameters` covers both `_merge` and `_union`, both input orders, and 39 incompatible parameter pairs.
- [x] Unit Test: 56 tests passed, including parameter compatibility, serialized merge, empty states, reset and compatible-state results, plus existing related aggregate tests.
- Local validation: BE ASAN and FE builds, the regression suite (156 expected errors), clang-format 16, and BE header hygiene passed. Local BE startup required OpenBLAS `USE_OPENMP=FALSE` to avoid a toolchain initialization crash; this build-cache setting is not part of the patch. Full clang-tidy is blocked by the existing unmatched `NOLINTEND` in `be/src/core/types.h`; diagnostics on the changed code have been addressed.
- Behavior changed:
- [x] Yes. Incompatible aggregate state parameters fail with `INVALID_ARGUMENT`; matching states retain their semantics.
- Does this need documentation?
- [x] No.
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
|
/review |
|
run buildall |
There was a problem hiding this comment.
Request changes — capped/incomplete after the mandatory three review rounds. Ten accepted correctness issues are attached inline. The final round found the exact-percentile configured-empty case, so the search cap was reached with a new accepted issue; every candidate found so far is adjudicated, but the review status must remain capped/incomplete.
Critical checkpoint conclusions:
- Goal and proof: The intended rejection of incompatible AggState parameters is not complete. The new unit tests cover direct and deserialize-and-merge behavior, reset, compatible reuse, and ordinary mismatches; the regression suite covers both
_mergeand_unionorders. They do not cover the accepted sentinel, non-finite, configured-empty, distinct-wrapper, or within-state_combinecases. - Scope and clarity: The production diff is focused on state comparison/reset logic and tests. The local checks are generally small and placed before mutation, but the overall fix omits supported wrapper and state-construction paths identified inline.
- Concurrency: These are per-aggregate-place state transitions; no new shared mutable state, thread entry, lock, atomic, or lock-order issue is introduced. Distributed partial aggregation matters semantically, as noted inline, but not as a data race.
- Lifecycle: Create, add, reset, nullable wrapping, serialization/deserialization, empty-state adoption, and result finalization were traced. Most reset paths restore their sentinel correctly; several accepted findings arise because configured-but-empty and legal sentinel-valued states are classified inconsistently. No static-initialization or ownership cycle is involved.
- Configuration: No Doris configuration item is added or changed.
- Compatibility: Function symbols and serialized layouts/version gates are unchanged, so the patch is wire-format compatible. Enforcement behavior is naturally unavailable on older BEs during a rolling upgrade, but no incompatible state encoding was introduced.
- Parallel paths: Direct, vector, selected, nullable, serialized-column, spill,
_state,_union, and_mergepaths ultimately reach nested merge. The exceptions are the outer multi-distinct wrapper and raw_combine/within-state paths called out inline. - Conditional checks: The new identity/equality conditions are not valid for accepted
-1, zero, NaN, all-false, and all-NaN cases. Supported integer/string intersect-key equality was rechecked and the proposed floating-key issue was dismissed because that BE instantiation is unreachable. - Test coverage and results: Test structure and expected-error oracles follow repository conventions, and the pure-text header-hygiene check passed. Per the review-runner instruction, no build or test suite was executed here. At submission time, live GitHub checks show CheckStyle and Clang Formatter passing; BE UT and performance report TeamCity build failed without diagnostics in this runner, and COMPILE remains pending.
- Observability: Existing function-specific exceptions are sufficient for these local validation failures; no new log or metric requirement was found.
- Transactions, persistence, and data writes: No transaction, EditLog, persisted metadata, storage visibility, MoW, or write-atomicity path is changed.
- FE/BE variables: No new variable or thrift field is introduced, so there is no propagation fan-out to update.
- Performance: The added comparisons are bounded by small parameter collections and occur before state mutation; no material CPU, memory, or allocation regression was found.
- Other issues: No additional user review focus was supplied. The TopN zero-expansion report was dismissed as pre-existing serialization behavior rather than a defect introduced by these comparisons.
Please address every inline issue and add the corresponding boundary/path regression coverage.
| void merge(const ExponentialMovingAverageData& rhs) { | ||
| double hd = half_decay != 0.0 ? half_decay : rhs.half_decay; | ||
| if (hd == 0.0) { | ||
| if (rhs.half_decay == 0.0) { |
There was a problem hiding this comment.
[P1] Do not use a valid zero decay as the empty-state marker
A half decay of 0.0 is accepted and explicitly handled by this aggregate, and add() can populate such a state. This return drops that populated source; in the reverse mismatch direction, the following half_decay == 0.0 branch adopts the nonzero configuration instead of rejecting it. The nullable wrapper already tracks whether the aggregate received a non-null row, so use an explicit initialization/emptiness signal rather than a legal parameter value, and add zero/nonzero tests in both directions.
There was a problem hiding this comment.
After discussing the intended contract, we will preserve the existing zero-half-decay behavior: zero returns 0 and serves as the empty/non-contributing state marker. A serialized zero-half-decay state is intentionally ignored by _merge/_union, even if it was built from non-null rows. Thus 0 versus a nonzero half-decay does not raise a mismatch; the compatibility check applies to contributing states with nonzero half-decays.
I have documented this exception in the function's source comment and a local documentation note, and withdrawn the proposed NaN-sentinel implementation. This is a clarification of the intended contract rather than a change to distinguish an initialized zero configuration. The separate NaN-equality concern remains open for discussion.
The documentation-only change passed formatting, header-hygiene, and whitespace checks. Runtime tests were not rerun. All changes remain local; no code has been pushed.
| merge_point(rhs, hd); | ||
| if (half_decay == 0.0) { | ||
| half_decay = rhs.half_decay; | ||
| } else if (UNLIKELY(half_decay != rhs.half_decay)) { |
There was a problem hiding this comment.
[P2] Define equality for every accepted half-decay value
FE accepts any constant numeric half decay, including a NaN double. The first NaN-configured source initializes the destination; merging a second identically configured source reaches this branch and throws because NaN != NaN. Either reject non-finite values at both FE and BE construction boundaries or use equality semantics that are reflexive for every accepted configuration, with direct and serialized identical-NaN coverage.
There was a problem hiding this comment.
Implemented locally: NaN half-decay is now unsupported. A shared check in ExponentialMovingAverageData::write() and get() throws INVALID_ARGUMENT with exponential_moving_average half decay must not be NaN. This rejects newly produced NaN-configured states before they can be serialized for subsequent merging, and also rejects direct finalization paths.
The check is deliberately at serialization/finalization boundaries rather than in per-row add(). Existing merge() compatibility checks and zero-half-decay semantics are unchanged. Only a NaN half-decay is rejected; this does not prohibit NaN input values/results or change infinity handling. No backward-compatibility handling for previously serialized NaN states is added, as discussed for this unreleased function.
All 8 AggregateStateParametersTest tests passed under ASAN, including checks that both output APIs reject NaN and that zero/one half-decays still work. Added SQL expected-error cases for the ordinary, _state, and _combine functions have not been run. Formatting and header-hygiene checks passed; clang-tidy encountered pre-existing diagnostics in core/types.h and unchanged aggregate code. No code has been pushed; the fixes will be submitted together.
| separator = rhs.separator; | ||
| data.assign(rhs.data); | ||
| } else { | ||
| if (UNLIKELY(separator != rhs.separator)) { |
There was a problem hiding this comment.
[P1] Cover the multi-distinct AggState wrapper
This validation runs only when the nested group_concat state is merged. multi_distinct_group_concat_state remains supported, but AggregateFunctionDistinct::merge() unions only its outer argument set and finalization later calls nested add(); it never invokes this method. States with ',' and ';' therefore merge silently and whichever tuple is iterated first selects the separator for all values. Either make that wrapper unsupported for AggState or preserve and validate its separator before unioning, with both operand orders in regression coverage.
There was a problem hiding this comment.
Thanks for pointing out this gap. The distinct wrapper merges and serializes its outer argument set, then feeds the reconstructed arguments to the nested aggregate via add() during finalization. It does not call the nested group_concat merge(), so the separator check added here does not cover this path.
We will defer this issue to a separate follow-up PR. Preserving support requires the wrapper to retain and validate its configuration before combining argument sets; alternatively, restricting AggState support needs consistent handling across function combinators, DDL, and existing stored states. We do not want to fold that broader change into this PR.
No implementation change for this issue is included here. Leaving this thread unresolved to make the remaining gap explicit.
| if (interval == 0) { | ||
| interval = rhs.interval; | ||
| offset = rhs.offset; | ||
| } else if (UNLIKELY(interval != rhs.interval || offset != rhs.offset)) { |
There was a problem hiding this comment.
[P1] Establish a stable, finite histogram configuration before merge
FE requires neither constant nor finite interval/offset values, while add() overwrites both for every row. A linear_histogram_combine(value, interval_column, offset_column) state can therefore contain buckets computed under several configurations but serialize only the last one; distributed partials then throw or pass here based on their last rows. NaN also passes the existing range predicates, and two identically configured NaN partials reject each other at this raw comparison. Enforce constant/finite parameters or validate them on every add, and cover _combine plus distributed varying-parameter cases.
There was a problem hiding this comment.
The missing constant/finite parameter validation and the per-row parameter overwrite both predate this PR. We will address these existing linear_histogram parameter-validation issues together in a follow-up change. No additional fix for this issue will be included in this PR; leaving the thread unresolved to track the deferred work.
| inited_flag = true; | ||
| } else { | ||
| levels.merge(rhs.levels); | ||
| } else if (UNLIKELY(levels.quantiles != rhs.levels.quantiles)) { |
There was a problem hiding this comment.
[P2] Treat an all-NaN exact-percentile state as identity
add_single_range()/add_many_range() set inited_flag and the quantiles before _append(), but _append() discards every floating NaN. Thus percentile_state(NaN, 0.25) serializes as initialized with zero retained values. Merging it with a contributing 0.75 state now throws here in either order even though the all-NaN side cannot affect the result; the reservoir path already uses retained-sample emptiness for this case. Check values.empty() symmetrically before comparing/adopting levels and add both direct and serialized merge orders.
There was a problem hiding this comment.
We do not consider this a bug under the intended parameter-compatibility contract. An uninitialized state has no quantile configuration and can be ignored. A state built from all-NaN input is initialized and retains its explicitly configured quantile, even though it has no retained samples. Merging a 0.25-configured state with a 0.75-configured state must therefore reject the mismatch in either operand order, including after serialization. Sample emptiness does not erase an established configuration. We will keep the current exact-percentile check; the reservoir implementation does not define the contract for this function.
| } | ||
| if (data.empty()) { | ||
| level = rhs.level; | ||
| } else if (UNLIKELY(level != rhs.level)) { |
There was a problem hiding this comment.
[P2] Reject or consistently compare non-finite reservoir levels
A binary prepared DOUBLE parameter can supply NaN as a DoubleLiteral; the FE check value < 0 || value > 1 admits it, and BE stores the level with the sample. After the first state initializes the destination, an identically configured second state throws here because NaN != NaN. Add finite-value validation at FE and BE boundaries, or define equality for every accepted level, and cover the prepared/direct plus serialized paths.
There was a problem hiding this comment.
Implemented locally in the FE PercentileReservoir legality check: changed the rejection condition to !(value >= 0 && value <= 1) and added a comment explaining that the old out-of-range comparisons both evaluate to false for NaN. The new condition rejects NaN and infinities while preserving valid levels, including 0 and 1. StateCombinator and CombineCombinator delegate to this nested legality check as well.
This is a scoped FE validation change. No BE validation or handling of previously serialized NaN-configured states is added.
FE Checkstyle passed with zero violations, git diff --check passed, and manual Java predicate checks covered NaN, both infinities, out-of-range values, negative zero, endpoints and an interior value. FE unit tests, SQL regression tests and the binary prepared-statement path were not run. The change is saved in local commit 243c526 and has not been pushed to this PR.
| void merge(const AggregateFunctionSequenceMatchData& other) { | ||
| if (other.events_list.empty()) return; | ||
|
|
||
| if (!init_flag) { |
There was a problem hiding this comment.
[P1] Treat eventless sequence states symmetrically
AggregateFunctionSequenceBase::add() initializes the pattern before data.add() discards an all-false event row. Such a state has init_flag == true but an empty events_list: as RHS it is ignored above, while as LHS this branch refuses to adopt a differently configured contributing RHS and the next branch throws. Merge success therefore depends on operand order. If the destination has no stored events, adopt the contributing source's pattern/parser state just as an empty RHS is ignored, and test both sequence_match and sequence_count orders.
| if (events_list.empty()) { | ||
| window = other.window; | ||
| window_funnel_mode = other.window_funnel_mode; | ||
| } else if (UNLIKELY(window != other.window || |
There was a problem hiding this comment.
[P1] Validate funnel parameters while building each state
FE checks only the types of window and mode; both V1 and V2 add() overwrite them on every row while retaining events gathered under prior values. Because _combine delegates raw adds, one serialized state can already mix configurations, and ordinary distributed partials may throw or silently merge here according to their final row rather than the full input. Require these parameters to be constant or compare them against the first observed values on every add; apply the same invariant to V2 and cover _combine and distributed varying-parameter inputs.
There was a problem hiding this comment.
Fixed by requiring both window and mode to be constant during FE legality checking in WindowFunnel (V1) and WindowFunnelV2. The ordinary functions and their _state/_combine forms delegate to these checks, so varying configuration columns are rejected before any state is built. Timestamp and event arguments can still be columns. Existing BE merge-time comparisons remain necessary for states built separately with different constant configurations.
Added FE unit coverage for both implementations and all three forms, plus 18 expected-error SQL cases covering window_funnel, window_funnel_v1 and window_funnel_v2 with varying window or mode. All 5 FE parameter tests (including reservoir NaN coverage) and all 3 regression suites (test_agg_state_parameters, window_funnel, window_funnel_v2) passed. The BE ASAN/FE build and FE Checkstyle also passed.
This fix is included in commit a8b018b, together with the previously agreed review follow-ups in this PR update.
### What problem does this PR solve? Issue Number: N/A Related PR: apache#67805 Problem Summary: Collect state merging only skipped the -1 initialization marker, while other negative limits could be adopted or compared against nonnegative limits. Treat every negative-limit source state as non-contributing during merge. In the serialized merge path, negative sources are skipped before they can initialize the fresh destination. ### Release note Collect state merging now ignores source states with any negative limit. ### Check List (For Author) - Test: Manual review of the one-line condition change; clang-format 16, repository format check and BE build hygiene passed. No build or runtime tests run for this incremental review fix. - Behavior changed: Yes. Source states with limits below -1 are skipped during merge. - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: apache#67805 Problem Summary: CollectSet accepted a varying limit expression even though the backend state retains the first observed limit. Add the same FE constant-argument check as CollectList so ordinary collect_set and its state/combine combinators reject a nonconstant limit before execution. Add expected-error regression cases for all three entry points. ### Release note collect_set now requires its optional limit argument to be constant, matching collect_list. ### Check List (For Author) - Test: FE Checkstyle passed with zero violations; git diff --check passed. Regression cases added but not executed in this incremental update; no FE build run. - Behavior changed: Yes. Nonconstant collect_set limits are rejected during analysis. - Does this need documentation: No
### What problem does this PR solve? Related PR: apache#67805 Problem Summary: Clarify that a zero EMA half-decay returns zero and marks a non-contributing serialized aggregate state. Such states are ignored by merge and union; incompatible nonzero half-decays still raise an error. Preserve the existing implementation and document this exception to parameter validation. ### Release note None ### Check List (For Author) - Test: No need to test (documentation and source comments only); C++ formatting, header hygiene and git diff --check passed. - Behavior changed: No - Does this need documentation: Yes (included in docs/exponential-moving-average.md)
### What problem does this PR solve? Related PR: apache#67805 Problem Summary: Two EMA states configured with NaN can fail the half-decay compatibility check because NaN is unequal to itself. Treat NaN half-decay as unsupported and reject it when serializing a state or finalizing a result. Use one shared check at the output boundaries, preserving add/merge behavior and the existing zero-half-decay semantics. ### Release note exponential_moving_average rejects NaN half-decay when serializing an aggregate state or producing a final result. ### Check List (For Author) - Test: Unit Test: all 8 AggregateStateParametersTest tests passed with ASAN. Formatting, header hygiene and git diff --check passed. Added SQL regression cases were not run. Clang-tidy encountered pre-existing diagnostics in core/types.h and unchanged aggregate code. - Behavior changed: Yes (NaN half-decay now errors at state/result output) - Does this need documentation: Yes (updated docs/exponential-moving-average.md)
### What problem does this PR solve? Related PR: apache#67805 Problem Summary: Remove linear_histogram from this change's scope. Restore its header exactly to the PR base and remove the corresponding parameter mismatch checks from the BE unit test and SQL regression suite. The function's parameter contract requires separate work; the other aggregate state validations remain. ### Release note None ### Check List (For Author) - Test: Manual test: verified the header matches the PR base byte for byte and no linear_histogram references remain in the PR diff. Formatting, header hygiene and git diff --check passed. Runtime tests were not rerun because this restores the base implementation and removes its newly added tests. - Behavior changed: No (relative to the PR base) - Does this need documentation: No
### What problem does this PR solve? Issue Number: N/A Related PR: apache#67805 Problem Summary: The percentile_reservoir FE legality check accepts NaN literals because both out-of-range comparisons are false. Negate the inclusive valid range instead so NaN is rejected with the existing AnalysisException, and document the floating-point comparison behavior. Valid levels, including zero and one, remain accepted. ### Release note Reject NaN percentile_reservoir quantile literals during query analysis. ### Check List (For Author) - Test: Manual Java predicate checks covering NaN, infinities, out-of-range values, negative zero, endpoints and an interior value; FE Checkstyle and git diff --check. FE unit tests and SQL regression tests were not run. - Behavior changed: Yes. FE rejects NaN quantile literals. - Does this need documentation: No. Enforces the existing [0, 1] parameter range.
### What problem does this PR solve? Issue Number: N/A Related PR: apache#67805 Problem Summary: Sequence states initialized by all-false event rows retain their pattern but contain no events. The merge path skipped these states as sources while checking their configuration as destinations, making direct merge validation asymmetric and allowing serialized eventless inputs to bypass compatibility checks. Ignore only uninitialized sources, adopt or validate established configuration before the no-event fast return, and keep serialized states with zero arguments uninitialized. Incompatible patterns now fail regardless of which configured state contains events or which input is merged first. The serialized layout is unchanged. ### Release note Reject incompatible sequence_match and sequence_count aggregate-state patterns even when one or both configured states contain no events. ### Check List (For Author) - Test: Unit Test - 20 ASAN tests passed via run-be-ut.sh, including aggregate parameter checks and existing sequence tests. Added expected-error SQL regression cases for both merge/union input orders; these SQL cases were not executed. clang-format 16, BE header hygiene and git diff --check passed. Full clang-tidy is blocked by existing core/types.h and unchanged sequence-header diagnostics. - Behavior changed: Yes. Configured eventless states retain parameter constraints during merge; truly uninitialized states remain identity elements. - Does this need documentation: No.
### What problem does this PR solve? Issue Number: N/A Related PR: apache#67805 Problem Summary: Window funnel states overwrite window and mode on each raw add, so column-valued configurations can mix events under different settings before merge-time validation. Require constant window and mode in FE for both implementations, including ordinary, state and combine calls. Add FE and regression coverage, and FE unit coverage for the earlier reservoir NaN validation fix. ### Release note Window funnel calls with nonconstant window or mode parameters now fail during analysis. ### Check List (For Author) - Test: FE unit tests (5 passed), regression tests (test_agg_state_parameters, window_funnel, window_funnel_v2 passed), BE ASAN and FE build including Checkstyle passed; latest related BE ASAN run passed 20 tests. - Behavior changed: Yes, window and mode must be constant. - Does this need documentation: No
|
/review |
There was a problem hiding this comment.
Requesting changes for one new correctness gap.
The inline comment covers a configured-eventless identity bug in WindowFunnelStateV2: non-null all-false input records and serializes window/mode, but merge() uses the empty event payload as if the state were truly fresh. Incompatible V2 states, including the default window_funnel alias, are consequently ignored or overwritten in both direct and serialized merge orders. This is distinct from the existing funnel thread about varying parameters within one state.
Critical checkpoint conclusions:
- Task goal and data correctness: compatibility validation is sound for the other reviewed aggregate families and configured contributing paths, but this V2 identity bypass leaves the PR's core configured-state guarantee incomplete.
- Tests and observed results: the added BE helper and regression suite cover direct/serialized failures, reverse order, reset/fresh adoption, aliases, and FE constant checks, but all funnel mismatch cases use a true event and miss configured all-false V2 states. Per the review prompt, I did not run builds or tests. At submission, live Clang Formatter and CheckStyle checks pass; the broader build/test workflows are skipped and the automated review checks remain pending. The PR records the author's local passing results.
- Scope and parallel paths: the change is focused. Ordinary/state/combine/merge/union, nullable, map/foreach, distinct, MV, prepared-expression, distributed, and default-alias paths were traced. The already-known
multi_distinct_group_concatbypass remains in its existing thread and was not duplicated. - Lifecycle, concurrency, and configuration: fresh, reset, all-null, and all-false states were distinguished. The suspected collect reset defect was disproved because aggregate reset destroys and reconstructs its data. No shared mutable/static state, runtime config refresh, or concurrency mechanism is introduced.
- Compatibility and failure behavior: serialized layouts are unchanged and same-configuration states remain readable across versions. Existing comparisons run before payload mutation and
INVALID_ARGUMENTpropagates through wrappers; V2's early identity branches alone bypass the check. Any fix that changes its state bytes needs backward-compatible/versioned handling. - Persistence, data writes, observability, and performance: there is no storage/data-write or observability change. Added comparisons are bounded by existing parameter containers and have no material performance or allocation concern.
- Existing review context: existing inline threads were treated as hard duplicate fences. The current head still leaves the already-raised zero-half-decay EMA and multi-distinct group-concat concerns open; they were not reposted.
- User focus and completion: no additional user focus was supplied. The complete PR was reviewed, all candidates were accepted, dismissed, or duplicate-fenced, and the review converged in two rounds on head
a8b018b9ef62be2b8f32becc097acf037ba08691.
TPC-H: Total hot run time: 16625 ms |
TPC-DS: Total hot run time: 81454 ms |
ClickBench: Total hot run time: 14.74 s |
### What problem does this PR solve? Issue Number: N/A Related PR: apache#67805 Problem Summary: WindowFunnelStateV2 records window and mode for all-false input but stores no events. Its merge identity branches previously ignored or overwrote this configuration, accepting incompatible aggregate states in either operand order. Track initialization separately, compare configured parameters before empty-payload handling, and clear configuration on reset. Preserve the serialized field layout using a configured-empty tag in the existing sorted field, which legacy readers already interpret as sorted. Recover initialized legacy states from their parameters or event payload. Extend existing unit and regression coverage for both operand orders, serialized/direct merges, fresh/reset states, aliases and legacy framing. ### Release note All-false window_funnel and window_funnel_v2 states retain their configured window and mode and reject merges with incompatible parameters. ### Check List (For Author) - Test: Unit Test / Regression test / Manual test - 58 related BE ASAN unit tests passed. - test_agg_state_parameters, window_funnel and window_funnel_v2 regression suites passed. - The original binary failed the new all-false mismatch regression as expected. - BE ASAN build, clang-format 16, header hygiene and whitespace checks passed. - clang-tidy reports existing diagnostics; none remain on changed lines. - Behavior changed: Yes, configured eventless funnel states reject incompatible merges. - Does this need documentation: No, the parameter contract is described in the PR release note.
|
/review |
There was a problem hiding this comment.
Requesting changes on exact head 21892a505bf8789c96f101531c7cfb6c9d55364c. I found four new issues: a finite-level reservoir state loses its configuration when every sample is NaN, and legacy reset states for Window Funnel V2, Histogram, and TopN acquire incompatible configured-state semantics on a new reader. The review converged after three complete normal/risk rounds; no user-provided focus narrowed the review.
Two unresolved blockers already have inline threads and are intentionally not duplicated: EMA zero half-decay identity and multi-distinct group-concat bypass.
Checkpoint conclusions:
- Goal and proof: The compatibility goal is only partially met. Added compatible/mismatch tests cover many ordinary cases, but they miss finite-level/all-NaN reservoir states and old-writer add-then-reset bytes consumed by a new reader.
- Scope: The 23-file change is broad but coherent around AggState parameter validation, FE legality, tests, and EMA documentation. No unrelated source change was identified.
- Concurrency and locking: No threads, shared synchronization, or lock protocols change. Aggregate state ownership remains engine-controlled; the defects are lifecycle/wire-compatibility issues, not data races.
- Lifecycle and static initialization: Reset, serialization, deserialization, merge, and analytic empty-frame lifecycles were traced. The three legacy-reset findings are lifecycle defects. No new static/global initialization or lifetime cycle appears.
- Configuration items: No Doris configuration item or dynamic configuration behavior changes.
- Compatibility and rolling upgrades: Nominal field layouts remain unchanged, and sequence/V2 framing is otherwise preserved, but unchanged Histogram/TopN headers and inferred V2 initialization reinterpret old reset identities; these are rolling-upgrade blockers.
- Parallel paths: Ordinary,
_state,_combine,_merge,_union, analytic, nullable, batch/vector, alias, scalar/array/weighted, V1/V2, bitmap, and DISTINCT-related paths were checked. The known multi-distinct group-concat bypass remains covered by its existing thread; no additional bypass survived review. - Special conditions and similar paths: Fresh, configured-empty, contributing, reset, sentinel-colliding, NaN/infinity, empty-array, zero-weight/capacity, and both merge orders were checked. The reservoir payload-empty test is the remaining distinct sentinel/configuration defect.
- Test coverage and correctness: BE/FE/regression additions exercise broad positive and negative behavior, but lack the four exact triggers in the inline findings. No reviewer build or test was run because the review prompt prohibits it. The author reports an ASAN build, 58 related unit tests, and three regression suites passing. Current CI has one CheckStyle failure caused by JUnit 4 imports in
fe/fe-core/src/test/java/org/apache/doris/cloud/rpc/VersionHelperTest.java, outside this PR's authoritative changed-file list, so it is not a finding against this patch. - Observability: Compatibility failures are deterministic exceptions with adequate parameter context where applicable; no new metrics or logging are required.
- Transactions, persistence, and data writes: No transaction protocol, catalog persistence, storage write, atomicity, or crash-recovery path changes. Persisted AggState bytes are in scope and are the source of the three mixed-version findings.
- FE/BE transmission: No new Thrift/session variable or cross-process configuration field is introduced. FE legality and BE function/state behavior were checked for naming/type consistency.
- Performance: New checks are bounded constant-time comparisons except existing-size bitmap key membership; no material performance regression was identified.
- Other: Validation generally occurs before payload mutation and wrapper exception propagation/null suppression are preserved, apart from the reservoir fast-path issue described inline. No security-sensitive behavior is in scope.
|
|
||
| void merge(const QuantileReservoirSampler& rhs) { | ||
| level = rhs.level; | ||
| if (rhs.data.empty()) { |
There was a problem hiding this comment.
[P2] Preserve finite configuration for all-NaN reservoir states
add() records input_level before ReservoirSampler::insert() drops NaN, and serialization still writes that finite level, so an all-NaN state is configured even though data.empty() is true. This return silently ignores an incompatible configured RHS; in the reverse order the empty-destination branch overwrites its level. That contradicts the configured-state contract already applied to exact percentile and eventless funnel states, and it affects direct plus serialized merge/union. Please track configuration separately from retained samples, compare levels before the payload-empty fast paths, and cover valid finite levels with all-NaN samples in both operand orders.
| read_var_int(tmp, in); | ||
| sorted = (tmp != 0); | ||
| // Legacy states use 0/1 and retain their configuration even without events. | ||
| initialized = tmp == 2 || window != WINDOW_UNSET || |
There was a problem hiding this comment.
[P1] Preserve identity semantics for legacy reset states
Before this change, V2 reset() cleared only events_list/sorted, so an old BE can serialize a reset identity while stale window/mode values remain in its 0/1 header. This is production-reachable with window_funnel_v2_union(stored_state) OVER (... ROWS ...): the analytic path resets before an empty frame and the non-null _union result serializes the nested state. This inference marks those old reset bytes initialized, so a new BE can spuriously reject a different valid configuration. Legacy tag-0/1 empty states had identity semantics under the old merge logic and are indistinguishable from old all-false states; preserve that legacy meaning (using tag 2 only for new configured-empty states), and add an old add-then-reset writer/new-reader test.
| } | ||
|
|
||
| max_num_buckets = rhs.max_num_buckets; | ||
| if (!max_num_buckets) { |
There was a problem hiding this comment.
[P1] Treat legacy reset histograms as identities
Before this PR, reset() cleared only ordered_map, so an old BE can serialize a reset state as a nonzero bucket count followed by zero elements. This is reachable through histogram_union(stored_state) OVER (... ROWS ...): the analytic path resets before an empty frame and serializes the non-null _union result. A new BE then treats the stale count as configuration here and throws against a different valid Histogram state in either operand order, although the reset state has no buckets. Since every configured non-null Histogram add inserts an entry, recognize the legacy nonzero-count/zero-element encoding as reset identity before comparing parameters, and add old add-then-reset writer/new-reader coverage.
|
|
||
| top_num = rhs.top_num; | ||
| capacity = rhs.capacity; | ||
| if (!top_num) { |
There was a problem hiding this comment.
[P1] Define compatibility for legacy reset TopN states
Old reset() cleared only counter_map, so an old-BE analytic topn*_union empty frame can serialize a reset identity with stale nonzero N/capacity. This new check treats those bytes as configured and can reject a different valid state in either order. Payload emptiness is not enough to repair it: accepted inputs such as topn_array_state(1, 1, 0) establish N=1/capacity=0 but serialize zero retained elements too. Please add old add-then-reset writer/new-reader tests for all TopN variants and introduce a versioned/tagged initialization policy (or another explicit rolling-upgrade rule) that distinguishes reset identities from legitimately configured empty states.
What problem does this PR solve?
Issue Number: N/A
Related PR: N/A
Problem Summary:
AggState types describe argument types but do not include constant parameter values. For example,
topn_mergeover aUNION ALLoftopn_state('a', 1)andtopn_state('a', 3)can combine incompatible states. TopN previously overwrote the destination N/capacity, making results depend on merge order. Other configurable aggregates silently adopted incompatible settings; legacy percentile arrays could index beyond the destination state when quantile counts differed.Reject incompatible configured states with
INVALID_ARGUMENT. Apply parameter compatibility checks to TopN variants, histogram, percentile variants, limited collect, group_concat, intersect_count, exponential moving average, sequence functions and both window_funnel implementations. Initialize approximate percentile digests with the source compression and retain serialized field layouts. V2 funnel serialization uses a backward-compatible sorted-field tag for configured eventless states.The review follow-up establishes these contracts:
_stateand_combinecalls. Both funnel implementations and the default alias are covered.[0, 1]. EMA rejects NaN half-decays during serialization and result production.docs/exponential-moving-average.md.Scope exclusions: linear_histogram changes were removed; its existing constant/finite parameter issues will be handled separately. The multi-distinct group_concat wrapper bypasses nested merge validation and remains deferred to a follow-up. These review threads remain open.
Release note
Merging covered aggregate states with incompatible parameters now returns an error instead of producing incorrect results or risking an out-of-bounds access. collect_set limits and window_funnel window/mode parameters must be constant. NaN reservoir levels and newly produced EMA states/results with NaN half-decay are rejected. All-false window_funnel states retain their configuration and reject incompatible window/mode merges.
Check List (For Author)
test_agg_state_parameters,window_funnel, andwindow_funnel_v2all passed against the local worktree cluster. The latest run passed all three suites with zero failures/skips. Coverage includes 48 new all-false mismatch combinations across V1/V2/default alias, both operand orders and merge/union, plus rejection of varying funnel parameters in ordinary/state/combine calls. The original binary fails the newly added all-false mismatch case because it raises no exception.WindowFunnelParameterTestandPercentileReservoirParameterTest.NOLINTENDinbe/src/core/types.hand existing V2 funnel source/test diagnostics. Local BE startup uses the OpenBLAS build-cache settingUSE_OPENMP=FALSE; that setting is not part of this patch.Check List (For Reviewer who merge this PR)