This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch branch-incremental-computation
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-incremental-computation 
by this push:
     new 71679aca362 branch-incremental-computation: pick the merged 
incremental-computation PRs from master in merge order (#68138) (#68303)
71679aca362 is described below

commit 71679aca3628ade1be3713c9f53ae30677a29db3
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Sep 21 11:12:10 2026 +0800

    branch-incremental-computation: pick the merged incremental-computation PRs 
from master in merge order (#68138) (#68303)
    
    Cherry-picked from #68138
    
    Batch pick of every merged PR carrying the `incremental-computation`
    label that `branch-incremental-computation` does not have yet (no
    `incremental-computation-picked` label), in the order they landed on
    master (`git log --first-parent`). One commit per PR, each ending with
    `(cherry picked from commit <master sha>)`. Follows the same convention
    as #67830, #68017, #68073, #68151 and #68236. This round has a single
    PR.
    
    | # | Master commit | PR | Title |
    |---|---|---|---|
    | 1 | 695c88b5772 | #68138 | [fix](binlog) Refresh incremental partition
    versions before pruning |
    
    Not included on purpose:
    - The 28 labelled PRs that already carry
    `incremental-computation-picked` (every other closed PR with the label).
    
    ### Prerequisite check
    
    - **#68138** declares #67181 as related; #67181 (`e5a4e725fac`) is
    before the fork point `efedf10c7e3`. Everything the pick uses already
    exists on this branch:
    `OlapTableWrapper.selectNonEmptyPartitionIds(Collection<Long>,
    Optional<StreamReadMode>)` / `hasFixedVisibleVersions()`, the
    two-argument `CloudPartition.getSnapshotVisibleVersionFromMs(List,
    boolean)`, `StreamReadMode`, the three `RowBinlogTableWrapper`
    constructors, and the test helpers
    (`BinlogTestUtils.newTestRowBinlogConfig`,
    `PlanConstructor.newOlapTable`, `MemoTestUtils.createConnectContext`,
    `PlanChecker.from(ConnectContext, Plan)`). `cherry-pick -x` applied
    cleanly (one auto-merge in `CloudPartition.java`, no conflict).
    
    ### Drift check against master
    
    - `RowBinlogTableWrapper.java`, `OlapTableWrapperTest.java` and the new
    `PruneEmptyPartitionTest.java` are byte-identical to master at
    `695c88b5772`.
    - `CloudPartition.java` differs from master only by the unlabelled
    #66296 ("Reduce cloud version sync config": the `maxAttempts` overload
    of `getSnapshotVisibleVersionFromMs` / `getSnapshotVisibleVersion` and
    the `VariableMgr.getDefaultSessionVariable()` fallback for
    `cloudPartitionVersionCacheTtlMs`). Applying #66296's hunks for this
    file on top of the branch in a temporary index gives a zero-line diff
    against master, so nothing of the pick is missing; the pick itself only
    calls the two-argument `getSnapshotVisibleVersionFromMs(partitions,
    false)`, which is the same on both sides. #66296 stays out, as in the
    previous rounds.
    
    ### Verification
    
    - FE: `run-fe-ut.sh --run` on this branch (regenerates thrift/protobuf,
    compiles fe-core main + test) with the two touched test classes plus
    every test class that exercises `selectNonEmptyPartitionIds` /
    `getSnapshotVisibleVersionFromMs` / `RowBinlogTableWrapper`: 8 classes,
    108 tests, 0 failures, 0 errors, BUILD SUCCESS —
    `CloudGlobalTransactionMgrTest` 37, `ExplainTableStreamPlanTest` 24,
    `PhysicalPlanTranslatorTest` 17, `OlapScanNodeTest` 12,
    `PartitionCompensatorTest` 12, `OlapTableWrapperTest` 4 (the two new
    tests included), `PruneEmptyPartitionTest` 1 (new),
    `TableBinlogFunctionAuthTest` 1. The `@Test` counts of the two touched
    classes equal master's.
    - FE checkstyle on fe-core: 0 violations.
    - No BE, cloud or regression-suite changes in this PR.
    
    Co-authored-by: Luwei <[email protected]>
---
 .../doris/catalog/RowBinlogTableWrapper.java       | 23 +++++++
 .../apache/doris/cloud/catalog/CloudPartition.java | 32 +++++++---
 .../apache/doris/catalog/OlapTableWrapperTest.java | 67 ++++++++++++++++++++
 .../rules/rewrite/PruneEmptyPartitionTest.java     | 72 ++++++++++++++++++++++
 4 files changed, 184 insertions(+), 10 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/RowBinlogTableWrapper.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/RowBinlogTableWrapper.java
index 3d88a5ffbf6..d10be2ef76e 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/RowBinlogTableWrapper.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/RowBinlogTableWrapper.java
@@ -17,14 +17,21 @@
 
 package org.apache.doris.catalog;
 
+import org.apache.doris.catalog.stream.StreamReadMode;
+import org.apache.doris.cloud.catalog.CloudPartition;
+import org.apache.doris.common.Config;
 import org.apache.doris.common.Pair;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Maps;
 
+import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * A lightweight wrapper base for read binlog<Row> of table
@@ -79,6 +86,22 @@ public class RowBinlogTableWrapper extends OlapTableWrapper {
         return KeysType.DUP_KEYS;
     }
 
+    @Override
+    public List<Long> selectNonEmptyPartitionIds(Collection<Long> partitionIds,
+            Optional<StreamReadMode> streamReadMode) {
+        if (Config.isCloudMode() && !hasFixedVisibleVersions()) {
+            // A row-binlog scan can start immediately after its target 
transaction becomes visible. Refresh
+            // cached-empty or unknown partitions so an older cache entry 
cannot prune newly visible binlog data.
+            List<CloudPartition> partitions = partitionIds.stream()
+                    .map(this::getPartition)
+                    .filter(Objects::nonNull)
+                    .map(partition -> (CloudPartition) partition)
+                    .collect(Collectors.toList());
+            return CloudPartition.selectNonEmptyPartitionIdsFromMs(partitions);
+        }
+        return super.selectNonEmptyPartitionIds(partitionIds, streamReadMode);
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!super.equals(obj)) {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudPartition.java 
b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudPartition.java
index 3dbd50e4ed0..d393bf347fa 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudPartition.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudPartition.java
@@ -213,25 +213,37 @@ public class CloudPartition extends Partition {
 
     // Select the non-empty partitions and return the ids.
     public static List<Long> selectNonEmptyPartitionIds(List<CloudPartition> 
partitions) {
-        List<Long> nonEmptyPartitionIds = partitions.stream()
-                .filter(CloudPartition::hasDataCached)
-                .map(CloudPartition::getId)
-                .collect(Collectors.toList());
-        if (nonEmptyPartitionIds.size() == partitions.size()) {
+        return selectNonEmptyPartitionIds(partitions, false);
+    }
+
+    // Select non-empty partitions while bypassing the version cache for 
cached-empty or unknown partitions.
+    public static List<Long> 
selectNonEmptyPartitionIdsFromMs(List<CloudPartition> partitions) {
+        return selectNonEmptyPartitionIds(partitions, true);
+    }
+
+    private static List<Long> selectNonEmptyPartitionIds(List<CloudPartition> 
partitions, boolean forceRefresh) {
+        List<Long> nonEmptyPartitionIds = new ArrayList<>(partitions.size());
+        List<CloudPartition> unknowns = new ArrayList<>(partitions.size());
+        for (CloudPartition partition : partitions) {
+            if (partition.hasDataCached()) {
+                nonEmptyPartitionIds.add(partition.getId());
+            } else {
+                unknowns.add(partition);
+            }
+        }
+        if (unknowns.isEmpty()) {
             return nonEmptyPartitionIds;
         }
 
-        List<CloudPartition> unknowns = partitions.stream()
-                .filter(p -> !p.hasDataCached())
-                .collect(Collectors.toList());
-
         SummaryProfile profile = getSummaryProfile();
         if (profile != null) {
             profile.incGetPartitionVersionByHasDataCount();
         }
 
         try {
-            List<Long> versions = 
CloudPartition.getSnapshotVisibleVersion(unknowns);
+            List<Long> versions = forceRefresh
+                    ? CloudPartition.getSnapshotVisibleVersionFromMs(unknowns, 
false)
+                    : CloudPartition.getSnapshotVisibleVersion(unknowns);
 
             int size = versions.size();
             for (int i = 0; i < size; i++) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableWrapperTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableWrapperTest.java
index 3d084f9e270..8c2c96ca4fc 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableWrapperTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableWrapperTest.java
@@ -18,13 +18,21 @@
 package org.apache.doris.catalog;
 
 import org.apache.doris.binlog.BinlogTestUtils;
+import org.apache.doris.cloud.catalog.CloudPartition;
+import org.apache.doris.common.Config;
 import org.apache.doris.thrift.TStorageType;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
 
+import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 
 public class OlapTableWrapperTest {
@@ -117,4 +125,63 @@ public class OlapTableWrapperTest {
         
Assertions.assertEquals(table.getSchemaByIndexId(table.getBaseIndexId()), 
wrapper.getSchemaByIndexId(table.getBaseIndexId()));
         
Assertions.assertEquals(table.getIndexSchemaVersion(table.getBaseIndexId()), 
wrapper.getIndexSchemaVersion(table.getBaseIndexId()));
     }
+
+    @Test
+    public void testCloudRowBinlogWrapperRefreshesCachedEmptyPartitions() {
+        long stalePartitionId = 100L;
+        long cachedNonEmptyPartitionId = 101L;
+        List<Long> partitionIds = ImmutableList.of(stalePartitionId, 
cachedNonEmptyPartitionId);
+
+        CloudPartition stalePartition = Mockito.mock(CloudPartition.class);
+        Mockito.when(stalePartition.getId()).thenReturn(stalePartitionId);
+        // Simulate another query refreshing the shared cache after the first 
cached-state check.
+        Mockito.when(stalePartition.hasDataCached()).thenReturn(false, true);
+        CloudPartition cachedNonEmptyPartition = 
Mockito.mock(CloudPartition.class);
+        
Mockito.when(cachedNonEmptyPartition.getId()).thenReturn(cachedNonEmptyPartitionId);
+        Mockito.when(cachedNonEmptyPartition.hasDataCached()).thenReturn(true);
+
+        OlapTable table = 
Mockito.spy(newTestTable(BinlogTestUtils.newTestRowBinlogConfig(true, true)));
+        
Mockito.doReturn(stalePartition).when(table).getPartition(stalePartitionId);
+        
Mockito.doReturn(cachedNonEmptyPartition).when(table).getPartition(cachedNonEmptyPartitionId);
+        RowBinlogTableWrapper wrapper = new RowBinlogTableWrapper(table);
+
+        try (MockedStatic<Config> mockedConfig = 
Mockito.mockStatic(Config.class);
+                MockedStatic<CloudPartition> mockedPartition = 
Mockito.mockStatic(
+                        CloudPartition.class, Mockito.CALLS_REAL_METHODS)) {
+            mockedConfig.when(Config::isCloudMode).thenReturn(true);
+            mockedPartition.when(() -> 
CloudPartition.getSnapshotVisibleVersionFromMs(
+                    ImmutableList.of(stalePartition), 
false)).thenReturn(ImmutableList.of(2L));
+            mockedPartition.clearInvocations();
+
+            
Assertions.assertEquals(ImmutableList.of(cachedNonEmptyPartitionId, 
stalePartitionId),
+                    wrapper.selectNonEmptyPartitionIds(partitionIds, 
Optional.empty()));
+            mockedPartition.verify(() -> 
CloudPartition.getSnapshotVisibleVersionFromMs(
+                    ImmutableList.of(stalePartition), false));
+            Mockito.verify(stalePartition).hasDataCached();
+            Mockito.verify(cachedNonEmptyPartition).hasDataCached();
+        }
+    }
+
+    @Test
+    public void 
testCloudRowBinlogWrapperWithFixedVisibleVersionsUsesOriginTable() {
+        long emptyPartitionId = 100L;
+        long nonEmptyPartitionId = 101L;
+        List<Long> partitionIds = ImmutableList.of(emptyPartitionId, 
nonEmptyPartitionId);
+
+        OlapTable table = 
Mockito.spy(newTestTable(BinlogTestUtils.newTestRowBinlogConfig(true, true)));
+        Mockito.doReturn(ImmutableList.of(nonEmptyPartitionId)).when(table)
+                .selectNonEmptyPartitionIds(partitionIds, Optional.empty());
+        RowBinlogTableWrapper wrapper = new RowBinlogTableWrapper(table, 
Collections.emptyMap(),
+                ImmutableMap.of(emptyPartitionId, 
Partition.PARTITION_INIT_VERSION, nonEmptyPartitionId, 2L));
+
+        try (MockedStatic<Config> mockedConfig = 
Mockito.mockStatic(Config.class);
+                MockedStatic<CloudPartition> mockedPartition = 
Mockito.mockStatic(CloudPartition.class)) {
+            mockedConfig.when(Config::isCloudMode).thenReturn(true);
+
+            Assertions.assertEquals(ImmutableList.of(nonEmptyPartitionId),
+                    wrapper.selectNonEmptyPartitionIds(partitionIds, 
Optional.empty()));
+            Mockito.verify(table).selectNonEmptyPartitionIds(partitionIds, 
Optional.empty());
+            mockedPartition.verifyNoInteractions();
+        }
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneEmptyPartitionTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneEmptyPartitionTest.java
new file mode 100644
index 00000000000..819b2b54197
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneEmptyPartitionTest.java
@@ -0,0 +1,72 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.rules.rewrite;
+
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.cloud.catalog.CloudPartition;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.util.MemoPatternMatchSupported;
+import org.apache.doris.nereids.util.MemoTestUtils;
+import org.apache.doris.nereids.util.PlanChecker;
+import org.apache.doris.nereids.util.PlanConstructor;
+import org.apache.doris.qe.ConnectContext;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import java.util.List;
+import java.util.Optional;
+
+class PruneEmptyPartitionTest implements MemoPatternMatchSupported {
+
+    @Test
+    void testNormalReadUsesCachedPartitionVersions() {
+        long emptyPartitionId = 100L;
+        long nonEmptyPartitionId = 101L;
+        List<Long> partitionIds = ImmutableList.of(emptyPartitionId, 
nonEmptyPartitionId);
+
+        CloudPartition emptyPartition = Mockito.mock(CloudPartition.class);
+        Mockito.when(emptyPartition.getId()).thenReturn(emptyPartitionId);
+        CloudPartition nonEmptyPartition = Mockito.mock(CloudPartition.class);
+        
Mockito.when(nonEmptyPartition.getId()).thenReturn(nonEmptyPartitionId);
+
+        OlapTable table = Mockito.spy(PlanConstructor.newOlapTable(10L, 
"normal_tbl", 0));
+        Mockito.doReturn(partitionIds).when(table).getPartitionIds();
+        
Mockito.doReturn(emptyPartition).when(table).getPartition(emptyPartitionId);
+        
Mockito.doReturn(nonEmptyPartition).when(table).getPartition(nonEmptyPartitionId);
+        Mockito.doReturn(ImmutableList.of(nonEmptyPartitionId)).when(table)
+                .selectNonEmptyPartitionIds(Mockito.anyCollection(), 
Mockito.any());
+
+        LogicalOlapScan scan = new LogicalOlapScan(
+                PlanConstructor.getNextRelationId(), table, 
ImmutableList.of("normal_tbl"));
+        ConnectContext connectContext = MemoTestUtils.createConnectContext();
+
+        try (MockedStatic<CloudPartition> mockedPartition = 
Mockito.mockStatic(CloudPartition.class)) {
+            LogicalOlapScan rewritten = (LogicalOlapScan) 
PlanChecker.from(connectContext, scan)
+                    .applyTopDown(new PruneEmptyPartition())
+                    .getPlan();
+
+            Assertions.assertEquals(ImmutableList.of(nonEmptyPartitionId), 
rewritten.getSelectedPartitionIds());
+            Mockito.verify(table).selectNonEmptyPartitionIds(partitionIds, 
Optional.empty());
+            mockedPartition.verifyNoInteractions();
+        }
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to