Skip to content

[improvement](build) Fully exclude OBS/COS deps via --exclude-{obs,cos}-dependencies - #4

Merged
RoanHeNaN merged 12 commits into
masterfrom
feat/disable-huaweicloud
Aug 7, 2026
Merged

RoanHeNaN merged 12 commits into
masterfrom
feat/disable-huaweicloud

Conversation

@RoanHeNaN

@RoanHeNaN RoanHeNaN commented Jul 28, 2026 •

Copy link
Copy Markdown
Owner

What

Turn the existing --exclude-obs-dependencies and --exclude-cos-dependencies build flags into a full exclusion of the respective cloud provider (Huawei OBS / Tencent COS): when passed, nothing from that provider is resolved, compiled, or bundled into the FE binary.

Why

The two flags were introduced in apache#58071 as a scope=provided downgrade. That only kept the jars out of the shipped artifact — Maven still resolved and downloaded them at build time. In particular com.huaweicloud:hadoop-huaweicloud:3.1.1-hw-46 is published only on Huawei's own Maven repo (repo.huaweicloud.com). Environments that cannot or must not reach it (corporate proxies blocking the host, or compliance constraints forbidding Huawei/Tencent artifacts) still could not build the FE, because resolution was unconditional. The flags now do what their names imply.

How

Active-by-default Maven profiles obs / cos, deactivated via -Ddisable.obs=true / -Ddisable.cos=true, wrap every touch point:

  • fe-core: the fe-filesystem-obs / fe-filesystem-cos test couplings.
  • hadoop-deps: the hadoop-huaweicloud dependency and the Huawei OBS Maven repository.
  • fe-filesystem: the fe-filesystem-obs / fe-filesystem-cos modules (which transitively pull the Huawei / Tencent SDKs).

build.sh maps --exclude-obs-dependencies → -Ddisable.obs=true and --exclude-cos-dependencies → -Ddisable.cos=true (replacing the old *.dependency.scope=provided mapping), and drops the corresponding provider from both the -pl reactor list and the filesystem plugin packaging loop, so the reactor and the dist layout stay consistent when a provider is excluded.

Behaviour

  • Default builds are unchanged — the profiles are active unless a flag is passed.
  • sh build.sh --fe --exclude-obs-dependencies builds the FE with no Huawei artifact resolved and no OBS code compiled or bundled; same for --exclude-cos-dependencies and Tencent COS.

Testing

  • mvn help:active-profiles on fe-core, fe-filesystem, and hadoop-deps: obs/cos active by default, and absent with -Ddisable.obs=true / -Ddisable.cos=true.
  • Confirmed fe-core has no compile-time import of the OBS/COS provider classes (loaded via SPI/reflection), so compile and test-compile stay green when the profiles are off.
  • xmllint on all changed poms and bash -n build.sh pass.

🤖 Generated with Claude Code

lsy3993 and others added 12 commits August 6, 2026 15:35
apache#66342)

### What problem does this PR solve?

Issue Number: N/A

Problem Summary: Session and global variable annotations currently store
both Chinese and English descriptions as a two-element array. The
Chinese description is unused and the array shape makes callers
cumbersome. This change keeps only the English description, updates the
VarAttr annotation type, and adjusts the affected unit tests.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - `./run-fe-ut.sh --run org.apache.doris.qe.SessionVariablesTest`
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Problem Summary:

Snapshot tasks persisted the complete offset provider after every split
commit, producing frequent edit logs while the snapshot state kept
growing. The previous default snapshot split size also produced too many
small snapshot tasks. Natural terminal transitions needed explicit
persistence so finished jobs replay with their terminal metadata.

