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

huajianlan 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 d0731c8a309 [opt](nereids) support prune partitions by specified 
tablet ids (#50424)
d0731c8a309 is described below

commit d0731c8a309d61c81f8dcf20c063d8ad2f9d4971
Author: zhaorongsheng <[email protected]>
AuthorDate: Mon May 12 22:19:42 2025 +0800

    [opt](nereids) support prune partitions by specified tablet ids (#50424)
    
    Issue Number: close #50255
    
    support prune partitions by specified tablet ids
---
 .../rules/rewrite/PruneOlapScanPartition.java      | 187 +++++++++++++++------
 .../rules/rewrite/PruneOlapScanPartitionTest.java  |  47 +++++-
 2 files changed, 183 insertions(+), 51 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartition.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartition.java
index debf4e880cc..023a17bec5a 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartition.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartition.java
@@ -19,10 +19,14 @@ package org.apache.doris.nereids.rules.rewrite;
 
 import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.MaterializedIndex;
 import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Partition;
 import org.apache.doris.catalog.PartitionInfo;
 import org.apache.doris.catalog.PartitionItem;
+import org.apache.doris.catalog.Tablet;
 import org.apache.doris.common.cache.NereidsSortedPartitionsCacheManager;
+import org.apache.doris.nereids.pattern.MatchingContext;
 import org.apache.doris.nereids.rules.Rule;
 import org.apache.doris.nereids.rules.RuleType;
 import org.apache.doris.nereids.rules.expression.rules.PartitionPruner;
@@ -32,12 +36,15 @@ import org.apache.doris.nereids.trees.expressions.Slot;
 import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation;
 import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
 import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalRelation;
 import org.apache.doris.nereids.util.Utils;
 import org.apache.doris.qe.ConnectContext;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import java.util.ArrayList;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -49,65 +56,145 @@ import java.util.stream.Collectors;
  * Used to prune partition of olap scan, should execute after 
SwapProjectAndFilter, MergeConsecutiveFilters,
  * MergeConsecutiveProjects and all predicate push down related rules.
  */
-public class PruneOlapScanPartition extends OneRewriteRuleFactory {
+public class PruneOlapScanPartition implements RewriteRuleFactory {
 
     @Override
-    public Rule build() {
-        return logicalFilter(logicalOlapScan()).when(p -> 
!p.child().isPartitionPruned()).thenApply(ctx -> {
-            LogicalFilter<LogicalOlapScan> filter = ctx.root;
-            LogicalOlapScan scan = filter.child();
-            OlapTable table = scan.getTable();
-            Set<String> partitionColumnNameSet = 
Utils.execWithReturnVal(table::getPartitionColumnNames);
-            if (partitionColumnNameSet.isEmpty()) {
-                return null;
-            }
-            List<Slot> output = scan.getOutput();
-            PartitionInfo partitionInfo = table.getPartitionInfo();
-            List<Column> partitionColumns = 
partitionInfo.getPartitionColumns();
-            List<Slot> partitionSlots = new 
ArrayList<>(partitionColumns.size());
-            for (Column column : partitionColumns) {
-                Slot partitionSlot = null;
-                // loop search is faster than build a map
-                for (Slot slot : output) {
-                    if (slot.getName().equalsIgnoreCase(column.getName())) {
-                        partitionSlot = slot;
-                        break;
-                    }
-                }
-                if (partitionSlot == null) {
-                    return null;
-                } else {
-                    partitionSlots.add(partitionSlot);
+    public List<Rule> buildRules() {
+        return ImmutableList.of(
+                logicalOlapScan()
+                    .when(scan -> !scan.isPartitionPruned()
+                            && !scan.getManuallySpecifiedTabletIds().isEmpty()
+                            && scan.getTable().isPartitionedTable()
+                    )
+                    .thenApply(ctx -> {
+                        // Case1: sql without filter condition, e.g. SELECT * 
FROM tbl (${tabletID})
+                        LogicalOlapScan scan = ctx.root;
+                        OlapTable table = scan.getTable();
+                        return prunePartition(scan, table, null, ctx);
+                    }).toRule(RuleType.OLAP_SCAN_PARTITION_PRUNE),
+                logicalFilter(logicalOlapScan()
+                    .whenNot(LogicalOlapScan::isPartitionPruned))
+                    .thenApply(ctx -> {
+                        // Case2: sql with filter condition, e.g. SELECT * 
FROM tbl (${tabletID}) WHERE part_column='x'
+                        LogicalFilter<LogicalOlapScan> filter = ctx.root;
+                        LogicalOlapScan scan = filter.child();
+                        OlapTable table = scan.getTable();
+                        LogicalRelation rewrittenLogicalRelation = 
prunePartition(scan, table, filter, ctx);
+                        if (rewrittenLogicalRelation == null) {
+                            return null;
+                        }
+                        if (rewrittenLogicalRelation instanceof 
LogicalEmptyRelation) {
+                            return rewrittenLogicalRelation;
+                        } else {
+                            LogicalOlapScan rewrittenScan = (LogicalOlapScan) 
rewrittenLogicalRelation;
+                            return 
filter.withChildren(ImmutableList.of(rewrittenScan));
+                        }
+                    }).toRule(RuleType.OLAP_SCAN_PARTITION_PRUNE)
+        );
+    }
+
+    private LogicalRelation prunePartition(LogicalOlapScan scan,
+                                      OlapTable table,
+                                      LogicalFilter filter,
+                                      MatchingContext ctx) {
+        List<Long> prunedPartitionsByFilters = prunePartitionByFilters(scan, 
table, filter, ctx);
+        List<Long> prunedPartitions = prunePartitionByTabletIds(scan, table, 
prunedPartitionsByFilters);
+        if (prunedPartitions == null) {
+            return null;
+        }
+        if (prunedPartitions.isEmpty()) {
+            return new LogicalEmptyRelation(
+                ConnectContext.get().getStatementContext().getNextRelationId(),
+                ctx.root.getOutput());
+        }
+        return scan.withSelectedPartitionIds(prunedPartitions);
+    }
+
+    private List<Long> prunePartitionByFilters(LogicalOlapScan scan,
+                                               OlapTable table,
+                                               LogicalFilter filter,
+                                               MatchingContext ctx) {
+        Set<String> partitionColumnNameSet = 
Utils.execWithReturnVal(table::getPartitionColumnNames);
+        if (partitionColumnNameSet.isEmpty()) {
+            return null;
+        }
+        List<Slot> output = scan.getOutput();
+        PartitionInfo partitionInfo = table.getPartitionInfo();
+        List<Column> partitionColumns = partitionInfo.getPartitionColumns();
+        List<Slot> partitionSlots = new ArrayList<>(partitionColumns.size());
+        for (Column column : partitionColumns) {
+            Slot partitionSlot = null;
+            // loop search is faster than build a map
+            for (Slot slot : output) {
+                if (slot.getName().equalsIgnoreCase(column.getName())) {
+                    partitionSlot = slot;
+                    break;
                 }
             }
-            NereidsSortedPartitionsCacheManager sortedPartitionsCacheManager = 
Env.getCurrentEnv()
-                    .getSortedPartitionsCacheManager();
-            List<Long> manuallySpecifiedPartitions = 
scan.getManuallySpecifiedPartitions();
-            Map<Long, PartitionItem> idToPartitions;
-            Optional<SortedPartitionRanges<Long>> sortedPartitionRanges = 
Optional.empty();
-            if (manuallySpecifiedPartitions.isEmpty()) {
-                Optional<SortedPartitionRanges<?>> sortedPartitionRangesOpt
-                        = sortedPartitionsCacheManager.get(table, scan);
-                if (sortedPartitionRangesOpt.isPresent()) {
-                    sortedPartitionRanges = (Optional) 
sortedPartitionRangesOpt;
-                }
-                idToPartitions = partitionInfo.getIdToItem(false);
+            if (partitionSlot == null) {
+                return null;
             } else {
-                Map<Long, PartitionItem> allPartitions = 
partitionInfo.getAllPartitions();
-                idToPartitions = allPartitions.keySet().stream()
-                        .filter(manuallySpecifiedPartitions::contains)
-                        .collect(Collectors.toMap(Function.identity(), 
allPartitions::get));
+                partitionSlots.add(partitionSlot);
+            }
+        }
+        NereidsSortedPartitionsCacheManager sortedPartitionsCacheManager = 
Env.getCurrentEnv()
+                .getSortedPartitionsCacheManager();
+        List<Long> manuallySpecifiedPartitions = 
scan.getManuallySpecifiedPartitions();
+        Map<Long, PartitionItem> idToPartitions;
+        Optional<SortedPartitionRanges<Long>> sortedPartitionRanges = 
Optional.empty();
+        if (manuallySpecifiedPartitions.isEmpty()) {
+            Optional<SortedPartitionRanges<?>> sortedPartitionRangesOpt
+                    = sortedPartitionsCacheManager.get(table, scan);
+            if (sortedPartitionRangesOpt.isPresent()) {
+                sortedPartitionRanges = (Optional) sortedPartitionRangesOpt;
             }
+            idToPartitions = partitionInfo.getIdToItem(false);
+        } else {
+            Map<Long, PartitionItem> allPartitions = 
partitionInfo.getAllPartitions();
+            idToPartitions = allPartitions.keySet().stream()
+                    .filter(manuallySpecifiedPartitions::contains)
+                    .collect(Collectors.toMap(Function.identity(), 
allPartitions::get));
+        }
+        if (filter != null) {
             List<Long> prunedPartitions = PartitionPruner.prune(
                     partitionSlots, filter.getPredicate(), idToPartitions, 
ctx.cascadesContext,
                     PartitionTableType.OLAP, sortedPartitionRanges);
-            if (prunedPartitions.isEmpty()) {
-                return new LogicalEmptyRelation(
-                        
ConnectContext.get().getStatementContext().getNextRelationId(),
-                        filter.getOutput());
+            return prunedPartitions;
+        } else if (!manuallySpecifiedPartitions.isEmpty()) {
+            return Utils.fastToImmutableList(idToPartitions.keySet());
+        } else {
+            return null;
+        }
+    }
+
+    private List<Long> prunePartitionByTabletIds(LogicalOlapScan scan,
+                                                 OlapTable table,
+                                                 List<Long> 
prunedPartitionsByFilters) {
+        if (scan.getManuallySpecifiedTabletIds().size() == 0
+                || (prunedPartitionsByFilters != null && 
prunedPartitionsByFilters.isEmpty())) {
+            // `prunedPartitionsByFilters is not null and is empty` means 
empty partitions after pruner
+            return prunedPartitionsByFilters;
+        }
+
+        Set<Long> selectedPartitions = new LinkedHashSet<>();
+        if (prunedPartitionsByFilters != null) {
+            selectedPartitions.addAll(prunedPartitionsByFilters);
+        }
+
+        Set<Long> manuallySpecifiedTabletIds = 
ImmutableSet.copyOf(scan.getManuallySpecifiedTabletIds());
+        List<Long> selectPartitionIds = new ArrayList<>();
+        for (Partition partition : table.getPartitions()) {
+            if (!selectedPartitions.isEmpty() && 
!selectedPartitions.contains(partition.getId())) {
+                continue;
+            }
+            MaterializedIndex baseIndex = partition.getBaseIndex();
+            for (Tablet tablet : baseIndex.getTablets()) {
+                if (manuallySpecifiedTabletIds.contains(tablet.getId())) {
+                    selectPartitionIds.add(partition.getId());
+                    break;
+                }
             }
-            LogicalOlapScan rewrittenScan = 
scan.withSelectedPartitionIds(prunedPartitions);
-            return filter.withChildren(ImmutableList.of(rewrittenScan));
-        }).toRule(RuleType.OLAP_SCAN_PARTITION_PRUNE);
+        }
+        return selectPartitionIds;
     }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
index de54a05bc5e..f87f51253ad 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PruneOlapScanPartitionTest.java
@@ -17,6 +17,11 @@
 
 package org.apache.doris.nereids.rules.rewrite;
 
+import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.MaterializedIndex;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Tablet;
 import org.apache.doris.common.FeConstants;
 import org.apache.doris.nereids.util.MemoPatternMatchSupported;
 import org.apache.doris.nereids.util.PlanChecker;
@@ -152,6 +157,30 @@ class PruneOlapScanPartitionTest extends TestWithFeService 
implements MemoPatter
         test("testOlapScanPartitionWithSingleColumnCase", "col1 >= 0 and col1 
<= 5", 2);
     }
 
+    @Test
+    void testOlapScanPartitionPruneWithTablet() throws Exception {
+        Database db = 
Env.getCurrentInternalCatalog().getDbOrMetaException("test");
+        OlapTable tbl = (OlapTable) 
db.getTableOrMetaException("single_not_null");
+        Assertions.assertNotNull(tbl);
+        Tablet tablet1 = tbl.getPartition("p20211122")
+                
.getMaterializedIndices(MaterializedIndex.IndexExtState.ALL).iterator().next()
+                .getTablets().iterator().next();
+        Tablet tablet2 = tbl.getPartition("p20211125")
+                
.getMaterializedIndices(MaterializedIndex.IndexExtState.ALL).iterator().next()
+                .getTablets().iterator().next();
+
+        test("test.single_not_null", "", String.valueOf(tablet1.getId()), 1);
+        test("test.single_not_null", "", tablet1.getId() + ", " + 
tablet2.getId(), 2);
+        test("test.single_not_null", "dt in (20211121)", tablet1.getId() + ", 
" + tablet2.getId(), 1);
+        test("test.single_not_null", "dt in (20211121, 20211124)", 
tablet1.getId() + ", " + tablet2.getId(), 2);
+        test("test.single_not_null", "dt in (20211121)", 
String.valueOf(tablet2.getId()), 0);
+        test("test.single_not_null", "", "", 7);
+        test("test.single_not_null", "", String.valueOf(tablet1.getId()), 
"p20211122", 1);
+        test("test.single_not_null", "", String.valueOf(tablet2.getId()), 
"p20211122", 0);
+        test("test.single_not_null", "dt in (20211124)", 
String.valueOf(tablet1.getId()), "p20211122", 0);
+
+    }
+
     @Test
     void testOlapScanPartitionPruneWithMultiColumnCase() throws Exception {
         createTable("create table 
testOlapScanPartitionPruneWithMultiColumnCase("
@@ -389,8 +418,24 @@ class PruneOlapScanPartitionTest extends TestWithFeService 
implements MemoPatter
     }
 
     private void test(String table, String filter, int expectScanPartitionNum) 
{
+        test(table, filter, "", expectScanPartitionNum);
+    }
+
+    private void test(String table, String filter, String tabletsFilter, int 
expectScanPartitionNum) {
+        test(table, filter, tabletsFilter, "", expectScanPartitionNum);
+    }
+
+    private void test(String table,
+                      String filter,
+                      String tabletsFilter,
+                      String specifiedPartition,
+                      int expectScanPartitionNum) {
         PlanChecker planChecker = PlanChecker.from(connectContext)
-                .analyze("select * from " + table + (filter.isEmpty() ? "" : " 
where " + filter))
+                .analyze("select * from " + table
+                        + (specifiedPartition.isEmpty() ? "" : String.format(" 
PARTITION %s", specifiedPartition))
+                        + (tabletsFilter.isEmpty() ? "" : String.format(" 
tablet(%s)", tabletsFilter))
+                        + (filter.isEmpty() ? "" : " where " + filter)
+                )
                 .rewrite()
                 .printlnTree();
 


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

Reply via email to