This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 168d0777833 [fix](build) Unbreak master: stale unity-skip entry (BE)
and dropped count probe (FE) (#66831)
168d0777833 is described below
commit 168d07778338761de5da5f0e97c36eee1e265b4b
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Aug 17 20:29:25 2026 +0800
[fix](build) Unbreak master: stale unity-skip entry (BE) and dropped count
probe (FE) (#66831)
### What
Two independent breakages that each make **current master fail to
build** — one in BE configure, one in FE compile.
They share a shape: a pair of PRs that never conflict textually, merge
cleanly, and only break once combined, so each PR's own pipeline was
green.
| | Breakage | Colliding PRs |
|---|---|---|
| BE | `cmake` configure aborts | #66052 moved a file, #66789 made a
dangling unity-skip entry fail loud |
| FE | `fe-connector-iceberg` does not compile | #66778 deleted
`getCountFromSnapshot()`, #66413 added a caller for it |
CI merges each PR into the latest master before building, so **every PR
pipeline that picks up current master is red** on one or both.
---
## 1. BE — stale `STORAGE_UNITY_SKIP` entry for a moved file
Remove the stale `STORAGE_UNITY_SKIP` entry (and its comment block) for
`compaction/collection_statistics.cpp`, which no longer exists.
### Why — master configure is currently broken
#66052 moved `storage/compaction/collection_statistics.{cpp,h}` to
`storage/index/inverted/similarity/` (rewritten), but left behind the
unity-skip entry that #66789 had added for the old path. The fail-loud
validation introduced by #66789 turns a dangling skip entry into a
configure-time error — which is exactly what it is designed to catch (a
skip list rotting after a file move), so BE configure on current master
fails immediately:
```
CMake Error at CMakeLists.txt:1002 (message):
unity skip entry does not exist (renamed or moved?):
.../be/src/storage/compaction/collection_statistics.cpp
```
#66826, #66824, #66819, #66820 were the first hits — same error on
multiple independent agents.
### Why deletion (not a path update) is correct
The old entry existed because the old `collection_statistics_test`
`#include`d the `.cpp` into a second TU (unity batching would then
produce a duplicate definition at link time). The rewritten file at the
new location is not `#include`d by any test (`grep -rn
'collection_statistics.cpp' be/test/` is empty on master), so the new
path needs no skip entry.
### Verification
- Full BE build from a clean tree at master + this change (clang20 /
macOS arm64, unity=ON, PCH=ON): configure passes the skip-list
validation and the build compiles. (The same tree without this change
fails configure with the error above.)
- Timeline note: #66052's last green CI round presumably predates
#66789's validation landing (2026-08-16), which is how the dangling
entry slipped through.
---
## 2. FE — the metadata-only COUNT(\*) probe calls a deleted method
#66778 replaced the snapshot-summary COUNT(\*) pushdown with a
manifest-derived count and deleted `getCountFromSnapshot()`.
`canServeMetadataOnlyCount()`, added by #66413, still calls it, so FE
compilation fails:
```
[ERROR] .../connector/iceberg/IcebergScanPlanProvider.java:[505,16] cannot
find symbol
[ERROR] symbol: method
getCountFromSnapshot(org.apache.iceberg.TableScan,org.apache.doris.connector.spi.ConnectorSession)
[ERROR] location: class
org.apache.doris.connector.iceberg.IcebergScanPlanProvider
```
### Why re-express the probe instead of restoring the method
Bringing `getCountFromSnapshot()` back would reintroduce precisely what
#66778 removed: a query result derived from optional, writer-provided
snapshot summary fields.
The probe is rebuilt in #66778's own terms instead. It reuses that PR's
delete gate and additionally requires the data manifests to carry
aggregate row counters, so the answer is proved from the manifest list
alone — `O(manifests)`, no data-file enumeration, which is what a probe
running before planning can afford.
Manifest lists that omit those aggregates now answer `false`, where
count planning still serves them through its bounded per-file fallback.
A capability probe should under-promise rather than over-promise; the
planner itself is untouched.
### Verification
- `mvn package` over the full FE reactor (74 modules): all green. This
also confirms no second semantic break is hiding behind the first — CI's
maven stops at `fe-connector-iceberg` and never reaches the rest.
- `IcebergScanPlanProviderTest`: 153 tests, 0 failures, including a new
case pinning that the probe follows the same delete gate as count
planning.
- FE checkstyle: 0 violations.
### Release note
None (both fixes only restore a buildable master; no user-visible
behavior change).
### Check List (For Author)
- Test
- [x] Regression test — not applicable: neither change alters query
behavior
- [x] Unit test: `IcebergScanPlanProviderTest` (153 tests, 0 failures)
- [x] Manual test: full BE configure/build and full FE reactor build
- Behavior changed: No
- Does this need documentation: No
---------
Co-authored-by: Claude Fable 5 <[email protected]>
---
be/src/storage/CMakeLists.txt | 7 +----
.../connector/iceberg/IcebergScanPlanProvider.java | 36 ++++++++++++++++++++--
.../iceberg/IcebergScanPlanProviderTest.java | 20 +++++++++++-
3 files changed, 54 insertions(+), 9 deletions(-)
diff --git a/be/src/storage/CMakeLists.txt b/be/src/storage/CMakeLists.txt
index 21e82183f58..306b4815f20 100644
--- a/be/src/storage/CMakeLists.txt
+++ b/be/src/storage/CMakeLists.txt
@@ -55,10 +55,6 @@ pch_reuse(Storage)
# FORMAT_*_ADD_JSON_NODE, RETURN_IF_ERROR_) must not leak into unity siblings
# - the three heaviest template-instantiation TUs (predicate creators) which
# would dominate any batch they join
-# - compaction/collection_statistics.cpp: its test compiles it a second time
-# by #including the .cpp; the test object must shadow a never-pulled archive
-# member, but a unity batch is pulled in for its siblings and the linker
-# sees a duplicate definition
set(STORAGE_UNITY_SKIP
${CMAKE_CURRENT_SOURCE_DIR}/index/inverted/inverted_index_compound_reader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/index/inverted/inverted_index_fs_directory.cpp
@@ -69,8 +65,7 @@ set(STORAGE_UNITY_SKIP
${CMAKE_CURRENT_SOURCE_DIR}/task/engine_clone_task.cpp
${CMAKE_CURRENT_SOURCE_DIR}/predicate/predicate_creator_comparison.cpp
${CMAKE_CURRENT_SOURCE_DIR}/predicate/predicate_creator_in_list_in.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/predicate/predicate_creator_in_list_not_in.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/compaction/collection_statistics.cpp)
+ ${CMAKE_CURRENT_SOURCE_DIR}/predicate/predicate_creator_in_list_not_in.cpp)
if (ENABLE_VARIANT_NESTED_GROUP)
# Out-of-tree module sources swapped into this target: unity hygiene
# unaudited, keep them individual.
diff --git
a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java
b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java
index bd2e90bd646..24c2a5ec0d1 100644
---
a/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java
+++
b/fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java
@@ -497,12 +497,12 @@ public class IcebergScanPlanProvider implements
ConnectorScanPlanProvider {
Optional<ConnectorExpression> filter) {
IcebergTableHandle iceHandle = (IcebergTableHandle) handle;
if (iceHandle.isSystemTable() || filter.isPresent()) {
- // Snapshot summaries describe the whole table and cannot prove a
filtered row count.
+ // A metadata count describes the whole table and cannot prove a
filtered row count.
return false;
}
Table table = resolveTable(session, iceHandle);
TableScan scan = buildScan(table, iceHandle, filter, session);
- return getCountFromSnapshot(scan, session) >= 0;
+ return canProveCountFromManifests(table, scan, session);
}
/**
@@ -1312,6 +1312,38 @@ public class IcebergScanPlanProvider implements
ConnectorScanPlanProvider {
netPositionDeletes ? positionDeleteRows.getAsLong() : 0);
}
+ /**
+ * The capability probe behind {@link #canServeMetadataOnlyCount}: the
delete gate of
+ * {@link #planCountPushdown} plus the requirement that the data manifests
really carry aggregate row
+ * counters. Reading only the manifest list keeps this O(manifests) with
no data-file enumeration, which is
+ * what a pre-planning probe can afford; the price is answering {@code
false} for the older manifest lists
+ * that {@code planCountPushdown} still serves through its bounded
per-file fallback. Never derives the
+ * count from snapshot summary fields — those are writer-provided hints,
not a query result.
+ */
+ private static boolean canProveCountFromManifests(Table table, TableScan
scan, ConnectorSession session) {
+ Snapshot snapshot = scan.snapshot();
+ if (snapshot == null) {
+ // No snapshot (empty table, or a pinned empty snapshot) is an
exact count of 0 without any read.
+ return true;
+ }
+ boolean netPositionDeletes = sessionBool(session,
IGNORE_ICEBERG_DANGLING_DELETE, false);
+ ManifestDeleteState deleteState =
manifestDeleteState(snapshot.deleteManifests(table.io()));
+ if (deleteState == ManifestDeleteState.PRESENT && !netPositionDeletes)
{
+ return false;
+ }
+ OptionalLong positionDeleteRows = deleteState ==
ManifestDeleteState.NONE
+ ? OptionalLong.of(0)
+ : livePositionDeleteRowCount(table, snapshot);
+ if (!positionDeleteRows.isPresent()
+ || (!netPositionDeletes && positionDeleteRows.getAsLong() !=
0)) {
+ return false;
+ }
+ OptionalLong manifestCount =
liveRowCountFromManifests(snapshot.dataManifests(table.io()));
+ return manifestCount.isPresent()
+ && subtractPositionDeleteRows(manifestCount.getAsLong(),
+ netPositionDeletes ? positionDeleteRows.getAsLong() :
0).isPresent();
+ }
+
private Optional<List<ConnectorScanRange>> planManifestCountRange(Table
table, TableScan scan, long exactCount,
int formatVersion, boolean partitioned, List<String>
orderedPartitionKeys, ZoneId zone,
UnaryOperator<String> uriNormalizer, ConnectorSession session,
Optional<ConnectorExpression> filter,
diff --git
a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergScanPlanProviderTest.java
b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergScanPlanProviderTest.java
index 73c0abd20a9..abbcb46a8e0 100644
---
a/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergScanPlanProviderTest.java
+++
b/fe/fe-connector/fe-connector-iceberg/src/test/java/org/apache/doris/connector/iceberg/IcebergScanPlanProviderTest.java
@@ -1871,7 +1871,7 @@ public class IcebergScanPlanProviderTest {
}
@Test
- public void metadataOnlyCountCapabilityUsesSnapshotSummary() {
+ public void metadataOnlyCountCapabilityUsesManifestAggregates() {
Table table = createTable("t1", SCHEMA, PartitionSpec.unpartitioned());
table.newAppend().appendFile(dataFile(
table.spec(), "s3://b/db/t1/f1.parquet", 1000, null,
null)).commit();
@@ -1887,6 +1887,24 @@ public class IcebergScanPlanProviderTest {
"db1", "t1", "snapshots", -1L, null, -1L),
Optional.empty()));
}
+ @Test
+ public void metadataOnlyCountCapabilityFollowsTheDeleteGate() {
+ // The capability must follow the same delete gate as count planning:
live deletes leave the row count
+ // unprovable from manifests alone. MUTATION: reporting the capability
from data manifests only -> red.
+ Table table = createTable("t1", SCHEMA, PartitionSpec.unpartitioned(),
+ Collections.singletonMap(TableProperties.FORMAT_VERSION, "2"));
+ table.newAppend().appendFile(dataFile(
+ table.spec(), "s3://b/db/t1/f1.parquet", 1000, null,
null)).commit();
+ table.newRowDelta().addDeletes(
+ positionDeleteFile("s3://b/db/t1/pos.parquet",
FileFormat.PARQUET, null, null)).commit();
+ IcebergScanPlanProvider provider = new IcebergScanPlanProvider(
+ IcebergCatalogProperties.of(Collections.emptyMap()),
opsReturning(table));
+ ConnectorSession session = new FakeScanSession("UTC",
Collections.emptyMap());
+
+ Assertions.assertFalse(provider.canServeMetadataOnlyCount(
+ session, new IcebergTableHandle("db1", "t1"),
Optional.empty()));
+ }
+
@Test
public void getScanNodePropertiesUnderPinEmitsFullPinnedSchemaDict()
throws Exception {
// T07 Option A: under a time-travel pin the field-id dict is built
from the FULL pinned schema (covering
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]