This change:
- Limits snapshot offset edit logs with a mutable 300-second interval
while keeping binlog offset commits immediate.
- Increases the default snapshot split size from 8,096 to 40,960 rows,
while preserving explicitly configured values.
- Removes obsolete snapshot recovery state after the first committed
binlog offset for FROM-TO and CDC stream TVF jobs.
- Persists natural FINISHED transitions and restores terminal replay
fields and callbacks consistently.
- Adds focused unit coverage and a MySQL snapshot-only FE restart
regression case.
…-job config (apache#66541)

### What problem does this PR solve?

Issue Number: close #xxx

Related PR: apache#66342, apache#66238

Problem Summary:

master does not compile. `fe-common` fails at

```
Config.java:[1177,65] annotation value not of an allowable type
```

apache#66342 retyped `ConfigBase.ConfField.description()` from `String[]` to
`String` and rewrote all 419 call sites accordingly. apache#66238 landed
shortly after with

```java
@ConfField(mutable = true, masterOnly = true, description = {
        "Minimum interval in seconds between snapshot offset persistence operations"})
public static int streaming_job_snapshot_offset_persist_interval_sec = 300;
```

written against the older `String[]` signature. The two changes are
textually disjoint, so git merges them without a conflict and neither
pull request could see the other — each was green on its own base.

The seven further errors reported in the same module are secondary. The
bad annotation value aborts the annotation-processing round, so lombok
never contributes its generated members, and `@Slf4j`'s `log` plus
`@AllArgsConstructor`/`@Data`'s constructors go missing:

```
DiskUtils.java:[70,13] cannot find symbol
JobBaseConfig ... constructor cannot be applied to given types
AbstractSourceSplit ... constructor cannot be applied to given types
```

All seven disappear once the annotation value is fixed; nothing else in
the tree needed a change.

This is the only remaining array-form description under `fe/` (`grep
-rnE 'description\s*=\s*\{'`), and the wrapped-argument layout matches
the neighbouring long descriptions such as
`max_create_table_timeout_second`.

`ConfigTest.testConfFieldDescriptionsAreEnglishStrings`, the guard
apache#66342 added, reflects over the annotation at runtime, so it cannot
catch a compile-time signature mismatch; it passes here because the text
is already English.

Verified on a clean checkout of master `4e3c1b84dd5`:

- reproduced the failure before the change, and confirmed all eight
errors are gone after it
- full FE reactor `mvn test-compile` (checkstyle included): **74/74
modules SUCCESS**
- `fe-common` module tests: **157 tests, 0 failures, 0 skipped**,
including `ConfigTest.testConfFieldDescriptionsAreEnglishStrings`
- the tests both colliding PRs added —
`StreamingInsertJobOffsetPersistenceTest`,
`JdbcSourceOffsetProviderOffsetTest`, `SessionVariablesTest`: **39
tests, 0 failures, 0 skipped**
Problem Summary:

The test inserts five batches, exactly matching the default cumulative
compaction threshold. The partition can become visible in FE before its
latest visible version reaches BE, so manual cumulative compaction may
return `E-2000: _input_rowsets is empty`. The generic helper ignored
E-2000, leaving no tracker record for the test to verify.

The fix targets the BE hosting the tablet, retries only E-2000 while
requesting tablet reports, and waits for that MANUAL cumulative tracker
record to reach FINISHED or FAILED. The case still uses the original
five batches, and other trigger errors fail immediately.
…ge (apache#66337)

Related PR: apache#63112

Problem Summary: This PR takes over apache#63112 and adds explicit local and
Cloud unit-test coverage.

While a schema change target tablet is `TABLET_NOTREADY`, cumulative
compaction should merge only older rowsets and leave the newest 10
versions unmerged. The filter used the inverse comparison, skipping
older rowsets and selecting the newest versions. A compaction output
could then cross the base tablet's maximum version and prevent
incremental schema-change conversion with `VERSION_ALREADY_MERGED`.

This change reverses the comparison in both local and Cloud size-based
cumulative compaction policies. The new tests verify that versions 2
through 10 are selected and versions 11 through 20 remain unmerged.

### Release note

Fix schema changes that could fail when cumulative compaction merged the
latest versions on the new tablet.
…he#66534)

### What problem does this PR solve?

Issue Number: None

Related PR: apache#66328

Problem Summary:

`WorkloadGroupManagerTest` creates and initializes a `SpillFileManager`
for every test case. Initialization starts a background spill GC thread,
but the fixture did not stop or delete the manager during teardown. Each
subsequent test overwrote the global manager pointer, leaving the
previous manager and its GC thread alive.

The leaked threads could continue accessing process-wide test utilities
during static destruction and trigger an ASAN heap-use-after-free when
the test binary exited. They could also interfere with tests that use
global spill GC debug points.

This change releases workload-group-owned query resources first, then
stops and joins the spill GC thread, deletes the manager, and finally
removes the temporary spill directory. This matches the lifecycle used
by other spill-related test fixtures.

### Release note

None

### Check List (For Author)

- Test
    - [ ] Regression test
    - [x] Unit Test
- `GTEST_REPEAT=10 ./run-be-ut.sh --run
--filter='WorkloadGroupManagerTest.*:DebugPointsTest.AddTest:SpillFileTest.RetryPreservesDirectoryQueuedAfterPendingDrain'
-j 8`
    - [ ] Manual test (add detailed scripts or steps below)
    - [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
        - [ ] Previous test can cover this change.
        - [ ] No code files have been changed.
        - [ ] Other reason

- Behavior changed:
    - [x] No. This only corrects test fixture resource teardown.
    - [ ] Yes.

- Does this need documentation?
    - [x] No.
    - [ ] Yes.

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
…66442)

Problem Summary: Nullable TopN comparisons can return a `ColumnConst`
that wraps a `ColumnNullable` result, especially when an input batch is
entirely NULL. The previous normalization only handled a top-level
`ColumnNullable`, so the constant nullable result could reach filter
execution without converting SQL NULL into the NULLS FIRST or NULLS LAST
filter decision.

This change recursively normalizes constant wrappers, collapses nullable
Boolean results according to the TopN null ordering, preserves the
constant shape, and aligns the unit-test expression metadata with
production nullability. The new unit test covers both NULLS FIRST and
NULLS LAST through `execute_column` and `execute_filter`.
…e#66345)

### What problem does this PR solve?

This PR fixes correctness and memory-usage edge cases in Iceberg MVCC
writes and nested schema evolution:

- An explicitly empty read could be reinterpreted as the latest snapshot
after a concurrent first append. A MERGE could then miss the concurrent
row and silently insert a duplicate.
- Write-schema validation used the INSERT column subset instead of the
complete bind-time target schema, rejecting valid partial and
static-partition INSERTs while weakening ordinal drift checks.
- Iceberg v3 row-lineage metadata columns could be mistaken for
user-schema drift.
- Parent null-mask handling for nested ARRAY/MAP values could scan
descendant entry maps and allocate entry-sized scratch even when the
inherited mask was absent or all-clear.
- Strict cast failure semantics were incorrectly used to mark a struct
field physically non-nullable even though BE narrowing casts still
return `ColumnNullable`.
- Nested DESCRIBE comments were not emitted as round-trippable SQL
literals.

The fix preserves the explicit empty-snapshot marker through scan
planning and keeps RowDelta conflict validation anchored at table
creation. In the concurrent empty-table MERGE/INSERT window, MERGE keeps
reading the original empty view and its commit conflicts with the
concurrent first append instead of producing duplicate data.

Write planning now carries an immutable complete target-schema snapshot
separately from the write subset, validates column order, type, and
Iceberg field id, and excludes engine-generated row-lineage metadata
from the user-schema comparison. Nested collection alignment gates
descendant scans on an inherited mask that can actually hide a row, and
struct constructors preserve the physical nullability of cast results.

### Release note

Fix Iceberg empty-snapshot concurrency, partial-write schema validation,
nested collection memory usage, cast-result nullability, and nested
DESCRIBE comment rendering.

### Check List (For Author)

- Test
  - [x] Regression tests
  - [x] Unit tests
- Behavior changed:
- [x] Yes. Correctness and memory-usage fixes for the affected edge
cases.
- Does this need documentation?
  - [x] No.

### Tests

- Iceberg write-plan tests: 50 passed
- Iceberg concurrent empty-snapshot barrier test: passed
- BE `TableReaderTest`: 95 passed
- FE connector sink, binding, executor, and struct tests: 38 passed
- FE Checkstyle: 0 violations
- Clang Formatter 16: passed
- `git diff --check`: passed
…and make each key have one reader (apache#66507)

### What problem does this PR solve?

Issue: apache#65185

Problem Summary:

Every connector had grown its own way of dealing with catalog
properties. Some kept a
`XxxConnectorProperties` constant class and parsed values at each read
site; some had no property
class at all and inlined the key names; iceberg and paimon had *two*
readers for the same keys — a
typed holder used for CREATE-time validation, and a separate raw-map
scan with its own copy of the
alias arrays used to actually build the catalog. Nothing kept those two
in agreement, so an alias
priority or a blank-value rule could be validated one way and assembled
another.

This PR gives every connector the same four-way split, and makes each
key have exactly one reader.
No new SPI: `ConnectorPluginSurfaceTest` is untouched throughout.

### Where things live now

**A — `<Xxx>CatalogProperties`: what a user writes in `CREATE
CATALOG`.**
One class per connector, in the connector module. Fields carry
`@ConnectorProperty` with the alias
list, so a key name and its aliases are declared exactly once, and
`ConnectorPropertiesUtils` binds
them. This is what the connector, the metadata layer and the scan/write
planners read — none of them
touch the raw map for a key this class declares.

The two entry points are deliberately not interchangeable, and this is
the part most worth reviewing:

- `of(Map)` **binds and derives, and never throws.** It runs at CREATE,
at ALTER validation, and on
every connector build — including the lazy rebuild after an FE restart.
A rule placed here that a
live catalog violates does not fail at review time; it fails months
later, as a catalog that stops
  coming back after a restart.
- `checkCreateTimeOnlyRules()` carries everything that judges, and only
the provider calls it, from
`validateProperties` — one line in most connectors. Unknown keys are
never rejected: the same map
carries engine keys and storage keys, and `ALTER CATALOG` can only
overwrite a key, never remove
  one, so a rejected unknown key could not be repaired.

**B — `<Xxx>Conf`: deployment-level settings.** The keys of the plugin's
own `<name>.conf`, each
falling back to the `fe.conf` key it used to live under. Static
accessors (`driversDir(context)`,
`metastoreClientTimeoutSecond(context)`, …) rather than a bound object,
because these belong to the
deployment and not to any one catalog. Only the connectors that actually
have such settings have one.

**Per-flavor `*MetaStoreProperties` (iceberg / paimon): the keys of one
metastore backend.**
These already existed in `fe-connector-metastore-{iceberg,paimon}` but
described themselves as
"validation only" while the connector re-scanned the same keys to build
the catalog. They are now the
single declaration: they gained the fields the assembly needed and the
getters it reads, and the
factories consume the bound holder instead of `firstNonBlank(props,
ALIASES)`. The alias arrays that
duplicated them are deleted. The metastore modules stay SDK-free — they
expose neutral getters and
maps; engine-SDK option assembly stays in the connector factory.

Splitting by flavor also splits the "annotation count == key count"
invariant: the connector-level
holder declares the flavor-independent keys, each backend holder
declares its own.

**F/G — literals the assembly emits, and mode enums.** Option keys and
values that a connector
*writes* (rather than a user setting them) are private to the class that
writes them —
`IcebergCatalogFactory` for the iceberg SDK dialect, and so on. Cache
key names sit next to the cache
they configure. In several places these had drifted into two spellings
of the same string in two
files; folding them removed those duplicates.

### Responsibilities, end to end

| Layer | Reads | Writes |
|---|---|---|
| `<Xxx>ConnectorProvider` | — | `validateProperties` →
`of(props).checkCreateTimeOnlyRules()` |
| `<Xxx>CatalogProperties` | the raw map, once | typed getters; the
derived flavor |
| `*MetaStoreProperties` | the raw map, once, for its own backend |
typed getters; neutral conf maps |
| `<Xxx>CatalogFactory` | the bound holders (+ raw map only for copy-all
/ prefix passthroughs) | the engine-SDK option map |
| `<Xxx>Conf` | `ConnectorContext` conf + environment | — |
| connector / metadata / scan / write | the bound holders | — |

The raw map is still read in three legitimate shapes, each commented
where it happens: copy-all
passthrough into the SDK options, whole-namespace forwarding (`fs.` /
`dfs.` / `hadoop.`, `paimon.`,
`jdbc.`), and alias sets that span namespaces and so belong to no single
flavor (the S3 region
aliases, the AWS credentials-provider mode).

### Verification

Unit tests only — this is a refactor with no intended behavior change,
and the guard against
unintended change is a set of whole-map snapshot tests added *before*
each rework: paimon 8 cases and
iceberg 20 cases assert the ENTIRE catalog option map, one per flavor
and per emission branch. Both
SDKs silently ignore an option they do not recognize, so a dropped or
misspelled key does not throw —
it produces a catalog that connects with different settings than the
operator asked for. Those tests
stay in the tree afterwards as the permanent guard that the holder and
the assembly agree.

Every touched module, run together at the final commit with
`-Dmaven.build.cache.enabled=false` (the build cache otherwise reports a
stale green):

| module | tests | module | tests |
|---|---|---|---|
| iceberg | 1215 (5 skip) | hudi | 203 |
| paimon | 532 (1 skip) | adbc | 200 |
| hive | 411 | maxcompute | 147 (1 skip) |
| jdbc | 222 | es | 118 |
| connector-spi | 140 | trino | 55 |
| hms shared lib | 107 | metastore-{iceberg,paimon,spi,api} | 77 |
| foundation | 161 | cache framework | 33 |

**3780 tests, 0 failures, 0 errors, 7 skips** — every skip is a
pre-existing live-connectivity test
gated on environment variables, and each was already skipped before this
PR. checkstyle clean;
`ConnectorPluginSurfaceTest` green.

### Behavior changes

Small, and all on inputs that are already degenerate. Full per-connector
tables are in the commit
messages; the classes of change are:

1. **Values are trimmed.** The property binder trims and the old
hand-written scans did not, so a
`uri` written with a trailing space was already *validated* trimmed
while the catalog was *built*
from the untrimmed string. The two now agree. For paimon HMS this
removed an existing internal
inconsistency (HiveConf got the trimmed value, the paimon `Options` got
the raw one).
2. **Blank now means unset**, where old code used `containsKey` /
`getOrDefault`. Affects e.g.
`iceberg.rest.view-enabled = ""` (was false, now its default true) and
`external_catalog.name = ""` (was an empty namespace level, now absent).
3. **Values the FE interprets are now sent downstream interpreted.**
This fixed three real bugs where
the FE parsed a value and then forwarded the unparsed original — the ES
`http_ssl_enabled` payload
   to BE, the hive `uri` shorthand, and the trino `connector.name`.

Numeric keys were audited per connector and deliberately left as Strings
wherever the value is
forwarded verbatim to an engine SDK, so that a catalog created with a
value the SDK tolerates keeps
building; the JDBC connection-pool knobs are the one place where the
strict/lenient choice is made
per key, with the reasoning in that commit.

### Release note

None

### Check List (For Author)

- Test
    - [x] Unit Test
    - [ ] Regression test
    - [ ] Manual test (add detailed scripts or steps below)
    - [ ] No need to test or manual test. Explain why:

- Behavior changed:
- [x] Yes. Whitespace around property values is now trimmed; an
explicitly blank value now reads
as unset rather than as an empty string; and three keys the FE
interprets are now forwarded in
their interpreted form. Details per connector are in the individual
commit messages.

- Does this need documentation?
    - [x] No.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pache#66515)

### What problem does this PR solve?

Issue Number: close #xxx

Related PR: #xxx

Problem Summary:

### Release note

None

### Check List (For Author)

- Test <!-- At least one of them must be included. -->
    - [ ] Regression test
    - [ ] Unit Test
    - [ ] Manual test (add detailed scripts or steps below)
    - [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
        - [ ] Previous test can cover this change.
        - [ ] No code files have been changed.
        - [ ] Other reason <!-- Add your reason?  -->

- Behavior changed:
    - [ ] No.
    - [ ] Yes. <!-- Explain the behavior change -->

- Does this need documentation?
    - [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
apache/doris-website#1214 -->

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
…s}-dependencies

Turn the existing `--exclude-obs-dependencies` / `--exclude-cos-dependencies`
flags from a scope=provided downgrade (which still resolved the jars from
their remote repositories, only skipping the bundling step) into a full
exclusion: the dependencies are no longer resolved, compiled, or bundled.

This is driven by active-by-default Maven profiles (`obs` / `cos`, deactivated
via -Ddisable.obs=true / -Ddisable.cos=true) that wrap every touch point:

- fe-core: the fe-filesystem-obs / fe-filesystem-cos test couplings.
- hadoop-deps: the hadoop-huaweicloud dependency and the Huawei OBS repository.
- fe-filesystem: the fe-filesystem-obs / fe-filesystem-cos modules.

build.sh maps the flags to -Ddisable.obs=true / -Ddisable.cos=true and drops
the corresponding provider from both the -pl reactor list and the plugin
packaging loop, so the build stays consistent when a provider is excluded.

Default builds are unchanged (profiles active unless the flag is passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@RoanHeNaN
RoanHeNaN force-pushed the feat/disable-huaweicloud branch from 1c700d5 to 52d3bc4 Compare August 7, 2026 02:02
@RoanHeNaN RoanHeNaN changed the title [feature](build) Add --disable-huaweicloud build option [improvement](build) Fully exclude OBS/COS deps via --exclude-{obs,cos}-dependencies Aug 7, 2026
@RoanHeNaN
RoanHeNaN merged commit 34fa5d2 into master Aug 7, 2026
16 of 20 checks passed
RoanHeNaN pushed a commit that referenced this pull request Aug 21, 2026
…projects (apache#66531)

### What problem does this PR solve?

Related PR: apache#63690

Problem Summary:

Eager aggregation pushdown may fail when different aggregate functions
become
the same expression after passing through a Project. report error:

```text
2026-08-06 04:26:54,285 INFO (mysql-nio-pool-14|325) [PushDownAggregation.visitLogicalAggregate():280] PushDownAggregation failed: Cannot invoke "org.apache.doris.nereids.trees.expressions.NamedExpression.toSlot()" because "namedExpression" is null
        at org.apache.doris.nereids.rules.rewrite.eageraggregation.EagerAggRewriter.visitLogicalProject(EagerAggRewriter.java:718)
        at org.apache.doris.nereids.rules.rewrite.eageraggregation.EagerAggRewriter.visitLogicalProject(EagerAggRewriter.java:90)
        at org.apache.doris.nereids.trees.plans.logical.LogicalProject.accept(LogicalProject.java:160)
        at org.apache.doris.nereids.rules.rewrite.eageraggregation.EagerAggRewriter.visitLogicalUnion(EagerAggRewriter.java:582)
        at org.apache.doris.nereids.rules.rewrite.eageraggregation.EagerAggRewriter.visitLogicalUnion(EagerAggRewriter.java:90)
        at org.apache.doris.nereids.trees.plans.logical.LogicalUnion.accept(LogicalUnion.java:155)
```

For example:

```text
Aggregate: SUM(x)#4, SUM(y)#5
  Union All
    Project: 0 AS x, 0 AS y
      Join
```

After pushing the aggregates through the Project, both functions
become`SUM(0)`:

```text
functions: [SUM(0), SUM(0)]
aliasMap:  SUM(0) -> #5
```

Because `aliasMap` uses expression equality, only one entry is retained.
The Project still tries to read both `#4` and `#5` from
`BilateralState`,
causing a null lookup.

This PR deduplicates the child aggregate and records the ExprId mapping:

```text
child aggregate: SUM(0) -> apache#8
ExprId mapping:  #4 -> apache#8, #5 -> apache#8
```

The Project then restores both required outputs:

```text
slot#8 AS slot#4
slot#8 AS slot#5
```

When no aggregate functions are merged, the original ExprIds are reused
to
avoid unnecessary aliases.

The same fix also covers cases such as:

```text
Project: a#1 AS x, a#1 AS y
```

where `SUM(x)` and `SUM(y)` both become `SUM(a#1)` after pushdown.

### Release note

None


### Check List (For Author)

- Test
    - [x] Regression test
        - `query_p0/eager_agg/bilateral_eager_agg`
- Covers two aggregate functions that become the same function after
          Project pushdown.
    - [ ] Unit Test
    - [ ] Manual test
    - [ ] No need to test or manual test.

- Behavior changed:
    - [x] Yes.
        - Prevents eager aggregation pushdown from failing when multiple
          aggregate functions become identical after Project rewriting.
- `eager_aggregation_mode=1` can force eligible pushdown after a UNION.

- Does this need documentation?
    - [x] No.
    - [ ] Yes.

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
RoanHeNaN pushed a commit that referenced this pull request Sep 11, 2026
Problem Summary: Streaming `ROWS` window aggregates retain state across
frame evaluations. The eviction path previously considered only whether
buffered blocks had been emitted, so it could erase either the outgoing
row needed by a bounded sliding frame or the next unread row needed by
an `UNBOUNDED PRECEDING ... N PRECEDING` frame. After rebasing, negative
partition and outgoing positions could allow a nullable aggregate to
access its null map out of bounds. Evicting either kind of required row
could also produce incorrect aggregate results.

Root cause: `_remove_unused_rows()` did not account for the earliest row
required by the next ROWS frame evaluation, and
`BoundaryPose::remove_unused_rows()` allowed retained-column coordinates
to become negative.

This change defers block-aligned eviction when the candidate prefix
contains either `frame_start - 1`, the outgoing row required by a
bounded sliding update, or the next unread row required by an `UNBOUNDED
PRECEDING ... N PRECEDING` frame. It also rebases partition and order
boundaries to nonnegative physical-column coordinates. The BE unit
coverage exercises both ROWS executors across eviction boundaries and
verifies boundary rebasing.

Observed ASAN failure before this change (`output/be/log/be.out`):

```text
ERROR: AddressSanitizer: heap-buffer-overflow
READ of size 1
    #0 doris::AggregateFunctionNullUnaryInlineV2<...>::execute_function_with_incremental(...)
       be/src/exprs/aggregate/aggregate_function_null_v2.h:595
    #1 doris::AggFnEvaluator::execute_function_with_incremental(...)
       be/src/exprs/vectorized_agg_fn.cpp:334
    #2 doris::AnalyticSinkLocalState::_execute_for_function<true>(...)
       be/src/exec/operator/analytic_sink_operator.cpp:385
    #3 doris::AnalyticSinkLocalState::_get_next_for_sliding_rows(...)
       be/src/exec/operator/analytic_sink_operator.cpp:203
    #4 doris::AnalyticSinkLocalState::_execute_impl(...)
       be/src/exec/operator/analytic_sink_operator.cpp:358
    #5 doris::AnalyticSinkOperatorX::sink_impl(...)
       be/src/exec/operator/analytic_sink_operator.cpp:757
SUMMARY: AddressSanitizer: heap-buffer-overflow in
doris::AggregateFunctionNullUnaryInlineV2<...>::execute_function_with_incremental(...)
```


### Release note

None

### Check List (For Author)

- Test <!-- At least one of them must be included. -->
    - [ ] Regression test
    - [ ] Unit Test
    - [ ] Manual test (add detailed scripts or steps below)
    - [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
        - [ ] Previous test can cover this change.
        - [ ] No code files have been changed.
        - [ ] Other reason <!-- Add your reason?  -->

- Behavior changed:
    - [ ] No.
    - [ ] Yes. <!-- Explain the behavior change -->

- Does this need documentation?
    - [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
apache/doris-website#1214 -->

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
RoanHeNaN pushed a commit that referenced this pull request Sep 29, 2026
…erived group by projection as a group by key (apache#67362)

### What problem does this PR solve?

Problem Summary:
Given a sync materialized view on `sync_tz_base`:

```sql
CREATE MATERIALIZED VIEW sync_tz_day AS
SELECT date_trunc(ts, 'day') AS day_ts, sum(v) AS day_sum
FROM sync_tz_base WHERE ts IS NOT NULL
GROUP BY date_trunc(ts, 'day');
```

the following query fails during planning with an NPE:

```sql
SELECT CAST(date_trunc(ts, 'day') AS STRING) AS day_ts, SUM(v)
FROM sync_tz_base WHERE ts IS NOT NULL
GROUP BY date_trunc(ts, 'day');
```

```
java.lang.NullPointerException: Cannot invoke "org.apache.doris.analysis.Expr.getChildren()" because "root" is null
	at org.apache.doris.analysis.Expr.extractSlots(Expr.java:173)
	at org.apache.doris.nereids.glue.translator.PhysicalPlanTranslator.visitPhysicalProject(PhysicalPlanTranslator.java:2173)
```

**Root cause**

In `AbstractMaterializedViewAggregateRule.aggregateRewriteByView`, the
group by keys of the rewritten aggregate were collected from the query
**top plan** output expressions. For a `project -> aggregate` structure,
`topPlanSplitToGroupAndFunction` classifies the derived projection
`cast(date_trunc(ts, 'day') AS STRING)` as a group-by expression,
because it is derived from the real group key. This derived projection
was then wrongly added as a group by key of the rewritten aggregate:

```
finalGroupExpressions = [cast(day_ts#7 as TEXT) AS apache#9, day_ts#7]
finalOutputExpressions = [cast(day_ts#7 as TEXT) AS apache#9, sum(day_sum#8) AS apache#10]
```

This led to two problems:

1. A redundant group by key `cast(day_ts#7 as TEXT)` which is only a
projection of the real group key `day_ts#7`.
2. The real group key `day_ts#7` was added by the group-by compensation
logic but was **not** present in the aggregate output expressions, so
the top project referenced a slot that the physical aggregate never
produced, causing the `"root" is null` NPE during physical plan
translation.

**Fix**

The rewritten aggregate is now built directly from the query bottom
aggregate:

- The group by keys are the query bottom aggregate's group by
expressions rewritten against the MV scan (always correct, so the
previous group-by compensation is no longer needed).
- The aggregate output contains the rewritten group keys and the
rolled-up aggregate functions.
- The query top plan output expressions — including derived projections
of group keys such as `cast(date_trunc(ts, 'day') AS STRING)` — are
recomputed by a `LogicalProject` above the rewritten aggregate when they
cannot be produced by the aggregate directly.

After the fix the rewritten plan is valid and the query returns correct
results:

```
Project [cast(day_ts#7 as TEXT) AS day_ts#3, sum(day_sum#8) AS SUM(v)#4]
  Aggregate [group by [day_ts#7], output [day_ts#7, sum(day_sum#8)]]
    MVScan(sync_tz_day)
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants