You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by "BiteTheDDDDt (via GitHub)" <gi...@apache.org> on 2026/04/17 09:29:32 UTC

[PR] Dev runtime partition prune [doris]

BiteTheDDDDt opened a new pull request, #62589:
URL: https://github.com/apache/doris/pull/62589

   ### 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: https://github.com/apache/doris-website/pull/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 -->
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4268930603

   # BE Regression && UT Coverage Report
   Increment line coverage `84.54% (257/304)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_141c78da888bc5cd8198ffebb607430c90015f0d_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_141c78da888bc5cd8198ffebb607430c90015f0d_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.71% (27513/37328) |
   | Line Coverage     | 57.47% (298118/518704) |
   | Region Coverage   | 54.54% (247670/454115) |
   | Branch Coverage   | 56.05% (107208/191288) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4277736731

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4287174053

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/6bb4482b83b38672c7368e51b346c7bea9dc9247_6bb4482b83b38672c7368e51b346c7bea9dc9247_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/6bb4482b83b38672c7368e51b346c7bea9dc9247_6bb4482b83b38672c7368e51b346c7bea9dc9247_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.06% (1843/2361) |
   | Line Coverage     | 64.77% (32998/50947) |
   | Region Coverage   | 65.27% (16374/25085) |
   | Branch Coverage   | 55.86% (8743/15652) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4505822322

   # BE Regression && UT Coverage Report
   Increment line coverage `75.28% (603/801)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.56% (27865/37883) |
   | Line Coverage     | 57.43% (302571/526849) |
   | Region Coverage   | 54.28% (253198/466433) |
   | Branch Coverage   | 55.80% (109792/196776) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "HappenLee (via GitHub)" <gi...@apache.org>.
HappenLee commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3308463839


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,875 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary must contain partition_id and slot_id");
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary references unknown slot_id={}, "
+                    "partition_id={}",
+                    slot_id, tb.partition_id);
+        }
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) {                                                           \
+            return Status::InternalError(                                                      \
+                    "Runtime filter partition boundary must be RANGE or LIST, "                \
+                    "partition_id={}, slot_id={}",                                             \
+                    tb.partition_id, slot_id);                                                 \
+        }                                                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        } else {
+            return Status::InternalError(
+                    "Runtime filter partition boundary has unsupported type, partition_id={}, "
+                    "slot_id={}, type={}",
+                    tb.partition_id, slot_id, slot_type->get_name());
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (expr->is_slot_ref()) {
+        return assert_cast<const VSlotRef*>(expr);
+    }
+    const VSlotRef* found = nullptr;
+    for (const auto& child : expr->children()) {
+        const VSlotRef* c = find_unique_slot_ref(child.get());
+        if (c) {
+            if (found) {
+                return nullptr; // multiple slot refs, can't handle
+            }
+            found = c;
+        }
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        if (direction == TTargetExprMonotonicity::NON_MONOTONIC) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received NON_MONOTONIC partition "
+                    "metadata, filter_id={}, partition_id={}",
+                    filter_id, partition_id);
+        }
+        if (!boundary_partition_ids.contains(partition_id)) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received monotonicity for a partition "
+                    "without boundary, filter_id={}, partition_id={}, slot_id={}",
+                    filter_id, partition_id, leaf_slot_id);
+        }
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        if (slot_ref->slot_id() != leaf_slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning SlotRef target mismatch, filter_id={}, "
+                    "target_slot_id={}, leaf_slot_id={}",
+                    filter_id, slot_ref->slot_id(), leaf_slot_id);
+        }
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            if (direction_it->second != TTargetExprMonotonicity::MONOTONIC_INCREASING) {
+                return Status::InternalError(
+                        "Runtime filter partition pruning SlotRef target must use increasing "
+                        "monotonicity, filter_id={}, partition_id={}, monotonicity={}",
+                        filter_id, boundary.partition_id, direction_it->second);
+            }
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    if (slot_type_it == _slot_data_types.end()) {
+        return Status::InternalError(
+                "Runtime filter partition pruning target slot has no boundary type, filter_id={}, "
+                "slot_id={}, target_expr={}",
+                filter_id, leaf_slot_id, target_expr->expr_name());
+    }
+
+    // LIST partitions require regrouping projected values per partition and
+    // should not be marked as expression-prunable by FE yet.
+    for (const auto& selected_boundary : selected_boundaries) {
+        bool is_list = false;
+        std::visit([&](const auto& cvr) { is_list = cvr.is_fixed_value_range(); },
+                   orig_boundaries[selected_boundary.first].boundary_cvr);
+        if (is_list) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning expression target does not support LIST "
+                    "partition boundaries, filter_id={}, partition_id={}, target_expr={}",
+                    filter_id, orig_boundaries[selected_boundary.first].partition_id,
+                    target_expr->expr_name());
+        }
+    }
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+
+    // Build input blocks for lo and hi
+    Block lo_block;
+    Block hi_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns. Open endpoints are not executed through target_expr; they stay
+    // open after projection and swap sides for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    if (!input_built) {
+        return Status::InternalError(
+                "Runtime filter partition pruning expression target has unsupported input type, "
+                "filter_id={}, input_type={}, target_expr={}",
+                filter_id, input_type->get_name(), target_expr->expr_name());
+    }
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        if (lo_result_id < 0) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning failed to project lower boundary, "
+                    "filter_id={}, target_expr={}",
+                    filter_id, target_expr->expr_name());
+        }
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        if (hi_result_id < 0) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning failed to project upper boundary, "
+                    "filter_id={}, target_expr={}",
+                    filter_id, target_expr->expr_name());
+        }
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \

Review Comment:
   原始 RANGE 分区上界可能是开区间(<),投影后统一变成 FILTER_LESS_OR_EQUAL(<=)?这个是否可能有问题



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4551440400

   # BE UT Coverage Report
   Increment line coverage `62.92% (504/801)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/3d38dc5ab813ed88625762426d63d5324bfff4fe_3d38dc5ab813ed88625762426d63d5324bfff4fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/3d38dc5ab813ed88625762426d63d5324bfff4fe_3d38dc5ab813ed88625762426d63d5324bfff4fe/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 53.88% (20936/38857) |
   | Line Coverage     | 37.47% (198602/530087) |
   | Region Coverage   | 33.67% (156138/463736) |
   | Branch Coverage   | 34.72% (68307/196735) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3309562083


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,875 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary must contain partition_id and slot_id");
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary references unknown slot_id={}, "
+                    "partition_id={}",
+                    slot_id, tb.partition_id);
+        }
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) {                                                           \
+            return Status::InternalError(                                                      \
+                    "Runtime filter partition boundary must be RANGE or LIST, "                \
+                    "partition_id={}, slot_id={}",                                             \
+                    tb.partition_id, slot_id);                                                 \
+        }                                                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        } else {
+            return Status::InternalError(
+                    "Runtime filter partition boundary has unsupported type, partition_id={}, "
+                    "slot_id={}, type={}",
+                    tb.partition_id, slot_id, slot_type->get_name());
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (expr->is_slot_ref()) {
+        return assert_cast<const VSlotRef*>(expr);
+    }
+    const VSlotRef* found = nullptr;
+    for (const auto& child : expr->children()) {
+        const VSlotRef* c = find_unique_slot_ref(child.get());
+        if (c) {
+            if (found) {
+                return nullptr; // multiple slot refs, can't handle
+            }
+            found = c;
+        }
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        if (direction == TTargetExprMonotonicity::NON_MONOTONIC) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received NON_MONOTONIC partition "
+                    "metadata, filter_id={}, partition_id={}",
+                    filter_id, partition_id);
+        }
+        if (!boundary_partition_ids.contains(partition_id)) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received monotonicity for a partition "
+                    "without boundary, filter_id={}, partition_id={}, slot_id={}",
+                    filter_id, partition_id, leaf_slot_id);
+        }
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        if (slot_ref->slot_id() != leaf_slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning SlotRef target mismatch, filter_id={}, "
+                    "target_slot_id={}, leaf_slot_id={}",
+                    filter_id, slot_ref->slot_id(), leaf_slot_id);
+        }
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            if (direction_it->second != TTargetExprMonotonicity::MONOTONIC_INCREASING) {
+                return Status::InternalError(
+                        "Runtime filter partition pruning SlotRef target must use increasing "
+                        "monotonicity, filter_id={}, partition_id={}, monotonicity={}",
+                        filter_id, boundary.partition_id, direction_it->second);
+            }
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    if (slot_type_it == _slot_data_types.end()) {
+        return Status::InternalError(
+                "Runtime filter partition pruning target slot has no boundary type, filter_id={}, "
+                "slot_id={}, target_expr={}",
+                filter_id, leaf_slot_id, target_expr->expr_name());
+    }
+
+    // LIST partitions require regrouping projected values per partition and
+    // should not be marked as expression-prunable by FE yet.
+    for (const auto& selected_boundary : selected_boundaries) {
+        bool is_list = false;
+        std::visit([&](const auto& cvr) { is_list = cvr.is_fixed_value_range(); },
+                   orig_boundaries[selected_boundary.first].boundary_cvr);
+        if (is_list) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning expression target does not support LIST "
+                    "partition boundaries, filter_id={}, partition_id={}, target_expr={}",
+                    filter_id, orig_boundaries[selected_boundary.first].partition_id,
+                    target_expr->expr_name());
+        }
+    }
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+
+    // Build input blocks for lo and hi
+    Block lo_block;
+    Block hi_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns. Open endpoints are not executed through target_expr; they stay
+    // open after projection and swap sides for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    if (!input_built) {
+        return Status::InternalError(
+                "Runtime filter partition pruning expression target has unsupported input type, "
+                "filter_id={}, input_type={}, target_expr={}",
+                filter_id, input_type->get_name(), target_expr->expr_name());
+    }
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        if (lo_result_id < 0) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning failed to project lower boundary, "
+                    "filter_id={}, target_expr={}",
+                    filter_id, target_expr->expr_name());
+        }
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        if (hi_result_id < 0) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning failed to project upper boundary, "
+                    "filter_id={}, target_expr={}",
+                    filter_id, target_expr->expr_name());
+        }
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \

Review Comment:
   假设分区列是dt,原始RANGE分区是:
   P1:dtin[2024-01-0100:00:00,2024-01-0200:00:00)
   上界2024-01-0200:00:00是开区间,这一点没错。
   但RFtarget不是dt本身,而是一个非严格单调表达式,例如按天向上取整:
   target_expr=date_ceil(dt,'day')
   这个函数是单调递增的,但不是严格递增。分区里的真实行可能有:
   dt=2024-01-0112:00:00
   它满足原始分区范围,因为它小于开上界2024-01-0200:00:00。但投影后:
   date_ceil(2024-01-0112:00:00,'day')=2024-01-0200:00:00
   也就是说,虽然原始上界是开的,投影结果里仍然可能出现target_expr(range_end)这个值。
   如果BE把投影后的范围做成:
   [date_ceil(start),date_ceil(end))--上界仍然用<
   那就会变成不包含2024-01-0200:00:00。假如RFbuildside正好只有:
   RF={2024-01-0200:00:00}
   BE会认为投影分区范围和RF没交集,从而裁掉P1;但实际上P1里2024-01-0112:00:00这类行投影后能命中RF,所以这是误裁剪。
   所以当前用<=target_expr(end)是一个安全的over-approx:可能多保留一点分区,但不会把可能命中的分区裁掉。只有当我们能证明targetexpr
   是严格单调且开边界不会映射出端点值时,才可以安全地保留<。



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4554975524

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 31731 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 36cd8599c1b5eba6dcfb83d6c88457494678e987, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17726	4015	4014	4014
   q2	q3	10828	1438	806	806
   q4	4750	482	355	355
   q5	9561	2340	2146	2146
   q6	391	179	143	143
   q7	956	809	644	644
   q8	9578	1755	1641	1641
   q9	7022	5055	4974	4974
   q10	6471	2239	1911	1911
   q11	451	279	246	246
   q12	691	434	295	295
   q13	18235	3379	2823	2823
   q14	267	259	237	237
   q15	q16	835	788	708	708
   q17	937	966	941	941
   q18	6997	5782	5757	5757
   q19	1192	1205	1082	1082
   q20	513	398	275	275
   q21	5585	2570	2427	2427
   q22	428	362	306	306
   Total cold run time: 103414 ms
   Total hot run time: 31731 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4336	4262	4267	4262
   q2	q3	4549	4934	4343	4343
   q4	2109	2217	1374	1374
   q5	4485	4335	5538	4335
   q6	253	189	147	147
   q7	2109	1825	1616	1616
   q8	2554	2267	2355	2267
   q9	7967	8240	7991	7991
   q10	4804	4743	4320	4320
   q11	601	430	384	384
   q12	745	758	556	556
   q13	3244	3625	3006	3006
   q14	304	313	297	297
   q15	q16	862	744	694	694
   q17	1360	1337	1329	1329
   q18	7989	7186	7219	7186
   q19	1143	1095	1091	1091
   q20	2236	2218	1935	1935
   q21	5279	4627	4519	4519
   q22	535	455	409	409
   Total cold run time: 57464 ms
   Total hot run time: 52061 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4539357346

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/f779760a4db0a4e1f4605c63a8a9bc21f51af126_f779760a4db0a4e1f4605c63a8a9bc21f51af126_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/f779760a4db0a4e1f4605c63a8a9bc21f51af126_f779760a4db0a4e1f4605c63a8a9bc21f51af126_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.06% (1854/2375) |
   | Line Coverage     | 64.52% (33334/51665) |
   | Region Coverage   | 65.20% (16523/25343) |
   | Branch Coverage   | 55.74% (8837/15854) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4449720738

   # FE UT Coverage Report
   Increment line coverage `  30.41% (90/296)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_572ab7793a850dc7d5ad35a35834f642fa194f82/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_572ab7793a850dc7d5ad35a35834f642fa194f82/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3241048647


##########
regression-test/suites/query_p0/runtime_filter/rf_partition_pruning_type_matrix.groovy:
##########
@@ -0,0 +1,219 @@
+// 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.
+
+import org.apache.doris.regression.action.ProfileAction
+
+suite("rf_partition_pruning_type_matrix", "nonConcurrent") {
+    sql "set enable_runtime_filter_prune=false"
+    sql "set enable_runtime_filter_partition_prune=true"
+    sql "set runtime_filter_wait_infinitely=true"
+    sql "set disable_join_reorder=true"
+    sql "set enable_profile=true"
+    sql "set profile_level=2"
+    sql "set parallel_pipeline_task_num=1"
+
+    def profileAction = new ProfileAction(context)
+    def rfPruningCounterNames = ["TotalPartitionsForRFPruning", "PartitionsPrunedByRuntimeFilter"]
+
+    def getProfileByToken = { String token ->
+        String profileContent = ""
+        for (int attempt = 0; attempt < 60; attempt++) {
+            List profileData = profileAction.getProfileList()
+            for (final def profileItem in profileData) {
+                if (profileItem["Sql Statement"].toString().contains(token)) {
+                    profileContent = profileAction.getProfile(profileItem["Profile ID"].toString())
+                    break
+                }
+            }
+            if (profileContent != "" && rfPruningCounterNames.every { profileContent.contains(it) }) {
+                break
+            }
+            Thread.sleep(500)
+        }
+        return profileContent
+    }
+
+    def extractCounterSum = { String profileText, String counterName ->
+        def values = (profileText =~ /-\s*${counterName}:\s*(\d+)/).collect { it[1].toLong() }
+        return values.isEmpty() ? 0L : values.sum()
+    }
+
+    def assertPruningProfile = { String queryBody, String rfType, long expectedTotal, long expectedPruned ->
+        def token = UUID.randomUUID().toString()
+        def querySql = """
+            SELECT /*+ SET_VAR(runtime_filter_type='${rfType}') */ "${token}", *
+            ${queryBody}
+            ORDER BY 2
+        """
+        sql querySql
+        def profile = getProfileByToken(token)

Review Comment:
   This helper executes the join and then discards the result, so the type-matrix cases only prove that the profile counters are at least the expected values. A false-positive pruning bug can still pass here: for example, if an INT RANGE case prunes all four partitions instead of keeping the partition containing `dim_key = 150`, `PartitionsPrunedByRuntimeFilter >= 3` still succeeds while the query returns no matching fact row. Please have the helper assert the expected joined rows (or at least the expected fact ids) in addition to the pruning counters, so these cases catch over-pruning/data-loss regressions. This is distinct from the existing profile-polling/golden-file threads because the profile can be present and the counters can satisfy the current assertions while the query result is wrong.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4426034929

   # FE Regression Coverage Report
   Increment line coverage `  65.93% (178/270)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_b29520eb8c2fd176695ee6c263c89bab10446d4a_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_b29520eb8c2fd176695ee6c263c89bab10446d4a_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4534540271

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4286496306

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4286987764

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4277736436

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4278503350

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd_bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd_bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.02% (1842/2361) |
   | Line Coverage     | 64.60% (32919/50958) |
   | Region Coverage   | 65.22% (16326/25034) |
   | Branch Coverage   | 55.76% (8707/15616) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4539063251

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4553722777

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4551463359

   # BE Regression && UT Coverage Report
   Increment line coverage `75.28% (603/801)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_3d38dc5ab813ed88625762426d63d5324bfff4fe_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_3d38dc5ab813ed88625762426d63d5324bfff4fe_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.73% (28060/38058) |
   | Line Coverage     | 57.67% (304930/528726) |
   | Region Coverage   | 54.68% (255900/468024) |
   | Branch Coverage   | 56.11% (110795/197453) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4564331115

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "HappenLee (via GitHub)" <gi...@apache.org>.
HappenLee commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3330576119


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,866 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            bool range_has_null = false;                                                       \
+            if (is_nullable && !tb.__isset.range_start) {                                      \
+                range_has_null = true;                                                         \
+            }                                                                                  \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                } else if (is_nullable) {                                                      \
+                    range_has_null = true;                                                     \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+            if (range_has_null) {                                                              \
+                boundary.contains_null = true;                                                 \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        DORIS_CHECK(parsed_ok);
+        _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static bool find_unique_slot_ref_impl(const VExpr* expr, const VSlotRef** found) {
+    if (!expr) {
+        return true;
+    }
+    if (expr->is_slot_ref()) {
+        const auto* slot = assert_cast<const VSlotRef*>(expr);
+        if (*found == nullptr) {
+            *found = slot;
+            return true;
+        }
+        if ((*found)->slot_id() != slot->slot_id()) {
+            return false;
+        }
+        DORIS_CHECK((*found)->column_id() == slot->column_id());
+        return true;
+    }
+    for (const auto& child : expr->children()) {
+        if (!find_unique_slot_ref_impl(child.get(), found)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    const VSlotRef* found = nullptr;
+    if (!find_unique_slot_ref_impl(expr, &found)) {
+        return nullptr;
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        DORIS_CHECK(direction != TTargetExprMonotonicity::NON_MONOTONIC);
+        DORIS_CHECK(boundary_partition_ids.contains(partition_id));
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        DORIS_CHECK(slot_ref->slot_id() == leaf_slot_id);
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            DORIS_CHECK(direction_it->second == TTargetExprMonotonicity::MONOTONIC_INCREASING);
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    DORIS_CHECK(slot_type_it != _slot_data_types.end());
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<bool> boundary_is_list(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    std::vector<size_t> list_result_begin(N, 0);
+    std::vector<size_t> list_result_end(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+    size_t list_row_count = 0;
+
+    // Build input blocks for RANGE endpoints and LIST values.
+    Block lo_block;
+    Block hi_block;
+    Block list_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns and LIST value columns. Unbounded RANGE endpoints are not executed
+    // through target_expr; they stay unbounded after projection and swap sides
+    // for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr list_inner_base = inner_type->create_column();                          \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        auto* list_inner = assert_cast<InCol*>(list_inner_base.get());                           \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        list_inner->reserve(N);                                                                  \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        auto list_nulls = ColumnUInt8::create();                                                 \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        list_nulls->reserve(N);                                                                  \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            boundary_is_list[i] = cvr->is_fixed_value_range();                                   \
+            if (boundary_is_list[i]) {                                                           \
+                list_result_begin[i] = list_row_count;                                           \
+                for (const auto& value : cvr->get_fixed_value_set()) {                           \
+                    list_inner->insert_value(value);                                             \
+                    list_nulls->get_data().push_back(0);                                         \
+                    ++list_row_count;                                                            \
+                }                                                                                \
+                list_result_end[i] = list_row_count;                                             \
+                continue;                                                                        \
+            }                                                                                    \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        add_dummy_columns(list_block, list_row_count);                                           \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            auto list_col =                                                                      \
+                    ColumnNullable::create(std::move(list_inner_base), std::move(list_nulls));   \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+            list_block.insert({std::move(list_col), input_type, "leaf_slot"});                   \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+            list_block.insert({std::move(list_inner_base), input_type, "leaf_slot"});            \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    DORIS_CHECK(input_built);
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    int list_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    ColumnPtr list_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        DORIS_CHECK(lo_result_id >= 0);
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        DORIS_CHECK(hi_result_id >= 0);
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    if (list_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &list_block, &list_result_id));
+        DORIS_CHECK(list_result_id >= 0);
+        list_result_col = list_block.get_by_position(list_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if (boundary_is_list[i]) {                                                          \
+                auto cvr = ColumnValueRange<TYPE_##OUTPUT_PT>::create_empty_column_value_range( \
+                        out_nullable, out_precision, out_scale);                                \
+                bool list_has_value = false;                                                    \
+                bool list_has_null = false;                                                     \
+                for (size_t row = list_result_begin[i]; row < list_result_end[i]; ++row) {      \
+                    if (list_result_col->is_null_at(row)) {                                     \
+                        list_has_null = true;                                                   \
+                        continue;                                                               \
+                    }                                                                           \
+                    auto projected_list_value = projected_value(list_result_col, row);          \
+                    static_cast<void>(cvr.add_fixed_value(projected_list_value));               \
+                    list_has_value = true;                                                      \
+                }                                                                               \
+                if (!list_has_value && !list_has_null) {                                        \
+                    continue;                                                                   \
+                }                                                                               \
+                ParsedBoundary projected_boundary;                                              \
+                projected_boundary.partition_id = orig_boundary.partition_id;                   \
+                projected_boundary.slot_id = leaf_slot_id;                                      \
+                projected_boundary.is_nullable = out_nullable;                                  \
+                projected_boundary.contains_null = list_has_null;                               \
+                projected_boundary.only_null = list_has_null && !list_has_value;                \
+                projected_boundary.boundary_cvr = std::move(cvr);                               \
+                projected.emplace_back(std::move(projected_boundary));                          \
+                continue;                                                                       \
+            }                                                                                   \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            /* Use closed projected finite bounds as a conservative envelope for */             \
+            /* non-strict monotonic expressions such as date ceil/floor/cast.    */             \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));       \
+                }                                                                               \
+            } else {                                                                            \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));     \
+                }                                                                               \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));       \
+                }                                                                               \
+            }                                                                                   \
+            ParsedBoundary projected_boundary;                                                  \
+            projected_boundary.partition_id = orig_boundary.partition_id;                       \
+            projected_boundary.slot_id = leaf_slot_id;                                          \
+            projected_boundary.is_nullable = out_nullable;                                      \
+            projected_boundary.only_null = orig_boundary.only_null;                             \
+            projected_boundary.contains_null = orig_boundary.contains_null;                     \
+            projected_boundary.boundary_cvr = std::move(cvr);                                   \
+            projected.emplace_back(std::move(projected_boundary));                              \
+        }                                                                                       \
+        output_built = true;                                                                    \
+        break;                                                                                  \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    DORIS_CHECK(output_built);
+
+    return store_projected_boundaries(std::move(projected));
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    std::optional<ColumnValueRangeType> rf_cvr;
+    if (!boundaries.empty()) {
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {
+                        auto typed_rf_cvr = CvrType::create_empty_column_value_range(
+                                boundaries.front().is_nullable, boundary_cvr.precision(),
+                                boundary_cvr.scale());
+                        auto* iter = hybrid_set->begin();
+                        while (iter->has_next()) {
+                            const void* value = iter->get_value();
+                            if (value) {
+                                if constexpr (std::is_same_v<CppType, String>) {
+                                    // String HybridSets store StringRef*, but
+                                    // ColumnValueRange<String>::CppType is
+                                    // std::string. Construct from the bytes.
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else {
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            *reinterpret_cast<const CppType*>(value)));
+                                }
+                            }
+                            iter->next();
+                        }
+                        rf_cvr.emplace(std::move(typed_rf_cvr));
+                    } else if ((impl->node_type() == TExprNodeType::BINARY_PRED ||
+                                impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) &&
+                               impl->children().size() == 2 && impl->children()[1]->is_literal()) {
+                        // MinMax filter: binary pred with literal bound
+                        auto* literal = assert_cast<VLiteral*>(impl->children()[1].get());
+                        auto col_ptr = literal->get_column_ptr();
+                        auto data = col_ptr->get_data_at(0);
+                        CppType val {};
+                        if constexpr (std::is_same_v<CppType, String>) {
+                            // get_data_at returns a StringRef pointing at the
+                            // raw character bytes; ColumnValueRange<String>'s
+                            // CppType is std::string, so construct one from
+                            // the bytes rather than dereferencing as String.
+                            val = CppType(data.data, data.size);
+                        } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                            val = CppType(data.data, data.size);
+                        } else {
+                            val = *reinterpret_cast<const CppType*>(data.data);
+                        }
+
+                        SQLFilterOp op = convert_opcode_to_filter_op(impl->op());
+                        if (op == FILTER_IN) {
+                            return; // unrecognized opcode, skip
+                        }
+
+                        CvrType typed_rf_cvr(boundary_cvr.column_name(),
+                                             boundaries.front().is_nullable,
+                                             boundary_cvr.precision(), boundary_cvr.scale());
+                        static_cast<void>(typed_rf_cvr.add_range(op, val));
+                        rf_cvr.emplace(std::move(typed_rf_cvr));
+                    }
+                },
+                boundaries.front().boundary_cvr);
+    }
+    if (!rf_cvr.has_value()) {
+        return;
+    }
+
+    for (const auto& pb : boundaries) {
+        if (_pruned_partition_ids.contains(pb.partition_id) ||
+            newly_pruned.contains(pb.partition_id)) {
+            continue;
+        }
+
+        // NULL handling:
+        //   A partition row whose key is NULL matches the RF iff `rf_contains_null`.
+        //   - only_null partition (rows are exclusively NULL): prunable iff !rf_contains_null.
+        //   - mixed (NULL + concrete values): if rf_contains_null, NULL rows alone
+        //     prevent pruning. Otherwise NULL rows can never match, so we ignore
+        //     the NULL portion and let the regular non-NULL intersection decide.
+        if (pb.only_null) {
+            if (!rf_contains_null) {
+                newly_pruned.insert(pb.partition_id);
+            }
+            continue;
+        }
+        if (pb.contains_null && rf_contains_null) {
+            continue;
+        }
+
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+
+                    const auto* typed_rf_cvr = std::get_if<CvrType>(&rf_cvr.value());
+                    DORIS_CHECK(typed_rf_cvr != nullptr);
+
+                    auto boundary_copy = boundary_cvr;
+                    auto rf_cvr_copy = *typed_rf_cvr;
+                    boundary_copy.intersection(rf_cvr_copy);
+                    if (boundary_copy.is_empty_value_range()) {
+                        newly_pruned.insert(pb.partition_id);
+                    }
+                },
+                pb.boundary_cvr);
+    }
+}
+
+Status RuntimeFilterPartitionPruner::prune_by_runtime_filters(
+        const ParsedPartitionBoundaries& parsed, const VExprContextSPtrs& conjuncts,
+        const std::vector<TRuntimeFilterDesc>& rf_descs, int scan_node_id,
+        int64_t* newly_pruned_count) {
+    *newly_pruned_count = 0;
+    if (parsed.empty()) {
+        return Status::OK();
+    }
+    const auto& slot_to_boundaries = parsed.slot_to_boundaries();
+
+    // Build filter_id -> per-partition monotonicity maps.
+    std::unordered_map<int, std::unordered_map<int64_t, TTargetExprMonotonicity::type>>
+            filter_id_to_partition_monotonicity;
+    for (const auto& desc : rf_descs) {
+        if (desc.__isset.planId_to_partition_target_monotonicity) {
+            auto it = desc.planId_to_partition_target_monotonicity.find(scan_node_id);
+            if (it != desc.planId_to_partition_target_monotonicity.end()) {
+                auto& partition_monotonicity = filter_id_to_partition_monotonicity[desc.filter_id];
+                for (const auto& partition_entry : it->second) {
+                    DORIS_CHECK(partition_entry.__isset.partition_id);
+                    DORIS_CHECK(partition_entry.__isset.monotonicity);
+                    DORIS_CHECK(partition_entry.monotonicity !=
+                                TTargetExprMonotonicity::NON_MONOTONIC);
+                    auto [_, inserted] = partition_monotonicity.emplace(
+                            partition_entry.partition_id, partition_entry.monotonicity);
+                    DORIS_CHECK(inserted);
+                }
+            }
+        }
+    }
+
+    // This function is serialized by _conjuncts_lock in the caller, so our reads
+    // of _pruned_partition_ids never race with our writes below. The only concurrent
+    // readers are is_partition_pruned() calls (under shared_lock), which are
+    // properly synchronized by the unique_lock we take when inserting.
+    phmap::flat_hash_set<int64_t> newly_pruned;
+
+    for (const auto& conjunct_ctx : conjuncts) {
+        VExprSPtr root = conjunct_ctx->root();
+        if (!root->is_rf_wrapper()) {
+            continue;
+        }
+
+        VExprSPtr impl = root->get_impl();
+        if (!impl) {
+            continue;
+        }
+
+        if (impl->children().empty()) {
+            continue;
+        }
+
+        auto* wrapper_root = assert_cast<RuntimeFilterExpr*>(root.get());

Review Comment:
   static_cast, already check root->is_rf_wrapper()



##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,866 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            bool range_has_null = false;                                                       \
+            if (is_nullable && !tb.__isset.range_start) {                                      \
+                range_has_null = true;                                                         \
+            }                                                                                  \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                } else if (is_nullable) {                                                      \
+                    range_has_null = true;                                                     \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+            if (range_has_null) {                                                              \
+                boundary.contains_null = true;                                                 \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        DORIS_CHECK(parsed_ok);
+        _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static bool find_unique_slot_ref_impl(const VExpr* expr, const VSlotRef** found) {
+    if (!expr) {
+        return true;
+    }
+    if (expr->is_slot_ref()) {
+        const auto* slot = assert_cast<const VSlotRef*>(expr);
+        if (*found == nullptr) {
+            *found = slot;
+            return true;
+        }
+        if ((*found)->slot_id() != slot->slot_id()) {
+            return false;
+        }
+        DORIS_CHECK((*found)->column_id() == slot->column_id());
+        return true;
+    }
+    for (const auto& child : expr->children()) {
+        if (!find_unique_slot_ref_impl(child.get(), found)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    const VSlotRef* found = nullptr;
+    if (!find_unique_slot_ref_impl(expr, &found)) {
+        return nullptr;
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        DORIS_CHECK(direction != TTargetExprMonotonicity::NON_MONOTONIC);
+        DORIS_CHECK(boundary_partition_ids.contains(partition_id));
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        DORIS_CHECK(slot_ref->slot_id() == leaf_slot_id);
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            DORIS_CHECK(direction_it->second == TTargetExprMonotonicity::MONOTONIC_INCREASING);
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    DORIS_CHECK(slot_type_it != _slot_data_types.end());
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<bool> boundary_is_list(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    std::vector<size_t> list_result_begin(N, 0);
+    std::vector<size_t> list_result_end(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+    size_t list_row_count = 0;
+
+    // Build input blocks for RANGE endpoints and LIST values.
+    Block lo_block;
+    Block hi_block;
+    Block list_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns and LIST value columns. Unbounded RANGE endpoints are not executed
+    // through target_expr; they stay unbounded after projection and swap sides
+    // for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr list_inner_base = inner_type->create_column();                          \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        auto* list_inner = assert_cast<InCol*>(list_inner_base.get());                           \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        list_inner->reserve(N);                                                                  \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        auto list_nulls = ColumnUInt8::create();                                                 \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        list_nulls->reserve(N);                                                                  \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            boundary_is_list[i] = cvr->is_fixed_value_range();                                   \
+            if (boundary_is_list[i]) {                                                           \
+                list_result_begin[i] = list_row_count;                                           \
+                for (const auto& value : cvr->get_fixed_value_set()) {                           \
+                    list_inner->insert_value(value);                                             \
+                    list_nulls->get_data().push_back(0);                                         \
+                    ++list_row_count;                                                            \
+                }                                                                                \
+                list_result_end[i] = list_row_count;                                             \
+                continue;                                                                        \
+            }                                                                                    \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        add_dummy_columns(list_block, list_row_count);                                           \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            auto list_col =                                                                      \
+                    ColumnNullable::create(std::move(list_inner_base), std::move(list_nulls));   \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+            list_block.insert({std::move(list_col), input_type, "leaf_slot"});                   \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+            list_block.insert({std::move(list_inner_base), input_type, "leaf_slot"});            \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    DORIS_CHECK(input_built);
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    int list_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    ColumnPtr list_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        DORIS_CHECK(lo_result_id >= 0);
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        DORIS_CHECK(hi_result_id >= 0);
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    if (list_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &list_block, &list_result_id));
+        DORIS_CHECK(list_result_id >= 0);
+        list_result_col = list_block.get_by_position(list_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if (boundary_is_list[i]) {                                                          \
+                auto cvr = ColumnValueRange<TYPE_##OUTPUT_PT>::create_empty_column_value_range( \
+                        out_nullable, out_precision, out_scale);                                \
+                bool list_has_value = false;                                                    \
+                bool list_has_null = false;                                                     \
+                for (size_t row = list_result_begin[i]; row < list_result_end[i]; ++row) {      \
+                    if (list_result_col->is_null_at(row)) {                                     \
+                        list_has_null = true;                                                   \
+                        continue;                                                               \
+                    }                                                                           \
+                    auto projected_list_value = projected_value(list_result_col, row);          \
+                    static_cast<void>(cvr.add_fixed_value(projected_list_value));               \
+                    list_has_value = true;                                                      \
+                }                                                                               \
+                if (!list_has_value && !list_has_null) {                                        \
+                    continue;                                                                   \
+                }                                                                               \
+                ParsedBoundary projected_boundary;                                              \
+                projected_boundary.partition_id = orig_boundary.partition_id;                   \
+                projected_boundary.slot_id = leaf_slot_id;                                      \
+                projected_boundary.is_nullable = out_nullable;                                  \
+                projected_boundary.contains_null = list_has_null;                               \
+                projected_boundary.only_null = list_has_null && !list_has_value;                \
+                projected_boundary.boundary_cvr = std::move(cvr);                               \
+                projected.emplace_back(std::move(projected_boundary));                          \
+                continue;                                                                       \
+            }                                                                                   \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            /* Use closed projected finite bounds as a conservative envelope for */             \
+            /* non-strict monotonic expressions such as date ceil/floor/cast.    */             \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));       \
+                }                                                                               \
+            } else {                                                                            \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));     \
+                }                                                                               \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));       \
+                }                                                                               \
+            }                                                                                   \
+            ParsedBoundary projected_boundary;                                                  \
+            projected_boundary.partition_id = orig_boundary.partition_id;                       \
+            projected_boundary.slot_id = leaf_slot_id;                                          \
+            projected_boundary.is_nullable = out_nullable;                                      \
+            projected_boundary.only_null = orig_boundary.only_null;                             \
+            projected_boundary.contains_null = orig_boundary.contains_null;                     \
+            projected_boundary.boundary_cvr = std::move(cvr);                                   \
+            projected.emplace_back(std::move(projected_boundary));                              \
+        }                                                                                       \
+        output_built = true;                                                                    \
+        break;                                                                                  \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    DORIS_CHECK(output_built);
+
+    return store_projected_boundaries(std::move(projected));
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    std::optional<ColumnValueRangeType> rf_cvr;
+    if (!boundaries.empty()) {
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {
+                        auto typed_rf_cvr = CvrType::create_empty_column_value_range(
+                                boundaries.front().is_nullable, boundary_cvr.precision(),
+                                boundary_cvr.scale());
+                        auto* iter = hybrid_set->begin();
+                        while (iter->has_next()) {
+                            const void* value = iter->get_value();
+                            if (value) {
+                                if constexpr (std::is_same_v<CppType, String>) {
+                                    // String HybridSets store StringRef*, but
+                                    // ColumnValueRange<String>::CppType is
+                                    // std::string. Construct from the bytes.
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else {
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            *reinterpret_cast<const CppType*>(value)));
+                                }
+                            }
+                            iter->next();
+                        }
+                        rf_cvr.emplace(std::move(typed_rf_cvr));
+                    } else if ((impl->node_type() == TExprNodeType::BINARY_PRED ||
+                                impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) &&
+                               impl->children().size() == 2 && impl->children()[1]->is_literal()) {
+                        // MinMax filter: binary pred with literal bound
+                        auto* literal = assert_cast<VLiteral*>(impl->children()[1].get());
+                        auto col_ptr = literal->get_column_ptr();
+                        auto data = col_ptr->get_data_at(0);
+                        CppType val {};
+                        if constexpr (std::is_same_v<CppType, String>) {
+                            // get_data_at returns a StringRef pointing at the
+                            // raw character bytes; ColumnValueRange<String>'s
+                            // CppType is std::string, so construct one from
+                            // the bytes rather than dereferencing as String.
+                            val = CppType(data.data, data.size);
+                        } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                            val = CppType(data.data, data.size);
+                        } else {
+                            val = *reinterpret_cast<const CppType*>(data.data);
+                        }
+
+                        SQLFilterOp op = convert_opcode_to_filter_op(impl->op());
+                        if (op == FILTER_IN) {
+                            return; // unrecognized opcode, skip
+                        }
+
+                        CvrType typed_rf_cvr(boundary_cvr.column_name(),
+                                             boundaries.front().is_nullable,
+                                             boundary_cvr.precision(), boundary_cvr.scale());
+                        static_cast<void>(typed_rf_cvr.add_range(op, val));
+                        rf_cvr.emplace(std::move(typed_rf_cvr));
+                    }
+                },
+                boundaries.front().boundary_cvr);
+    }
+    if (!rf_cvr.has_value()) {
+        return;
+    }
+
+    for (const auto& pb : boundaries) {
+        if (_pruned_partition_ids.contains(pb.partition_id) ||
+            newly_pruned.contains(pb.partition_id)) {
+            continue;
+        }
+
+        // NULL handling:
+        //   A partition row whose key is NULL matches the RF iff `rf_contains_null`.
+        //   - only_null partition (rows are exclusively NULL): prunable iff !rf_contains_null.
+        //   - mixed (NULL + concrete values): if rf_contains_null, NULL rows alone
+        //     prevent pruning. Otherwise NULL rows can never match, so we ignore
+        //     the NULL portion and let the regular non-NULL intersection decide.
+        if (pb.only_null) {
+            if (!rf_contains_null) {
+                newly_pruned.insert(pb.partition_id);
+            }
+            continue;
+        }
+        if (pb.contains_null && rf_contains_null) {
+            continue;
+        }
+
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+
+                    const auto* typed_rf_cvr = std::get_if<CvrType>(&rf_cvr.value());
+                    DORIS_CHECK(typed_rf_cvr != nullptr);
+
+                    auto boundary_copy = boundary_cvr;
+                    auto rf_cvr_copy = *typed_rf_cvr;
+                    boundary_copy.intersection(rf_cvr_copy);
+                    if (boundary_copy.is_empty_value_range()) {
+                        newly_pruned.insert(pb.partition_id);
+                    }
+                },
+                pb.boundary_cvr);
+    }
+}
+
+Status RuntimeFilterPartitionPruner::prune_by_runtime_filters(
+        const ParsedPartitionBoundaries& parsed, const VExprContextSPtrs& conjuncts,
+        const std::vector<TRuntimeFilterDesc>& rf_descs, int scan_node_id,
+        int64_t* newly_pruned_count) {
+    *newly_pruned_count = 0;
+    if (parsed.empty()) {
+        return Status::OK();
+    }
+    const auto& slot_to_boundaries = parsed.slot_to_boundaries();
+
+    // Build filter_id -> per-partition monotonicity maps.
+    std::unordered_map<int, std::unordered_map<int64_t, TTargetExprMonotonicity::type>>
+            filter_id_to_partition_monotonicity;
+    for (const auto& desc : rf_descs) {
+        if (desc.__isset.planId_to_partition_target_monotonicity) {
+            auto it = desc.planId_to_partition_target_monotonicity.find(scan_node_id);
+            if (it != desc.planId_to_partition_target_monotonicity.end()) {
+                auto& partition_monotonicity = filter_id_to_partition_monotonicity[desc.filter_id];
+                for (const auto& partition_entry : it->second) {
+                    DORIS_CHECK(partition_entry.__isset.partition_id);
+                    DORIS_CHECK(partition_entry.__isset.monotonicity);
+                    DORIS_CHECK(partition_entry.monotonicity !=
+                                TTargetExprMonotonicity::NON_MONOTONIC);
+                    auto [_, inserted] = partition_monotonicity.emplace(
+                            partition_entry.partition_id, partition_entry.monotonicity);
+                    DORIS_CHECK(inserted);
+                }
+            }
+        }
+    }
+
+    // This function is serialized by _conjuncts_lock in the caller, so our reads
+    // of _pruned_partition_ids never race with our writes below. The only concurrent
+    // readers are is_partition_pruned() calls (under shared_lock), which are
+    // properly synchronized by the unique_lock we take when inserting.
+    phmap::flat_hash_set<int64_t> newly_pruned;
+
+    for (const auto& conjunct_ctx : conjuncts) {
+        VExprSPtr root = conjunct_ctx->root();
+        if (!root->is_rf_wrapper()) {
+            continue;
+        }
+
+        VExprSPtr impl = root->get_impl();
+        if (!impl) {
+            continue;
+        }
+
+        if (impl->children().empty()) {
+            continue;
+        }
+
+        auto* wrapper_root = assert_cast<RuntimeFilterExpr*>(root.get());

Review Comment:
   static_cast, already check root->is_rf_wrapper()



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4494441476

   # FE UT Coverage Report
   Increment line coverage `  30.98% (92/297)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4496646451

   # BE UT Coverage Report
   Increment line coverage `62.92% (504/801)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 53.58% (20758/38741) |
   | Line Coverage     | 37.28% (196810/527979) |
   | Region Coverage   | 33.55% (154907/461676) |
   | Branch Coverage   | 34.63% (67810/195795) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4592142245

   # FE UT Coverage Report
   Increment line coverage `  31.37% (96/306)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_b82194ce0fb5ac216aeed034490645f04c19f4e5/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_b82194ce0fb5ac216aeed034490645f04c19f4e5/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4560149031

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4429384142

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 30022 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17712	4007	4041	4007
   q2	q3	10796	878	622	622
   q4	4709	469	344	344
   q5	8187	1339	1134	1134
   q6	362	167	138	138
   q7	933	970	748	748
   q8	11213	1408	1273	1273
   q9	7448	5645	5428	5428
   q10	6447	2079	1812	1812
   q11	481	265	261	261
   q12	688	421	291	291
   q13	18224	3421	2756	2756
   q14	290	286	262	262
   q15	q16	903	870	793	793
   q17	996	1016	790	790
   q18	6559	5837	5729	5729
   q19	1207	1389	1105	1105
   q20	538	410	275	275
   q21	4876	2313	1913	1913
   q22	438	385	341	341
   Total cold run time: 103007 ms
   Total hot run time: 30022 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4828	4733	4798	4733
   q2	q3	4670	4795	4201	4201
   q4	2161	2217	1402	1402
   q5	5033	5057	5216	5057
   q6	196	171	133	133
   q7	2046	1817	1855	1817
   q8	3355	3152	3165	3152
   q9	8510	8482	8559	8482
   q10	4515	4564	4265	4265
   q11	601	438	414	414
   q12	705	756	535	535
   q13	3348	3609	2923	2923
   q14	314	308	287	287
   q15	q16	778	788	690	690
   q17	1341	1320	1307	1307
   q18	8002	7115	7186	7115
   q19	1173	1151	1132	1132
   q20	2216	2206	1940	1940
   q21	6198	5470	4922	4922
   q22	527	476	398	398
   Total cold run time: 60517 ms
   Total hot run time: 54905 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4429111858

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4429112570

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4447561911

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4450215166

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4505871031

   # BE Regression && UT Coverage Report
   Increment line coverage `75.28% (603/801)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.63% (27894/37883) |
   | Line Coverage     | 57.53% (303104/526849) |
   | Region Coverage   | 54.44% (253916/466433) |
   | Branch Coverage   | 55.92% (110043/196776) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4495241204

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 31123 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 6be29ffaf962c85e6c243c68a1c26cf413cdb0b5, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17610	3896	3926	3896
   q2	q3	11000	1435	807	807
   q4	4813	481	355	355
   q5	10591	2271	2133	2133
   q6	412	180	138	138
   q7	956	792	641	641
   q8	9611	1735	1657	1657
   q9	7014	4961	5004	4961
   q10	6433	2120	1792	1792
   q11	449	278	239	239
   q12	644	429	291	291
   q13	18127	3410	2787	2787
   q14	262	258	242	242
   q15	q16	822	800	710	710
   q17	959	870	898	870
   q18	6909	5848	5581	5581
   q19	1190	1218	1109	1109
   q20	513	420	267	267
   q21	5693	2728	2345	2345
   q22	439	363	302	302
   Total cold run time: 104447 ms
   Total hot run time: 31123 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4191	4115	4099	4099
   q2	q3	4571	4921	4339	4339
   q4	2104	2224	1431	1431
   q5	4404	4282	5228	4282
   q6	250	194	144	144
   q7	2065	1788	1549	1549
   q8	2436	2133	2111	2111
   q9	7933	7747	7780	7747
   q10	4781	4491	4040	4040
   q11	563	394	382	382
   q12	760	728	522	522
   q13	3290	3687	2992	2992
   q14	289	293	291	291
   q15	q16	730	749	661	661
   q17	1338	1331	1315	1315
   q18	7973	7200	6770	6770
   q19	1110	1103	1140	1103
   q20	2261	2223	1942	1942
   q21	5326	4641	4480	4480
   q22	559	464	435	435
   Total cold run time: 56934 ms
   Total hot run time: 50635 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4504715563

   # FE UT Coverage Report
   Increment line coverage `  30.98% (92/297)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4449904600

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4572757109

   # BE Regression && UT Coverage Report
   Increment line coverage `84.56% (668/790)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.86% (28155/38121) |
   | Line Coverage     | 57.78% (306127/529792) |
   | Region Coverage   | 54.48% (256051/469957) |
   | Branch Coverage   | 56.00% (111235/198626) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4555068765

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 171516 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 36cd8599c1b5eba6dcfb83d6c88457494678e987, data reload: false
   
   query5	4339	638	480	480
   query6	456	210	183	183
   query7	4970	566	308	308
   query8	374	223	205	205
   query9	8766	4174	4120	4120
   query10	458	328	260	260
   query11	5896	2440	2475	2440
   query12	150	104	100	100
   query13	1259	611	428	428
   query14	6383	5451	5125	5125
   query14_1	4440	4479	4437	4437
   query15	204	201	176	176
   query16	1014	452	456	452
   query17	1092	702	563	563
   query18	2608	479	345	345
   query19	199	189	141	141
   query20	115	105	103	103
   query21	214	143	121	121
   query22	13667	13548	13441	13441
   query23	17389	16461	16231	16231
   query23_1	16396	16346	16248	16248
   query24	7524	1770	1317	1317
   query24_1	1348	1338	1339	1338
   query25	547	446	396	396
   query26	1307	310	163	163
   query27	2667	566	340	340
   query28	4445	2045	2034	2034
   query29	1086	599	491	491
   query30	310	239	225	225
   query31	1143	1071	968	968
   query32	121	64	59	59
   query33	499	325	259	259
   query34	1185	1155	637	637
   query35	758	789	703	703
   query36	1403	1348	1256	1256
   query37	156	119	94	94
   query38	3212	3133	3035	3035
   query39	934	911	890	890
   query39_1	883	866	884	866
   query40	217	125	104	104
   query41	64	63	63	63
   query42	95	95	93	93
   query43	321	326	275	275
   query44	
   query45	197	194	175	175
   query46	1142	1228	749	749
   query47	2387	2411	2256	2256
   query48	416	424	307	307
   query49	625	473	350	350
   query50	977	354	249	249
   query51	4394	4340	4282	4282
   query52	92	89	77	77
   query53	249	272	196	196
   query54	266	224	198	198
   query55	81	77	71	71
   query56	240	231	229	229
   query57	1431	1423	1327	1327
   query58	244	220	203	203
   query59	1552	1676	1442	1442
   query60	299	252	237	237
   query61	160	157	153	153
   query62	697	663	590	590
   query63	234	183	196	183
   query64	2529	860	647	647
   query65	
   query66	1735	468	343	343
   query67	29740	29737	29460	29460
   query68	
   query69	426	320	265	265
   query70	982	959	929	929
   query71	306	219	207	207
   query72	2990	2719	2585	2585
   query73	853	765	432	432
   query74	5156	4993	4777	4777
   query75	2701	2615	2253	2253
   query76	2328	1155	828	828
   query77	366	390	286	286
   query78	12503	12247	11921	11921
   query79	1298	1040	763	763
   query80	566	515	422	422
   query81	453	287	249	249
   query82	240	159	131	131
   query83	280	281	260	260
   query84	261	145	117	117
   query85	948	621	502	502
   query86	336	317	289	289
   query87	3388	3345	3242	3242
   query88	3615	2749	2706	2706
   query89	418	384	337	337
   query90	2192	177	199	177
   query91	177	173	143	143
   query92	66	63	58	58
   query93	1475	1542	895	895
   query94	530	343	333	333
   query95	687	385	348	348
   query96	1091	790	358	358
   query97	2713	2683	2566	2566
   query98	212	208	206	206
   query99	1196	1194	1035	1035
   Total cold run time: 251467 ms
   Total hot run time: 171516 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4541735618

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3310374444


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.h:
##########
@@ -0,0 +1,163 @@
+// 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.
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <shared_mutex>
+#include <unordered_map>
+#include <vector>
+
+#include "common/global_types.h"
+#include "common/status.h"
+#include "core/data_type/data_type.h"
+#include "exec/common/hash_table/phmap_fwd_decl.h"
+#include "exprs/vexpr_fwd.h"
+#include "storage/olap_scan_common.h"
+
+namespace doris {
+
+class SlotDescriptor;
+class VExprContext;
+struct TPartitionBoundary;
+struct TRuntimeFilterDesc;
+struct TTargetExprMonotonicity;
+
+// Parsed representation of one partition boundary for one slot column.
+struct ParsedBoundary {
+    int64_t partition_id = 0;
+    SlotId slot_id = 0;
+    bool is_nullable = false;
+    ColumnValueRangeType boundary_cvr;
+    // True if the partition's value set is exactly {NULL} (e.g. LIST
+    // partition whose only key is NULL). The CVR alone cannot encode
+    // "only NULL" -- it stays as the whole range with contain_null=true
+    // -- so we track it explicitly to enable accurate pruning.
+    bool only_null = false;
+    // True if the partition's value set includes NULL (covers both
+    // only-NULL and mixed LIST partitions). Tracked separately because
+    // ColumnValueRange::set_contain_null(true) destructively clears the
+    // fixed-value set, so we cannot stash the NULL flag inside the CVR
+    // alongside the concrete values.
+    bool contains_null = false;
+};
+
+// Immutable, fragment-shared parse result of TPartitionBoundary list.
+//
+// Parsing is expensive (constructs VLiteral per literal, materializes a
+// ColumnPtr, builds ColumnValueRange) and depends only on plan-time data.
+// All pipeline instances of the same fragment share one parse, performed
+// in OperatorX::prepare() which is single-threaded fragment setup.
+class ParsedPartitionBoundaries {
+public:
+    ParsedPartitionBoundaries() = default;
+
+    // Build the parse result from the thrift `boundaries` list. Caller must
+    // ensure this is invoked at most once per instance (OperatorX::prepare()
+    // is the natural call site).
+    Status parse(const std::vector<TPartitionBoundary>& boundaries,
+                 const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs);
+
+    bool empty() const { return _slot_to_boundaries.empty(); }
+    int64_t total_partitions() const { return _total_partition_count; }
+
+    const std::unordered_map<SlotId, std::vector<ParsedBoundary>>& slot_to_boundaries() const {
+        return _slot_to_boundaries;
+    }
+
+    // Lazily compute target-domain boundaries for a monotonic RF target.
+    // `target_expr` is `impl->children()[0]` of the RF wrapper (a sub-tree of
+    // the conjunct). `leaf_slot_id` is the unique VSlotRef leaf inside it
+    // (FE asserted target_expr has exactly one input slot). `leaf_column_id`
+    // is that slot ref's `column_id()` -- the position in the runtime block.
+    // Only partitions present in `partition_directions` are projected and each
+    // partition uses its own FE-proven local direction.
+    // `ctx` is the conjunct's VExprContext (used to execute the sub-expression).
+    //
+    // Direct SlotRef targets reuse the parsed partition boundaries. Expression
+    // targets project finite RANGE endpoints by executing `target_expr`.
+    //
+    // Returns an empty vector when no selected boundary can be projected (e.g.
+    // every candidate contains NULL). Unexpected FE/BE metadata mismatches
+    // return an error instead of disabling pruning silently.
+    // The shared_ptr keeps the computed vector alive even if another pipeline
+    // instance inserts into the shared map and triggers unordered_map rehash.
+    //
+    // Direction:
+    //   MONOTONIC_INCREASING: projected lo and hi keep their roles
+    //   MONOTONIC_DECREASING: swap (projected lo, hi) -> (hi, lo)
+    //
+    // Open endpoints (MINVALUE/MAXVALUE) stay open after projection and swap
+    // sides for monotonic decreasing targets. Boundaries containing NULL
+    // partition values, or finite endpoints that project to NULL, are omitted
+    // from the result so this RF conservatively leaves them unpruned.
+    Status get_or_compute_projected_boundaries(
+            int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+            const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,

Review Comment:
   This header only forward-declares `struct TTargetExprMonotonicity` above, but this public signature uses the nested type `TTargetExprMonotonicity::type`. A nested type cannot be named through an incomplete forward declaration, and `runtime_filter_partition_pruner.cpp` includes this header before including `gen_cpp/PlanNodes_types.h`, so the new file can fail to compile as soon as the header is parsed. Please include the generated thrift definition in the header (or avoid exposing the nested thrift type in the header signature) instead of relying on a forward declaration.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "HappenLee (via GitHub)" <gi...@apache.org>.
HappenLee commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3330500997


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,866 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \

Review Comment:
   如果不是nullable是不是规划错误要报错呢?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4590367114

   # FE Regression Coverage Report
   Increment line coverage `  21.76% (158/726)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_586640a94f47548a444d49ee53d5474cf04e3ae0_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_586640a94f47548a444d49ee53d5474cf04e3ae0_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4267225083

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4441069036

   # BE UT Coverage Report
   Increment line coverage `1.15% (8/698)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/ca30c183fbab4ffab1f11b3d91b0561736c7c199_ca30c183fbab4ffab1f11b3d91b0561736c7c199/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/ca30c183fbab4ffab1f11b3d91b0561736c7c199_ca30c183fbab4ffab1f11b3d91b0561736c7c199/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 53.53% (20616/38513) |
   | Line Coverage     | 37.15% (194893/524665) |
   | Region Coverage   | 33.45% (152527/455984) |
   | Branch Coverage   | 34.27% (66511/194075) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4440437251

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3318305519


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,845 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        DORIS_CHECK(parsed_ok);
+        _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (expr->is_slot_ref()) {
+        return assert_cast<const VSlotRef*>(expr);
+    }
+    const VSlotRef* found = nullptr;
+    for (const auto& child : expr->children()) {
+        const VSlotRef* c = find_unique_slot_ref(child.get());
+        if (c) {
+            if (found) {
+                return nullptr; // multiple slot refs, can't handle
+            }
+            found = c;
+        }
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        DORIS_CHECK(direction != TTargetExprMonotonicity::NON_MONOTONIC);
+        DORIS_CHECK(boundary_partition_ids.contains(partition_id));
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        DORIS_CHECK(slot_ref->slot_id() == leaf_slot_id);
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            DORIS_CHECK(direction_it->second == TTargetExprMonotonicity::MONOTONIC_INCREASING);
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    DORIS_CHECK(slot_type_it != _slot_data_types.end());
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<bool> boundary_is_list(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    std::vector<size_t> list_result_begin(N, 0);
+    std::vector<size_t> list_result_end(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+    size_t list_row_count = 0;
+
+    // Build input blocks for RANGE endpoints and LIST values.
+    Block lo_block;
+    Block hi_block;
+    Block list_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns and LIST value columns. Unbounded RANGE endpoints are not executed
+    // through target_expr; they stay unbounded after projection and swap sides
+    // for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr list_inner_base = inner_type->create_column();                          \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        auto* list_inner = assert_cast<InCol*>(list_inner_base.get());                           \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        list_inner->reserve(N);                                                                  \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        auto list_nulls = ColumnUInt8::create();                                                 \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        list_nulls->reserve(N);                                                                  \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            boundary_is_list[i] = cvr->is_fixed_value_range();                                   \
+            if (boundary_is_list[i]) {                                                           \
+                list_result_begin[i] = list_row_count;                                           \
+                for (const auto& value : cvr->get_fixed_value_set()) {                           \
+                    list_inner->insert_value(value);                                             \
+                    list_nulls->get_data().push_back(0);                                         \
+                    ++list_row_count;                                                            \
+                }                                                                                \
+                list_result_end[i] = list_row_count;                                             \
+                continue;                                                                        \
+            }                                                                                    \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        add_dummy_columns(list_block, list_row_count);                                           \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            auto list_col =                                                                      \
+                    ColumnNullable::create(std::move(list_inner_base), std::move(list_nulls));   \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+            list_block.insert({std::move(list_col), input_type, "leaf_slot"});                   \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+            list_block.insert({std::move(list_inner_base), input_type, "leaf_slot"});            \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    DORIS_CHECK(input_built);
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    int list_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    ColumnPtr list_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        DORIS_CHECK(lo_result_id >= 0);
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        DORIS_CHECK(hi_result_id >= 0);
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    if (list_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &list_block, &list_result_id));
+        DORIS_CHECK(list_result_id >= 0);
+        list_result_col = list_block.get_by_position(list_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if (boundary_is_list[i]) {                                                          \
+                auto cvr = ColumnValueRange<TYPE_##OUTPUT_PT>::create_empty_column_value_range( \
+                        out_nullable, out_precision, out_scale);                                \
+                bool list_has_value = false;                                                    \
+                bool list_has_null = false;                                                     \
+                for (size_t row = list_result_begin[i]; row < list_result_end[i]; ++row) {      \
+                    if (list_result_col->is_null_at(row)) {                                     \
+                        list_has_null = true;                                                   \
+                        continue;                                                               \
+                    }                                                                           \
+                    auto projected_list_value = projected_value(list_result_col, row);          \
+                    static_cast<void>(cvr.add_fixed_value(projected_list_value));               \
+                    list_has_value = true;                                                      \
+                }                                                                               \
+                if (!list_has_value && !list_has_null) {                                        \
+                    continue;                                                                   \
+                }                                                                               \
+                ParsedBoundary projected_boundary;                                              \
+                projected_boundary.partition_id = orig_boundary.partition_id;                   \
+                projected_boundary.slot_id = leaf_slot_id;                                      \
+                projected_boundary.is_nullable = out_nullable;                                  \
+                projected_boundary.contains_null = list_has_null;                               \
+                projected_boundary.only_null = list_has_null && !list_has_value;                \
+                projected_boundary.boundary_cvr = std::move(cvr);                               \
+                projected.emplace_back(std::move(projected_boundary));                          \
+                continue;                                                                       \
+            }                                                                                   \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            /* Use closed projected finite bounds as a conservative envelope for */             \
+            /* non-strict monotonic expressions such as date ceil/floor/cast.    */             \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));       \
+                }                                                                               \
+            } else {                                                                            \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));     \
+                }                                                                               \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));       \
+                }                                                                               \
+            }                                                                                   \
+            ParsedBoundary projected_boundary;                                                  \
+            projected_boundary.partition_id = orig_boundary.partition_id;                       \
+            projected_boundary.slot_id = leaf_slot_id;                                          \
+            projected_boundary.is_nullable = out_nullable;                                      \
+            projected_boundary.only_null = orig_boundary.only_null;                             \
+            projected_boundary.contains_null = orig_boundary.contains_null;                     \
+            projected_boundary.boundary_cvr = std::move(cvr);                                   \
+            projected.emplace_back(std::move(projected_boundary));                              \
+        }                                                                                       \
+        output_built = true;                                                                    \
+        break;                                                                                  \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    DORIS_CHECK(output_built);
+
+    return store_projected_boundaries(std::move(projected));
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    std::optional<ColumnValueRangeType> rf_cvr;
+    if (!boundaries.empty()) {
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {
+                        auto typed_rf_cvr = CvrType::create_empty_column_value_range(
+                                boundaries.front().is_nullable, boundary_cvr.precision(),
+                                boundary_cvr.scale());
+                        auto* iter = hybrid_set->begin();
+                        while (iter->has_next()) {
+                            const void* value = iter->get_value();
+                            if (value) {
+                                if constexpr (std::is_same_v<CppType, String>) {
+                                    // String HybridSets store StringRef*, but
+                                    // ColumnValueRange<String>::CppType is
+                                    // std::string. Construct from the bytes.
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else {
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            *reinterpret_cast<const CppType*>(value)));
+                                }
+                            }
+                            iter->next();
+                        }
+                        rf_cvr.emplace(std::move(typed_rf_cvr));
+                    } else if ((impl->node_type() == TExprNodeType::BINARY_PRED ||
+                                impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) &&
+                               impl->children().size() == 2 && impl->children()[1]->is_literal()) {
+                        // MinMax filter: binary pred with literal bound
+                        auto* literal = assert_cast<VLiteral*>(impl->children()[1].get());
+                        auto col_ptr = literal->get_column_ptr();
+                        auto data = col_ptr->get_data_at(0);
+                        CppType val {};
+                        if constexpr (std::is_same_v<CppType, String>) {
+                            // get_data_at returns a StringRef pointing at the
+                            // raw character bytes; ColumnValueRange<String>'s
+                            // CppType is std::string, so construct one from
+                            // the bytes rather than dereferencing as String.
+                            val = CppType(data.data, data.size);
+                        } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                            val = CppType(data.data, data.size);
+                        } else {
+                            val = *reinterpret_cast<const CppType*>(data.data);
+                        }
+
+                        SQLFilterOp op = convert_opcode_to_filter_op(impl->op());
+                        if (op == FILTER_IN) {
+                            return; // unrecognized opcode, skip
+                        }
+
+                        CvrType typed_rf_cvr(boundary_cvr.column_name(),
+                                             boundaries.front().is_nullable,
+                                             boundary_cvr.precision(), boundary_cvr.scale());
+                        static_cast<void>(typed_rf_cvr.add_range(op, val));
+                        rf_cvr.emplace(std::move(typed_rf_cvr));
+                    }
+                },
+                boundaries.front().boundary_cvr);
+    }
+    if (!rf_cvr.has_value()) {
+        return;
+    }
+
+    for (const auto& pb : boundaries) {
+        if (_pruned_partition_ids.contains(pb.partition_id) ||
+            newly_pruned.contains(pb.partition_id)) {
+            continue;
+        }
+
+        // NULL handling:
+        //   A partition row whose key is NULL matches the RF iff `rf_contains_null`.
+        //   - only_null partition (rows are exclusively NULL): prunable iff !rf_contains_null.
+        //   - mixed (NULL + concrete values): if rf_contains_null, NULL rows alone
+        //     prevent pruning. Otherwise NULL rows can never match, so we ignore
+        //     the NULL portion and let the regular non-NULL intersection decide.
+        if (pb.only_null) {
+            if (!rf_contains_null) {
+                newly_pruned.insert(pb.partition_id);
+            }
+            continue;
+        }
+        if (pb.contains_null && rf_contains_null) {
+            continue;
+        }
+
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+
+                    const auto* typed_rf_cvr = std::get_if<CvrType>(&rf_cvr.value());
+                    DORIS_CHECK(typed_rf_cvr != nullptr);
+
+                    auto boundary_copy = boundary_cvr;
+                    auto rf_cvr_copy = *typed_rf_cvr;
+                    boundary_copy.intersection(rf_cvr_copy);
+                    if (boundary_copy.is_empty_value_range()) {
+                        newly_pruned.insert(pb.partition_id);
+                    }
+                },
+                pb.boundary_cvr);
+    }
+}
+
+Status RuntimeFilterPartitionPruner::prune_by_runtime_filters(
+        const ParsedPartitionBoundaries& parsed, const VExprContextSPtrs& conjuncts,
+        const std::vector<TRuntimeFilterDesc>& rf_descs, int scan_node_id,
+        int64_t* newly_pruned_count) {
+    *newly_pruned_count = 0;
+    if (parsed.empty()) {
+        return Status::OK();
+    }
+    const auto& slot_to_boundaries = parsed.slot_to_boundaries();
+
+    // Build filter_id -> per-partition monotonicity maps.
+    std::unordered_map<int, std::unordered_map<int64_t, TTargetExprMonotonicity::type>>
+            filter_id_to_partition_monotonicity;
+    for (const auto& desc : rf_descs) {
+        if (desc.__isset.planId_to_partition_target_monotonicity) {
+            auto it = desc.planId_to_partition_target_monotonicity.find(scan_node_id);
+            if (it != desc.planId_to_partition_target_monotonicity.end()) {
+                auto& partition_monotonicity = filter_id_to_partition_monotonicity[desc.filter_id];
+                for (const auto& partition_entry : it->second) {
+                    DORIS_CHECK(partition_entry.__isset.partition_id);
+                    DORIS_CHECK(partition_entry.__isset.monotonicity);
+                    DORIS_CHECK(partition_entry.monotonicity !=
+                                TTargetExprMonotonicity::NON_MONOTONIC);
+                    auto [_, inserted] = partition_monotonicity.emplace(
+                            partition_entry.partition_id, partition_entry.monotonicity);
+                    DORIS_CHECK(inserted);
+                }
+            }
+        }
+    }
+
+    // This function is serialized by _conjuncts_lock in the caller, so our reads
+    // of _pruned_partition_ids never race with our writes below. The only concurrent
+    // readers are is_partition_pruned() calls (under shared_lock), which are
+    // properly synchronized by the unique_lock we take when inserting.
+    phmap::flat_hash_set<int64_t> newly_pruned;
+
+    for (const auto& conjunct_ctx : conjuncts) {
+        VExprSPtr root = conjunct_ctx->root();
+        if (!root->is_rf_wrapper()) {
+            continue;
+        }
+
+        VExprSPtr impl = root->get_impl();
+        if (!impl) {
+            continue;
+        }
+
+        if (impl->children().empty()) {
+            continue;
+        }
+
+        auto* wrapper_root = assert_cast<RuntimeFilterExpr*>(root.get());
+        int filter_id = wrapper_root->filter_id();
+
+        VExprSPtr target_subtree = impl->children()[0];
+
+        auto partition_mono_it = filter_id_to_partition_monotonicity.find(filter_id);
+        bool has_partition_mono = partition_mono_it != filter_id_to_partition_monotonicity.end() &&
+                                  !partition_mono_it->second.empty();
+        if (!has_partition_mono) {
+            continue;
+        }
+
+        const VSlotRef* leaf_slot = find_unique_slot_ref(target_subtree.get());

Review Comment:
   FE currently accepts LIST-partition expression targets as long as the expression is deterministic and `findUniqueSlotRef()` resolves to one partition slot. That Java helper still returns the slot when the same slot appears more than once, for example a target like `concat(k, k)` or `k + k` on `PARTITION BY LIST(k)`. But the BE helper above returns `nullptr` as soon as it sees a second `VSlotRef`, even if it is the same slot, so this `DORIS_CHECK` aborts the query after FE has marked the filter as partition-prunable.
   
   This is distinct from the existing classifier monotonicity thread: LIST pruning does not use monotonicity, and the failure here is an FE/BE acceptance mismatch that can crash instead of conservatively skipping pruning. Please either make FE reject repeated-slot expression targets for LIST pruning, or make the BE helper accept repeated references to the same slot id and add a regression case for that shape.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4570626122

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d_32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d_32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.06% (1854/2375) |
   | Line Coverage     | 64.54% (33342/51663) |
   | Region Coverage   | 65.24% (16535/25343) |
   | Branch Coverage   | 55.75% (8838/15854) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4571689394

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 31522 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17661	4002	4007	4002
   q2	q3	10996	1447	836	836
   q4	4805	483	350	350
   q5	10690	2296	2142	2142
   q6	402	181	141	141
   q7	1024	779	656	656
   q8	9605	1740	1688	1688
   q9	7083	4986	4965	4965
   q10	6547	2331	1905	1905
   q11	440	280	246	246
   q12	695	425	297	297
   q13	18249	3487	2814	2814
   q14	265	261	243	243
   q15	q16	816	796	712	712
   q17	947	945	879	879
   q18	7048	5779	5571	5571
   q19	1232	1197	1172	1172
   q20	510	389	266	266
   q21	5664	2565	2334	2334
   q22	452	370	303	303
   Total cold run time: 105131 ms
   Total hot run time: 31522 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4360	4254	4290	4254
   q2	q3	4546	4940	4331	4331
   q4	2078	2210	1391	1391
   q5	4743	4839	5114	4839
   q6	241	184	140	140
   q7	1988	1820	1604	1604
   q8	2445	2179	2349	2179
   q9	8078	7991	8192	7991
   q10	4808	4830	4368	4368
   q11	575	421	373	373
   q12	756	757	538	538
   q13	3300	3629	2966	2966
   q14	308	305	268	268
   q15	q16	739	741	640	640
   q17	1361	1338	1326	1326
   q18	8012	7152	6899	6899
   q19	1132	1117	1099	1099
   q20	2212	2240	1943	1943
   q21	5277	4586	4422	4422
   q22	531	468	409	409
   Total cold run time: 57490 ms
   Total hot run time: 51980 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4570295094

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4570403351

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt merged PR #62589:
URL: https://github.com/apache/doris/pull/62589


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4288149525

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3108771902


##########
regression-test/data/query_p0/runtime_filter/rf_partition_pruning.out:
##########
@@ -0,0 +1,106 @@
+-- This file is automatically generated. You should know what you did if you want to edit this
+-- !range_int_in --
+1	10	a
+3	50	c
+5	90	e
+
+-- !range_int_minmax --
+1	10	a
+3	50	c
+5	90	e
+
+-- !range_date_in --
+1	2024-01-15	jan
+2	2024-02-20	feb
+3	2024-03-10	mar
+
+-- !range_date_minmax --
+1	2024-01-15	jan
+2	2024-02-20	feb
+3	2024-03-10	mar
+
+-- !list_str_in --
+1	Beijing	a
+2	Beijing	b
+3	Beijing	c
+
+-- !list_int_in --
+1	1	a
+2	1	b
+5	3	e
+6	3	f
+
+-- !no_pruning --
+13	250	m
+18	350	r
+3	50	c
+8	150	h
+
+-- !range_int_agg --
+3	10	90
+
+-- !list_str_agg --
+Beijing	3
+
+-- !list_int_multi --
+3	2	c
+4	2	d
+7	4	g
+8	4	h
+
+-- !bigint_range_in --
+1	100	v0a
+11	250100	v5a
+12	275000	v5b
+2	25000	v0b
+
+-- !bigint_range_minmax --
+1	100	v0a
+11	250100	v5a
+12	275000	v5b
+2	25000	v0b
+
+-- !datetime_range_in --
+1	2024-02-15T10:00	jan
+
+-- !datetime_range_minmax --
+1	2024-02-15T10:00	jan
+
+-- !list_50_in --
+10	10	cat10
+25	25	cat25
+3	3	cat3
+40	40	cat40
+50	50	cat50
+
+-- !neg_range_in --
+7	-50	g
+8	-10	h
+
+-- !neg_range_minmax --
+7	-50	g
+8	-10	h
+
+-- !desc_dim_in --
+1	10	a
+3	50	c
+5	90	e
+
+-- !desc_dim_minmax --
+1	10	a
+3	50	c
+5	90	e
+
+-- !combined_rf --
+1	10	a
+3	50	c
+5	90	e
+
+-- !span_in --
+13	250	m
+3	50	c
+
+-- !span_minmax --
+13	250	m
+3	50	c
+

Review Comment:
   The suite now defines five more ordered result checks, but this golden file still stops at `span_minmax`. The missing sections are `list_only_null_pruned`, `list_only_null_minmax`, `list_mixed_value_kept`, `list_mixed_nullsafe`, and `range_multi_first_col`, so `run-regression-test.sh -d query_p0 -s rf_partition_pruning` will fail at the first new `order_qt_*` case.



##########
regression-test/suites/query_p0/runtime_filter/rf_partition_pruning.groovy:
##########
@@ -0,0 +1,1120 @@
+// 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.
+
+import org.apache.doris.regression.action.ProfileAction
+
+// Marked nonConcurrent: this suite asserts on FE query-profile counters
+// (TotalPartitionsForRFPruning / PartitionsPrunedByRuntimeFilter). Even
+// though each query embeds a unique UUID token to look up its own profile,
+// the FE profile list is bounded (max_query_profile_num) and shared across
+// sessions, so heavy parallel traffic could evict our profile before the
+// poller finds it. Running serially keeps the assertions deterministic.
+suite("rf_partition_pruning", "nonConcurrent") {
+    sql "set enable_runtime_filter_prune=true;"
+
+    // ---- Profile utilities ----
+    def profileAction = new ProfileAction(context)
+
+    def getProfileByToken = { String token ->

Review Comment:
   This helper still turns `profile was not visible yet` into `total=0/pruned=0`: if the profile list does not contain the token within `15 * 500ms`, `getProfileByToken()` returns `""` and `extractCounterSum()` quietly reads that as zero. On a slow or loaded FE that makes the suite fail even though pruning worked. Please fail explicitly when the profile is still missing, or keep polling until it appears / the query is known to be gone.



##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,392 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <utility>
+
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// Helper to extract a typed value from a ColumnPtr's first row.
+// Must be a template function so that `if constexpr` properly discards
+// the inapplicable branch during instantiation.
+template <PrimitiveType PT>
+static typename PrimitiveTypeTraits<PT>::CppType extract_value_from_column(const ColumnPtr& col) {
+    using CppType = typename PrimitiveTypeTraits<PT>::CppType;
+    auto data = col->get_data_at(0);
+    if constexpr (is_string_type(PT)) {
+        return CppType(data.data, data.size);
+    } else {
+        return *reinterpret_cast<const CppType*>(data.data);
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+void RuntimeFilterPartitionPruner::parse_boundaries(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            continue;
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            continue;
+        }
+        SlotDescriptor* slot = slot_it->second;
+        PrimitiveType ptype = slot->type()->get_primitive_type();
+        int precision = cast_set<int>(slot->type()->get_precision());
+        int scale = cast_set<int>(slot->type()->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) break;                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node =                                                                \
+                [&](const TExprNode& node) -> std::optional<std::pair<CppType, ColumnPtr>> {   \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            VLiteral literal(node);                                                            \
+            auto col_ptr = literal.get_column_ptr();                                           \
+            auto val = extract_value_from_column<TYPE_##NAME>(col_ptr);                        \
+            return std::make_optional(std::pair<CppType, ColumnPtr> {val, col_ptr});           \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                auto& [val, col_ptr] = *parsed;                                                \
+                boundary.literal_columns.push_back(std::move(col_ptr));                        \
+                static_cast<void>(empty_cvr.add_fixed_value(val));                             \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    auto& [val, col_ptr] = *parsed;                                            \
+                    boundary.literal_columns.push_back(std::move(col_ptr));                    \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, val));             \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    auto& [val, col_ptr] = *parsed;                                            \
+                    boundary.literal_columns.push_back(std::move(col_ptr));                    \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, val));                           \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _partition_column_slot_ids.insert(slot_id);
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_partition_column_slot_ids.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const VExprSPtr& impl, SlotId slot_id, phmap::flat_hash_set<int64_t>& newly_pruned) {
+    auto boundaries_it = _slot_to_boundaries.find(slot_id);
+    if (boundaries_it == _slot_to_boundaries.end()) {
+        return;
+    }
+
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    for (const auto& pb : boundaries_it->second) {
+        if (_pruned_partition_ids.contains(pb.partition_id) ||
+            newly_pruned.contains(pb.partition_id)) {
+            continue;
+        }
+
+        // NULL handling:
+        //   A partition row whose key is NULL matches the RF iff `rf_contains_null`.
+        //   - only_null partition (rows are exclusively NULL): prunable iff !rf_contains_null.
+        //   - mixed (NULL + concrete values): if rf_contains_null, NULL rows alone
+        //     prevent pruning. Otherwise NULL rows can never match, so we ignore
+        //     the NULL portion and let the regular non-NULL intersection decide.
+        if (pb.only_null) {
+            if (!rf_contains_null) {
+                newly_pruned.insert(pb.partition_id);
+            }
+            continue;
+        }
+        if (pb.contains_null && rf_contains_null) {
+            continue;
+        }
+
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();

Review Comment:
   `VBloomPredicate` falls through this pruning logic today. In the real Bloom case (`runtime_filter_type='BLOOM_FILTER'`, or `IN_OR_BLOOM_FILTER` after the wrapper switches past `runtime_filter_max_in_num`), `impl->node_type()==BLOOM_PRED`, `get_set_func()==nullptr`, and we never enter the MinMax branch either. The result is that the new OLAP partition-pruning path becomes a no-op for Bloom RFs, so LIST-partition scans lose pruning entirely once the filter stops being an IN set.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4289197162

   # FE Regression Coverage Report
   Increment line coverage `  76.34% (100/131)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6f985006ec32cea727f90e22b3cd688c1a0626e1_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6f985006ec32cea727f90e22b3cd688c1a0626e1_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4267408412

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/141c78da888bc5cd8198ffebb607430c90015f0d_141c78da888bc5cd8198ffebb607430c90015f0d_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/141c78da888bc5cd8198ffebb607430c90015f0d_141c78da888bc5cd8198ffebb607430c90015f0d_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.48% (1798/2291) |
   | Line Coverage     | 64.12% (32283/50345) |
   | Region Coverage   | 65.03% (16235/24967) |
   | Branch Coverage   | 55.54% (8675/15620) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4429681479

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 168934 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c, data reload: false
   
   query5	4318	631	492	492
   query6	441	195	179	179
   query7	4867	555	276	276
   query8	359	218	204	204
   query9	8810	4035	4040	4035
   query10	434	315	253	253
   query11	6189	2366	2156	2156
   query12	159	105	98	98
   query13	1276	607	430	430
   query14	7404	5333	4963	4963
   query14_1	4353	4313	4288	4288
   query15	213	198	175	175
   query16	1013	463	455	455
   query17	1289	746	608	608
   query18	2727	493	356	356
   query19	321	188	150	150
   query20	116	109	106	106
   query21	214	146	122	122
   query22	13721	13953	14546	13953
   query23	17240	16572	16249	16249
   query23_1	16330	16249	16245	16245
   query24	8053	1817	1406	1406
   query24_1	1419	1397	1364	1364
   query25	551	453	391	391
   query26	1275	312	168	168
   query27	2537	621	327	327
   query28	4318	1980	1957	1957
   query29	1074	608	501	501
   query30	300	235	197	197
   query31	1097	1057	933	933
   query32	101	58	57	57
   query33	519	315	252	252
   query34	1163	1175	636	636
   query35	741	772	650	650
   query36	1292	1317	1152	1152
   query37	148	103	87	87
   query38	3184	3096	3062	3062
   query39	915	921	881	881
   query39_1	856	901	874	874
   query40	228	130	116	116
   query41	62	60	61	60
   query42	94	90	93	90
   query43	310	313	271	271
   query44	
   query45	195	188	171	171
   query46	1075	1188	709	709
   query47	2303	2275	2203	2203
   query48	387	403	298	298
   query49	620	504	379	379
   query50	724	284	220	220
   query51	4285	4295	4183	4183
   query52	88	89	79	79
   query53	248	259	200	200
   query54	258	219	199	199
   query55	75	77	68	68
   query56	230	221	210	210
   query57	1394	1368	1296	1296
   query58	242	212	211	211
   query59	1543	1611	1389	1389
   query60	310	259	243	243
   query61	162	152	155	152
   query62	666	622	563	563
   query63	229	184	188	184
   query64	2478	811	658	658
   query65	
   query66	1726	485	351	351
   query67	30073	29925	29264	29264
   query68	
   query69	420	311	262	262
   query70	977	944	900	900
   query71	303	225	215	215
   query72	3038	2550	2375	2375
   query73	864	769	412	412
   query74	5085	4913	4751	4751
   query75	2731	2638	2277	2277
   query76	2282	1138	784	784
   query77	364	380	292	292
   query78	12876	13026	12423	12423
   query79	1481	966	682	682
   query80	742	493	410	410
   query81	465	287	236	236
   query82	574	167	128	128
   query83	349	275	250	250
   query84	264	142	114	114
   query85	889	487	430	430
   query86	404	289	277	277
   query87	3412	3332	3168	3168
   query88	3565	2683	2666	2666
   query89	421	366	327	327
   query90	1781	185	180	180
   query91	178	196	142	142
   query92	64	62	58	58
   query93	977	967	553	553
   query94	601	354	294	294
   query95	687	364	423	364
   query96	1027	794	322	322
   query97	2679	2673	2535	2535
   query98	213	210	209	209
   query99	1087	1135	1005	1005
   Total cold run time: 252526 ms
   Total hot run time: 168934 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4432433257

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 170082 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit e7a084166584e3ccdc5ae8b6bd5fc50d3c0cb832, data reload: false
   
   query5	4330	630	481	481
   query6	422	203	180	180
   query7	4826	559	308	308
   query8	364	223	215	215
   query9	8808	4112	4098	4098
   query10	456	308	254	254
   query11	5925	2334	2155	2155
   query12	152	101	95	95
   query13	1258	620	431	431
   query14	6408	5301	5016	5016
   query14_1	4343	4360	4348	4348
   query15	206	203	202	202
   query16	1026	490	465	465
   query17	1157	742	632	632
   query18	2470	512	363	363
   query19	216	189	153	153
   query20	114	109	109	109
   query21	218	152	138	138
   query22	13622	13564	13311	13311
   query23	17321	16272	16591	16272
   query23_1	16345	16243	16202	16202
   query24	7626	1823	1377	1377
   query24_1	1379	1380	1382	1380
   query25	588	442	385	385
   query26	1309	310	158	158
   query27	2656	582	331	331
   query28	4402	1961	1948	1948
   query29	1111	597	509	509
   query30	305	235	197	197
   query31	1091	1070	942	942
   query32	104	55	54	54
   query33	514	309	246	246
   query34	1154	1151	636	636
   query35	736	753	649	649
   query36	1319	1300	1173	1173
   query37	153	99	84	84
   query38	3187	3094	3036	3036
   query39	924	920	881	881
   query39_1	880	872	888	872
   query40	229	124	111	111
   query41	63	61	58	58
   query42	91	90	93	90
   query43	316	314	270	270
   query44	
   query45	192	183	177	177
   query46	1100	1160	729	729
   query47	2253	2250	2181	2181
   query48	410	410	286	286
   query49	632	482	384	384
   query50	707	286	215	215
   query51	4371	4273	4157	4157
   query52	87	86	74	74
   query53	246	265	183	183
   query54	259	211	201	201
   query55	77	75	74	74
   query56	236	232	213	213
   query57	1397	1372	1303	1303
   query58	241	205	205	205
   query59	1558	1656	1411	1411
   query60	289	254	233	233
   query61	161	159	168	159
   query62	673	620	561	561
   query63	230	192	190	190
   query64	2581	800	636	636
   query65	
   query66	1814	478	361	361
   query67	30043	29275	29889	29275
   query68	
   query69	435	307	286	286
   query70	996	879	929	879
   query71	328	231	212	212
   query72	3111	2852	2544	2544
   query73	826	794	442	442
   query74	5088	4908	4745	4745
   query75	2730	2661	2318	2318
   query76	2302	1145	767	767
   query77	375	398	308	308
   query78	13055	13006	12289	12289
   query79	1428	949	752	752
   query80	1305	510	409	409
   query81	524	276	237	237
   query82	572	163	129	129
   query83	346	282	259	259
   query84	258	135	109	109
   query85	954	493	441	441
   query86	428	288	297	288
   query87	3382	3313	3173	3173
   query88	3566	2689	2651	2651
   query89	428	373	321	321
   query90	1984	187	182	182
   query91	183	166	147	147
   query92	63	61	59	59
   query93	1016	970	577	577
   query94	724	358	311	311
   query95	686	375	344	344
   query96	1066	815	328	328
   query97	2654	2678	2530	2530
   query98	211	214	203	203
   query99	1090	1105	981	981
   Total cold run time: 252299 ms
   Total hot run time: 170082 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3224701222


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/RuntimeFilterTranslator.java:
##########
@@ -228,6 +230,18 @@ private void createLegacyRuntimeFilterFromGroup(List<RuntimeFilter> group,
                     Expr targetExpr = targetExprList.get(i);
                     origFilter.addTarget(new RuntimeFilterTarget(
                             scanNode, targetExpr, true, isLocalTarget));
+                    RuntimeFilterPartitionPruneClassifier.Classification classification =

Review Comment:
   This grouped-RF path still classifies `curTargetExpression` even when the legacy `targetExpr` was just wrapped in the type-adjustment `CastExpr` above. The single-filter path now tracks `adjustedAfterNonIdentity` and skips classification in that case, but grouped filters do not, so a grouped RF target like `date_trunc(part_col)` that needs an outer cast can still be marked partition-prunable based on the pre-cast Nereids expression while BE evaluates partition endpoints through the post-cast legacy expression. That can produce unsafe projected boundaries and false-prune partitions. Please mirror the single-filter guard here, or classify an equivalent expression that includes the inserted cast, and add grouped-RF coverage for this case.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3225527557


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/RuntimeFilterTranslator.java:
##########
@@ -228,6 +230,18 @@ private void createLegacyRuntimeFilterFromGroup(List<RuntimeFilter> group,
                     Expr targetExpr = targetExprList.get(i);
                     origFilter.addTarget(new RuntimeFilterTarget(
                             scanNode, targetExpr, true, isLocalTarget));
+                    RuntimeFilterPartitionPruneClassifier.Classification classification =
+                            RuntimeFilterPartitionPruneClassifier.classify(
+                                    targetExpr, nereidsTargetExprList.get(i), scanNode);
+                    if (classification.canPrunePartitions()) {
+                        origFilter.markTargetCanPrunePartitions(scanNode.getId());
+                    }
+                    if (classification.emitTargetMonotonicity()) {
+                        origFilter.setTargetMonotonicity(scanNode.getId(),
+                                classification.getMonotonicity());
+                    }
+                    origFilter.setTargetPartitionMonotonicity(
+                            scanNode.getId(), classification.getPartitionMonotonicity());

Review Comment:
   This partition-pruning metadata is keyed only by `scanNode.getId()`, but a grouped runtime filter can contain more than one target for the same `OlapScanNode` after RF propagation through equalities on that scan. In that case the later target overwrites the same scan-id entry here, while `TRuntimeFilterDesc.planId_to_target_expr` is also serialized as one expression per plan node. BE therefore cannot tell which target expression the monotonicity map belongs to; if one target is eligible and another same-scan target is not (or has different per-partition monotonicity), the scan can receive stale/incorrect pruning metadata for the expression it actually evaluates and false-prune partitions. Please either prevent grouped filters from contributing more than one partition-pruning target per scan node, or key the pruning metadata by target expression/slot as well as scan id and add a grouped same-scan regression case.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4439827585

   # BE UT Coverage Report
   Increment line coverage `1.15% (8/698)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/2337ac55b6530b92cabafe14881c96951fab95c9_2337ac55b6530b92cabafe14881c96951fab95c9/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/2337ac55b6530b92cabafe14881c96951fab95c9_2337ac55b6530b92cabafe14881c96951fab95c9/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 53.53% (20614/38511) |
   | Line Coverage     | 37.14% (194862/524640) |
   | Region Coverage   | 33.44% (152486/455966) |
   | Branch Coverage   | 34.27% (66501/194063) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4438222914

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4494528283

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 31269 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit ef2529ca3c35ebe1f89cb9fa4212f239fce43a97, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17674	3877	3848	3848
   q2	q3	10764	1448	836	836
   q4	4692	472	342	342
   q5	7636	2270	2078	2078
   q6	241	182	139	139
   q7	951	786	648	648
   q8	9349	1704	1584	1584
   q9	5220	4944	4915	4915
   q10	6378	2129	1783	1783
   q11	455	274	243	243
   q12	640	422	296	296
   q13	18119	3433	2740	2740
   q14	261	253	233	233
   q15	q16	791	777	711	711
   q17	913	955	1003	955
   q18	6853	5686	5512	5512
   q19	1169	1244	1112	1112
   q20	505	408	276	276
   q21	5535	2812	2699	2699
   q22	460	384	319	319
   Total cold run time: 98606 ms
   Total hot run time: 31269 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	5273	4562	4477	4477
   q2	q3	4895	5265	4722	4722
   q4	2153	2262	1422	1422
   q5	4775	4727	4571	4571
   q6	229	190	128	128
   q7	1899	1819	1534	1534
   q8	2399	2062	2018	2018
   q9	7793	7548	7202	7202
   q10	4491	4394	3999	3999
   q11	534	385	351	351
   q12	716	722	513	513
   q13	3070	3338	2832	2832
   q14	270	273	259	259
   q15	q16	683	695	631	631
   q17	1264	1246	1231	1231
   q18	7202	6760	6883	6760
   q19	1090	1093	1096	1093
   q20	2215	2214	1931	1931
   q21	5278	4678	4546	4546
   q22	525	452	441	441
   Total cold run time: 56754 ms
   Total hot run time: 50661 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4496714900

   # FE Regression Coverage Report
   Increment line coverage `  46.98% (218/464)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4560623923

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 32177 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 9af6867af36b4da0dec52406116ba2d158e2e2b9, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17676	4073	4023	4023
   q2	q3	10847	1393	815	815
   q4	4741	484	350	350
   q5	9461	2369	2193	2193
   q6	388	177	141	141
   q7	968	811	653	653
   q8	9866	1781	1583	1583
   q9	7139	5034	5041	5034
   q10	6489	2388	1937	1937
   q11	452	281	246	246
   q12	691	449	301	301
   q13	18263	3448	2841	2841
   q14	271	264	242	242
   q15	q16	841	786	713	713
   q17	1018	1006	976	976
   q18	6904	5734	6192	5734
   q19	1240	1309	1119	1119
   q20	545	428	271	271
   q21	6270	2854	2701	2701
   q22	460	373	304	304
   Total cold run time: 104530 ms
   Total hot run time: 32177 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4812	4810	4793	4793
   q2	q3	4961	5238	4614	4614
   q4	2119	2205	1392	1392
   q5	4895	4769	4660	4660
   q6	230	178	134	134
   q7	1884	1741	1541	1541
   q8	2348	1953	1935	1935
   q9	7416	7429	7435	7429
   q10	4781	4667	4216	4216
   q11	533	381	354	354
   q12	745	757	529	529
   q13	3020	3405	2773	2773
   q14	269	279	259	259
   q15	q16	689	702	620	620
   q17	1279	1290	1271	1271
   q18	7386	6808	6673	6673
   q19	1110	1103	1122	1103
   q20	2230	2252	1932	1932
   q21	5293	4564	4463	4463
   q22	512	480	406	406
   Total cold run time: 56512 ms
   Total hot run time: 51097 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4571773092

   # BE UT Coverage Report
   Increment line coverage `71.27% (563/790)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d_32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d_32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 54.02% (21024/38921) |
   | Line Coverage     | 37.61% (199772/531138) |
   | Region Coverage   | 33.79% (157296/465535) |
   | Branch Coverage   | 34.79% (68849/197896) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4571688715

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 31614 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17666	4176	4235	4176
   q2	q3	11001	1383	832	832
   q4	4745	474	346	346
   q5	9936	2333	2087	2087
   q6	343	183	136	136
   q7	982	809	641	641
   q8	9593	1701	1653	1653
   q9	7072	5035	5007	5007
   q10	6502	2211	1899	1899
   q11	447	277	247	247
   q12	693	431	315	315
   q13	18235	3350	2783	2783
   q14	274	263	244	244
   q15	q16	821	788	709	709
   q17	990	981	1042	981
   q18	6764	5781	5476	5476
   q19	1157	1250	1065	1065
   q20	523	407	280	280
   q21	5815	2707	2430	2430
   q22	445	356	307	307
   Total cold run time: 104004 ms
   Total hot run time: 31614 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4362	4329	4329	4329
   q2	q3	4534	4989	4387	4387
   q4	2088	2212	1386	1386
   q5	4466	4573	5061	4573
   q6	252	196	135	135
   q7	2032	1830	1570	1570
   q8	2480	2203	2108	2108
   q9	8116	7911	8027	7911
   q10	4782	4744	4305	4305
   q11	586	410	380	380
   q12	745	782	548	548
   q13	3330	3671	2951	2951
   q14	300	293	274	274
   q15	q16	708	737	645	645
   q17	1405	1323	1334	1323
   q18	8004	7183	6990	6990
   q19	1156	1108	1132	1108
   q20	2231	2220	1964	1964
   q21	5308	4631	4420	4420
   q22	513	460	438	438
   Total cold run time: 57398 ms
   Total hot run time: 51745 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4589969678

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 31958 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 586640a94f47548a444d49ee53d5474cf04e3ae0, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17884	4089	4041	4041
   q2	q3	10829	1440	824	824
   q4	4756	481	353	353
   q5	9882	2279	2179	2179
   q6	388	185	140	140
   q7	995	783	634	634
   q8	9617	1778	1771	1771
   q9	7136	5013	5016	5013
   q10	6546	2220	1944	1944
   q11	466	276	255	255
   q12	687	429	302	302
   q13	18223	3437	2812	2812
   q14	269	258	233	233
   q15	q16	821	772	705	705
   q17	933	956	942	942
   q18	6849	5696	5682	5682
   q19	1178	1379	1058	1058
   q20	519	407	264	264
   q21	5735	2529	2504	2504
   q22	427	360	302	302
   Total cold run time: 104140 ms
   Total hot run time: 31958 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4366	4264	4285	4264
   q2	q3	4555	4940	4338	4338
   q4	2108	2226	1378	1378
   q5	4469	4570	5344	4570
   q6	256	197	147	147
   q7	1999	1813	1612	1612
   q8	2548	2205	2375	2205
   q9	7936	8268	7987	7987
   q10	4842	4760	4322	4322
   q11	574	428	380	380
   q12	752	763	566	566
   q13	3216	3532	2971	2971
   q14	297	315	358	315
   q15	q16	736	758	676	676
   q17	1359	1361	1393	1361
   q18	7977	7494	7024	7024
   q19	1142	1096	1109	1096
   q20	2242	2239	1956	1956
   q21	5321	4631	4448	4448
   q22	532	480	422	422
   Total cold run time: 57227 ms
   Total hot run time: 52038 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4594105894

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 169272 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit b82194ce0fb5ac216aeed034490645f04c19f4e5, data reload: false
   
   query5	4318	648	485	485
   query6	450	191	182	182
   query7	4872	544	311	311
   query8	384	220	199	199
   query9	8795	4080	4044	4044
   query10	445	313	253	253
   query11	5867	2340	2144	2144
   query12	152	103	103	103
   query13	1292	613	444	444
   query14	6411	5388	5113	5113
   query14_1	4408	4432	4385	4385
   query15	210	202	181	181
   query16	1029	471	441	441
   query17	1133	712	597	597
   query18	2745	485	357	357
   query19	210	190	151	151
   query20	111	110	108	108
   query21	222	150	133	133
   query22	13637	13569	13341	13341
   query23	17411	16536	16185	16185
   query23_1	16398	16313	16264	16264
   query24	7571	1781	1290	1290
   query24_1	1298	1308	1317	1308
   query25	554	437	393	393
   query26	1325	316	174	174
   query27	2577	548	347	347
   query28	4429	2001	2027	2001
   query29	1070	618	488	488
   query30	306	234	203	203
   query31	1132	1072	944	944
   query32	109	60	58	58
   query33	530	320	243	243
   query34	1163	1172	646	646
   query35	748	772	686	686
   query36	1399	1363	1277	1277
   query37	157	101	95	95
   query38	3204	3123	3046	3046
   query39	938	921	891	891
   query39_1	889	876	878	876
   query40	219	118	99	99
   query41	64	66	61	61
   query42	93	92	94	92
   query43	317	321	279	279
   query44	
   query45	216	195	181	181
   query46	1099	1216	734	734
   query47	2400	2380	2279	2279
   query48	412	404	297	297
   query49	626	470	366	366
   query50	1032	354	258	258
   query51	4301	4287	4235	4235
   query52	89	88	77	77
   query53	251	268	195	195
   query54	296	219	193	193
   query55	78	73	69	69
   query56	232	221	217	217
   query57	1455	1407	1332	1332
   query58	248	217	211	211
   query59	1571	1653	1400	1400
   query60	275	247	226	226
   query61	161	163	159	159
   query62	696	663	575	575
   query63	226	182	191	182
   query64	2509	805	604	604
   query65	
   query66	1742	456	342	342
   query67	29765	29602	29566	29566
   query68	
   query69	425	302	260	260
   query70	965	948	927	927
   query71	297	221	201	201
   query72	3054	2704	2533	2533
   query73	857	755	444	444
   query74	5174	4953	4768	4768
   query75	2647	2581	2240	2240
   query76	2301	1171	790	790
   query77	342	377	281	281
   query78	12299	12293	11878	11878
   query79	1264	1073	737	737
   query80	554	471	399	399
   query81	444	277	241	241
   query82	235	157	116	116
   query83	269	275	246	246
   query84	264	140	112	112
   query85	881	539	449	449
   query86	345	304	297	297
   query87	3356	3340	3173	3173
   query88	3626	2768	2747	2747
   query89	410	382	326	326
   query90	2178	185	172	172
   query91	178	168	139	139
   query92	65	62	58	58
   query93	1381	1433	879	879
   query94	545	372	323	323
   query95	709	375	440	375
   query96	1059	797	347	347
   query97	2673	2712	2563	2563
   query98	214	209	204	204
   query99	1166	1186	1038	1038
   Total cold run time: 250943 ms
   Total hot run time: 169272 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "HappenLee (via GitHub)" <gi...@apache.org>.
HappenLee commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3330589016


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,866 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            bool range_has_null = false;                                                       \
+            if (is_nullable && !tb.__isset.range_start) {                                      \
+                range_has_null = true;                                                         \
+            }                                                                                  \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                } else if (is_nullable) {                                                      \
+                    range_has_null = true;                                                     \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+            if (range_has_null) {                                                              \
+                boundary.contains_null = true;                                                 \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        DORIS_CHECK(parsed_ok);
+        _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static bool find_unique_slot_ref_impl(const VExpr* expr, const VSlotRef** found) {
+    if (!expr) {
+        return true;
+    }
+    if (expr->is_slot_ref()) {
+        const auto* slot = assert_cast<const VSlotRef*>(expr);
+        if (*found == nullptr) {
+            *found = slot;
+            return true;
+        }
+        if ((*found)->slot_id() != slot->slot_id()) {
+            return false;
+        }
+        DORIS_CHECK((*found)->column_id() == slot->column_id());
+        return true;
+    }
+    for (const auto& child : expr->children()) {
+        if (!find_unique_slot_ref_impl(child.get(), found)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    const VSlotRef* found = nullptr;
+    if (!find_unique_slot_ref_impl(expr, &found)) {
+        return nullptr;
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        DORIS_CHECK(direction != TTargetExprMonotonicity::NON_MONOTONIC);
+        DORIS_CHECK(boundary_partition_ids.contains(partition_id));
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        DORIS_CHECK(slot_ref->slot_id() == leaf_slot_id);
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            DORIS_CHECK(direction_it->second == TTargetExprMonotonicity::MONOTONIC_INCREASING);
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    DORIS_CHECK(slot_type_it != _slot_data_types.end());
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<bool> boundary_is_list(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    std::vector<size_t> list_result_begin(N, 0);
+    std::vector<size_t> list_result_end(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+    size_t list_row_count = 0;
+
+    // Build input blocks for RANGE endpoints and LIST values.
+    Block lo_block;
+    Block hi_block;
+    Block list_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns and LIST value columns. Unbounded RANGE endpoints are not executed
+    // through target_expr; they stay unbounded after projection and swap sides
+    // for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr list_inner_base = inner_type->create_column();                          \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        auto* list_inner = assert_cast<InCol*>(list_inner_base.get());                           \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        list_inner->reserve(N);                                                                  \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        auto list_nulls = ColumnUInt8::create();                                                 \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        list_nulls->reserve(N);                                                                  \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            boundary_is_list[i] = cvr->is_fixed_value_range();                                   \
+            if (boundary_is_list[i]) {                                                           \
+                list_result_begin[i] = list_row_count;                                           \
+                for (const auto& value : cvr->get_fixed_value_set()) {                           \
+                    list_inner->insert_value(value);                                             \
+                    list_nulls->get_data().push_back(0);                                         \
+                    ++list_row_count;                                                            \
+                }                                                                                \
+                list_result_end[i] = list_row_count;                                             \
+                continue;                                                                        \
+            }                                                                                    \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        add_dummy_columns(list_block, list_row_count);                                           \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            auto list_col =                                                                      \
+                    ColumnNullable::create(std::move(list_inner_base), std::move(list_nulls));   \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+            list_block.insert({std::move(list_col), input_type, "leaf_slot"});                   \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+            list_block.insert({std::move(list_inner_base), input_type, "leaf_slot"});            \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    DORIS_CHECK(input_built);
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    int list_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    ColumnPtr list_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        DORIS_CHECK(lo_result_id >= 0);
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        DORIS_CHECK(hi_result_id >= 0);
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    if (list_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &list_block, &list_result_id));
+        DORIS_CHECK(list_result_id >= 0);
+        list_result_col = list_block.get_by_position(list_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if (boundary_is_list[i]) {                                                          \
+                auto cvr = ColumnValueRange<TYPE_##OUTPUT_PT>::create_empty_column_value_range( \
+                        out_nullable, out_precision, out_scale);                                \
+                bool list_has_value = false;                                                    \
+                bool list_has_null = false;                                                     \
+                for (size_t row = list_result_begin[i]; row < list_result_end[i]; ++row) {      \
+                    if (list_result_col->is_null_at(row)) {                                     \
+                        list_has_null = true;                                                   \
+                        continue;                                                               \
+                    }                                                                           \
+                    auto projected_list_value = projected_value(list_result_col, row);          \
+                    static_cast<void>(cvr.add_fixed_value(projected_list_value));               \
+                    list_has_value = true;                                                      \
+                }                                                                               \
+                if (!list_has_value && !list_has_null) {                                        \
+                    continue;                                                                   \
+                }                                                                               \
+                ParsedBoundary projected_boundary;                                              \
+                projected_boundary.partition_id = orig_boundary.partition_id;                   \
+                projected_boundary.slot_id = leaf_slot_id;                                      \
+                projected_boundary.is_nullable = out_nullable;                                  \
+                projected_boundary.contains_null = list_has_null;                               \
+                projected_boundary.only_null = list_has_null && !list_has_value;                \
+                projected_boundary.boundary_cvr = std::move(cvr);                               \
+                projected.emplace_back(std::move(projected_boundary));                          \
+                continue;                                                                       \
+            }                                                                                   \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            /* Use closed projected finite bounds as a conservative envelope for */             \
+            /* non-strict monotonic expressions such as date ceil/floor/cast.    */             \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));       \
+                }                                                                               \
+            } else {                                                                            \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));     \
+                }                                                                               \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));       \
+                }                                                                               \
+            }                                                                                   \
+            ParsedBoundary projected_boundary;                                                  \
+            projected_boundary.partition_id = orig_boundary.partition_id;                       \
+            projected_boundary.slot_id = leaf_slot_id;                                          \
+            projected_boundary.is_nullable = out_nullable;                                      \
+            projected_boundary.only_null = orig_boundary.only_null;                             \
+            projected_boundary.contains_null = orig_boundary.contains_null;                     \
+            projected_boundary.boundary_cvr = std::move(cvr);                                   \
+            projected.emplace_back(std::move(projected_boundary));                              \
+        }                                                                                       \
+        output_built = true;                                                                    \
+        break;                                                                                  \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    DORIS_CHECK(output_built);
+
+    return store_projected_boundaries(std::move(projected));
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    std::optional<ColumnValueRangeType> rf_cvr;
+    if (!boundaries.empty()) {
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {
+                        auto typed_rf_cvr = CvrType::create_empty_column_value_range(
+                                boundaries.front().is_nullable, boundary_cvr.precision(),
+                                boundary_cvr.scale());
+                        auto* iter = hybrid_set->begin();
+                        while (iter->has_next()) {
+                            const void* value = iter->get_value();
+                            if (value) {
+                                if constexpr (std::is_same_v<CppType, String>) {
+                                    // String HybridSets store StringRef*, but
+                                    // ColumnValueRange<String>::CppType is
+                                    // std::string. Construct from the bytes.
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else {
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            *reinterpret_cast<const CppType*>(value)));
+                                }
+                            }
+                            iter->next();
+                        }
+                        rf_cvr.emplace(std::move(typed_rf_cvr));
+                    } else if ((impl->node_type() == TExprNodeType::BINARY_PRED ||
+                                impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) &&
+                               impl->children().size() == 2 && impl->children()[1]->is_literal()) {
+                        // MinMax filter: binary pred with literal bound
+                        auto* literal = assert_cast<VLiteral*>(impl->children()[1].get());
+                        auto col_ptr = literal->get_column_ptr();
+                        auto data = col_ptr->get_data_at(0);
+                        CppType val {};
+                        if constexpr (std::is_same_v<CppType, String>) {
+                            // get_data_at returns a StringRef pointing at the
+                            // raw character bytes; ColumnValueRange<String>'s
+                            // CppType is std::string, so construct one from
+                            // the bytes rather than dereferencing as String.
+                            val = CppType(data.data, data.size);
+                        } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                            val = CppType(data.data, data.size);
+                        } else {
+                            val = *reinterpret_cast<const CppType*>(data.data);
+                        }
+
+                        SQLFilterOp op = convert_opcode_to_filter_op(impl->op());
+                        if (op == FILTER_IN) {
+                            return; // unrecognized opcode, skip
+                        }
+
+                        CvrType typed_rf_cvr(boundary_cvr.column_name(),
+                                             boundaries.front().is_nullable,
+                                             boundary_cvr.precision(), boundary_cvr.scale());
+                        static_cast<void>(typed_rf_cvr.add_range(op, val));
+                        rf_cvr.emplace(std::move(typed_rf_cvr));
+                    }
+                },
+                boundaries.front().boundary_cvr);
+    }
+    if (!rf_cvr.has_value()) {
+        return;
+    }
+
+    for (const auto& pb : boundaries) {
+        if (_pruned_partition_ids.contains(pb.partition_id) ||
+            newly_pruned.contains(pb.partition_id)) {
+            continue;
+        }
+
+        // NULL handling:
+        //   A partition row whose key is NULL matches the RF iff `rf_contains_null`.
+        //   - only_null partition (rows are exclusively NULL): prunable iff !rf_contains_null.
+        //   - mixed (NULL + concrete values): if rf_contains_null, NULL rows alone
+        //     prevent pruning. Otherwise NULL rows can never match, so we ignore
+        //     the NULL portion and let the regular non-NULL intersection decide.
+        if (pb.only_null) {
+            if (!rf_contains_null) {
+                newly_pruned.insert(pb.partition_id);
+            }
+            continue;
+        }
+        if (pb.contains_null && rf_contains_null) {
+            continue;
+        }
+
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+
+                    const auto* typed_rf_cvr = std::get_if<CvrType>(&rf_cvr.value());
+                    DORIS_CHECK(typed_rf_cvr != nullptr);
+
+                    auto boundary_copy = boundary_cvr;
+                    auto rf_cvr_copy = *typed_rf_cvr;
+                    boundary_copy.intersection(rf_cvr_copy);
+                    if (boundary_copy.is_empty_value_range()) {
+                        newly_pruned.insert(pb.partition_id);
+                    }
+                },
+                pb.boundary_cvr);
+    }
+}
+
+Status RuntimeFilterPartitionPruner::prune_by_runtime_filters(
+        const ParsedPartitionBoundaries& parsed, const VExprContextSPtrs& conjuncts,
+        const std::vector<TRuntimeFilterDesc>& rf_descs, int scan_node_id,
+        int64_t* newly_pruned_count) {
+    *newly_pruned_count = 0;
+    if (parsed.empty()) {
+        return Status::OK();
+    }
+    const auto& slot_to_boundaries = parsed.slot_to_boundaries();
+
+    // Build filter_id -> per-partition monotonicity maps.
+    std::unordered_map<int, std::unordered_map<int64_t, TTargetExprMonotonicity::type>>
+            filter_id_to_partition_monotonicity;
+    for (const auto& desc : rf_descs) {
+        if (desc.__isset.planId_to_partition_target_monotonicity) {
+            auto it = desc.planId_to_partition_target_monotonicity.find(scan_node_id);
+            if (it != desc.planId_to_partition_target_monotonicity.end()) {
+                auto& partition_monotonicity = filter_id_to_partition_monotonicity[desc.filter_id];
+                for (const auto& partition_entry : it->second) {
+                    DORIS_CHECK(partition_entry.__isset.partition_id);
+                    DORIS_CHECK(partition_entry.__isset.monotonicity);
+                    DORIS_CHECK(partition_entry.monotonicity !=
+                                TTargetExprMonotonicity::NON_MONOTONIC);
+                    auto [_, inserted] = partition_monotonicity.emplace(
+                            partition_entry.partition_id, partition_entry.monotonicity);
+                    DORIS_CHECK(inserted);
+                }
+            }
+        }
+    }
+
+    // This function is serialized by _conjuncts_lock in the caller, so our reads
+    // of _pruned_partition_ids never race with our writes below. The only concurrent
+    // readers are is_partition_pruned() calls (under shared_lock), which are
+    // properly synchronized by the unique_lock we take when inserting.
+    phmap::flat_hash_set<int64_t> newly_pruned;
+
+    for (const auto& conjunct_ctx : conjuncts) {
+        VExprSPtr root = conjunct_ctx->root();
+        if (!root->is_rf_wrapper()) {
+            continue;
+        }
+
+        VExprSPtr impl = root->get_impl();
+        if (!impl) {
+            continue;
+        }
+
+        if (impl->children().empty()) {
+            continue;
+        }
+
+        auto* wrapper_root = assert_cast<RuntimeFilterExpr*>(root.get());
+        int filter_id = wrapper_root->filter_id();
+
+        VExprSPtr target_subtree = impl->children()[0];
+
+        auto partition_mono_it = filter_id_to_partition_monotonicity.find(filter_id);
+        bool has_partition_mono = partition_mono_it != filter_id_to_partition_monotonicity.end() &&
+                                  !partition_mono_it->second.empty();
+        if (!has_partition_mono) {

Review Comment:
   这里需要讨论一下,如果是list分区,这个单调的判断是不是没什么意义。list分区本身就是全算一遍的,不过这个可以后续来改进



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "HappenLee (via GitHub)" <gi...@apache.org>.
HappenLee commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3330549387


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,866 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            bool range_has_null = false;                                                       \
+            if (is_nullable && !tb.__isset.range_start) {                                      \
+                range_has_null = true;                                                         \
+            }                                                                                  \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                } else if (is_nullable) {                                                      \
+                    range_has_null = true;                                                     \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+            if (range_has_null) {                                                              \
+                boundary.contains_null = true;                                                 \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        DORIS_CHECK(parsed_ok);
+        _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static bool find_unique_slot_ref_impl(const VExpr* expr, const VSlotRef** found) {
+    if (!expr) {
+        return true;
+    }
+    if (expr->is_slot_ref()) {
+        const auto* slot = assert_cast<const VSlotRef*>(expr);
+        if (*found == nullptr) {
+            *found = slot;
+            return true;
+        }
+        if ((*found)->slot_id() != slot->slot_id()) {
+            return false;
+        }
+        DORIS_CHECK((*found)->column_id() == slot->column_id());
+        return true;
+    }
+    for (const auto& child : expr->children()) {
+        if (!find_unique_slot_ref_impl(child.get(), found)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    const VSlotRef* found = nullptr;
+    if (!find_unique_slot_ref_impl(expr, &found)) {
+        return nullptr;
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        DORIS_CHECK(direction != TTargetExprMonotonicity::NON_MONOTONIC);
+        DORIS_CHECK(boundary_partition_ids.contains(partition_id));
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        DORIS_CHECK(slot_ref->slot_id() == leaf_slot_id);
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            DORIS_CHECK(direction_it->second == TTargetExprMonotonicity::MONOTONIC_INCREASING);
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    DORIS_CHECK(slot_type_it != _slot_data_types.end());
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<bool> boundary_is_list(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    std::vector<size_t> list_result_begin(N, 0);
+    std::vector<size_t> list_result_end(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+    size_t list_row_count = 0;
+
+    // Build input blocks for RANGE endpoints and LIST values.
+    Block lo_block;
+    Block hi_block;
+    Block list_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns and LIST value columns. Unbounded RANGE endpoints are not executed
+    // through target_expr; they stay unbounded after projection and swap sides
+    // for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr list_inner_base = inner_type->create_column();                          \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        auto* list_inner = assert_cast<InCol*>(list_inner_base.get());                           \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        list_inner->reserve(N);                                                                  \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        auto list_nulls = ColumnUInt8::create();                                                 \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        list_nulls->reserve(N);                                                                  \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            boundary_is_list[i] = cvr->is_fixed_value_range();                                   \
+            if (boundary_is_list[i]) {                                                           \
+                list_result_begin[i] = list_row_count;                                           \
+                for (const auto& value : cvr->get_fixed_value_set()) {                           \
+                    list_inner->insert_value(value);                                             \
+                    list_nulls->get_data().push_back(0);                                         \
+                    ++list_row_count;                                                            \
+                }                                                                                \
+                list_result_end[i] = list_row_count;                                             \
+                continue;                                                                        \
+            }                                                                                    \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        add_dummy_columns(list_block, list_row_count);                                           \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            auto list_col =                                                                      \
+                    ColumnNullable::create(std::move(list_inner_base), std::move(list_nulls));   \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+            list_block.insert({std::move(list_col), input_type, "leaf_slot"});                   \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+            list_block.insert({std::move(list_inner_base), input_type, "leaf_slot"});            \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    DORIS_CHECK(input_built);
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    int list_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    ColumnPtr list_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        DORIS_CHECK(lo_result_id >= 0);
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        DORIS_CHECK(hi_result_id >= 0);
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    if (list_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &list_block, &list_result_id));
+        DORIS_CHECK(list_result_id >= 0);
+        list_result_col = list_block.get_by_position(list_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if (boundary_is_list[i]) {                                                          \
+                auto cvr = ColumnValueRange<TYPE_##OUTPUT_PT>::create_empty_column_value_range( \
+                        out_nullable, out_precision, out_scale);                                \
+                bool list_has_value = false;                                                    \
+                bool list_has_null = false;                                                     \
+                for (size_t row = list_result_begin[i]; row < list_result_end[i]; ++row) {      \
+                    if (list_result_col->is_null_at(row)) {                                     \
+                        list_has_null = true;                                                   \
+                        continue;                                                               \
+                    }                                                                           \
+                    auto projected_list_value = projected_value(list_result_col, row);          \
+                    static_cast<void>(cvr.add_fixed_value(projected_list_value));               \
+                    list_has_value = true;                                                      \
+                }                                                                               \
+                if (!list_has_value && !list_has_null) {                                        \
+                    continue;                                                                   \
+                }                                                                               \
+                ParsedBoundary projected_boundary;                                              \
+                projected_boundary.partition_id = orig_boundary.partition_id;                   \
+                projected_boundary.slot_id = leaf_slot_id;                                      \
+                projected_boundary.is_nullable = out_nullable;                                  \
+                projected_boundary.contains_null = list_has_null;                               \
+                projected_boundary.only_null = list_has_null && !list_has_value;                \
+                projected_boundary.boundary_cvr = std::move(cvr);                               \
+                projected.emplace_back(std::move(projected_boundary));                          \
+                continue;                                                                       \
+            }                                                                                   \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            /* Use closed projected finite bounds as a conservative envelope for */             \
+            /* non-strict monotonic expressions such as date ceil/floor/cast.    */             \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));       \
+                }                                                                               \
+            } else {                                                                            \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));     \
+                }                                                                               \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));       \
+                }                                                                               \
+            }                                                                                   \
+            ParsedBoundary projected_boundary;                                                  \
+            projected_boundary.partition_id = orig_boundary.partition_id;                       \
+            projected_boundary.slot_id = leaf_slot_id;                                          \
+            projected_boundary.is_nullable = out_nullable;                                      \
+            projected_boundary.only_null = orig_boundary.only_null;                             \
+            projected_boundary.contains_null = orig_boundary.contains_null;                     \
+            projected_boundary.boundary_cvr = std::move(cvr);                                   \
+            projected.emplace_back(std::move(projected_boundary));                              \
+        }                                                                                       \
+        output_built = true;                                                                    \
+        break;                                                                                  \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    DORIS_CHECK(output_built);
+
+    return store_projected_boundaries(std::move(projected));
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    std::optional<ColumnValueRangeType> rf_cvr;
+    if (!boundaries.empty()) {
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {
+                        auto typed_rf_cvr = CvrType::create_empty_column_value_range(
+                                boundaries.front().is_nullable, boundary_cvr.precision(),
+                                boundary_cvr.scale());
+                        auto* iter = hybrid_set->begin();
+                        while (iter->has_next()) {
+                            const void* value = iter->get_value();
+                            if (value) {
+                                if constexpr (std::is_same_v<CppType, String>) {
+                                    // String HybridSets store StringRef*, but
+                                    // ColumnValueRange<String>::CppType is
+                                    // std::string. Construct from the bytes.
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);

Review Comment:
   这个分支是什么情况会走到呢?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4594017466

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 29888 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit b82194ce0fb5ac216aeed034490645f04c19f4e5, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17653	4033	3997	3997
   q2	q3	10875	1441	812	812
   q4	4752	486	345	345
   q5	8400	922	587	587
   q6	337	182	138	138
   q7	902	865	652	652
   q8	11024	1637	1635	1635
   q9	7156	4583	4515	4515
   q10	6865	1907	1529	1529
   q11	456	281	258	258
   q12	656	433	292	292
   q13	18114	3830	2821	2821
   q14	265	261	249	249
   q15	q16	814	777	703	703
   q17	950	946	1016	946
   q18	6770	5742	5771	5742
   q19	1178	1367	1268	1268
   q20	560	474	287	287
   q21	5852	2861	2799	2799
   q22	520	368	313	313
   Total cold run time: 104099 ms
   Total hot run time: 29888 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4840	4799	4744	4744
   q2	q3	5015	5294	4634	4634
   q4	2117	2198	1390	1390
   q5	5071	4789	4587	4587
   q6	227	176	130	130
   q7	2016	1798	1546	1546
   q8	2452	2135	2141	2135
   q9	7864	7347	7406	7347
   q10	4722	4684	4222	4222
   q11	538	397	356	356
   q12	733	744	540	540
   q13	3042	3409	2857	2857
   q14	278	279	266	266
   q15	q16	681	698	600	600
   q17	1328	1290	1276	1276
   q18	7490	6880	6928	6880
   q19	1106	1104	1092	1092
   q20	2237	2212	2007	2007
   q21	5366	4646	4504	4504
   q22	528	487	438	438
   Total cold run time: 57651 ms
   Total hot run time: 51551 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4450386075

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 29592 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit c090ab6440346db9f2c052585ec7b6dc593d0df3, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17623	3889	3851	3851
   q2	q3	10714	887	628	628
   q4	4706	469	351	351
   q5	7820	1351	1136	1136
   q6	310	171	142	142
   q7	962	948	765	765
   q8	10795	1443	1342	1342
   q9	6958	5410	5536	5410
   q10	6334	2089	1805	1805
   q11	503	269	254	254
   q12	699	418	306	306
   q13	18199	3417	2774	2774
   q14	303	292	262	262
   q15	q16	898	880	790	790
   q17	1281	1076	678	678
   q18	6481	5673	5566	5566
   q19	1507	1234	1081	1081
   q20	546	404	269	269
   q21	4804	2277	1870	1870
   q22	426	357	312	312
   Total cold run time: 101869 ms
   Total hot run time: 29592 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4246	4119	4118	4118
   q2	q3	4650	4795	4177	4177
   q4	2113	2185	1390	1390
   q5	4990	4967	5329	4967
   q6	329	202	161	161
   q7	2138	1859	1718	1718
   q8	3551	3264	3181	3181
   q9	8570	8616	8561	8561
   q10	4500	4502	4281	4281
   q11	647	426	418	418
   q12	734	748	527	527
   q13	3454	3605	2983	2983
   q14	328	313	287	287
   q15	q16	790	790	707	707
   q17	1373	1335	1304	1304
   q18	8036	7126	7210	7126
   q19	1177	1196	1191	1191
   q20	2244	2280	1982	1982
   q21	6214	5449	5205	5205
   q22	570	548	437	437
   Total cold run time: 60654 ms
   Total hot run time: 54721 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4541734955

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "HappenLee (via GitHub)" <gi...@apache.org>.
HappenLee commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3330534056


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,866 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            bool range_has_null = false;                                                       \
+            if (is_nullable && !tb.__isset.range_start) {                                      \
+                range_has_null = true;                                                         \
+            }                                                                                  \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                } else if (is_nullable) {                                                      \
+                    range_has_null = true;                                                     \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+            if (range_has_null) {                                                              \
+                boundary.contains_null = true;                                                 \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        DORIS_CHECK(parsed_ok);
+        _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static bool find_unique_slot_ref_impl(const VExpr* expr, const VSlotRef** found) {
+    if (!expr) {

Review Comment:
   什么情况下这个expr会是空指针呢



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "HappenLee (via GitHub)" <gi...@apache.org>.
HappenLee commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3330551591


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,866 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            bool range_has_null = false;                                                       \
+            if (is_nullable && !tb.__isset.range_start) {                                      \
+                range_has_null = true;                                                         \
+            }                                                                                  \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                } else if (is_nullable) {                                                      \
+                    range_has_null = true;                                                     \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+            if (range_has_null) {                                                              \
+                boundary.contains_null = true;                                                 \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        DORIS_CHECK(parsed_ok);
+        _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static bool find_unique_slot_ref_impl(const VExpr* expr, const VSlotRef** found) {
+    if (!expr) {
+        return true;
+    }
+    if (expr->is_slot_ref()) {
+        const auto* slot = assert_cast<const VSlotRef*>(expr);
+        if (*found == nullptr) {
+            *found = slot;
+            return true;
+        }
+        if ((*found)->slot_id() != slot->slot_id()) {
+            return false;
+        }
+        DORIS_CHECK((*found)->column_id() == slot->column_id());
+        return true;
+    }
+    for (const auto& child : expr->children()) {
+        if (!find_unique_slot_ref_impl(child.get(), found)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    const VSlotRef* found = nullptr;
+    if (!find_unique_slot_ref_impl(expr, &found)) {
+        return nullptr;
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        DORIS_CHECK(direction != TTargetExprMonotonicity::NON_MONOTONIC);
+        DORIS_CHECK(boundary_partition_ids.contains(partition_id));
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        DORIS_CHECK(slot_ref->slot_id() == leaf_slot_id);
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            DORIS_CHECK(direction_it->second == TTargetExprMonotonicity::MONOTONIC_INCREASING);
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    DORIS_CHECK(slot_type_it != _slot_data_types.end());
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<bool> boundary_is_list(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    std::vector<size_t> list_result_begin(N, 0);
+    std::vector<size_t> list_result_end(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+    size_t list_row_count = 0;
+
+    // Build input blocks for RANGE endpoints and LIST values.
+    Block lo_block;
+    Block hi_block;
+    Block list_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns and LIST value columns. Unbounded RANGE endpoints are not executed
+    // through target_expr; they stay unbounded after projection and swap sides
+    // for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr list_inner_base = inner_type->create_column();                          \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        auto* list_inner = assert_cast<InCol*>(list_inner_base.get());                           \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        list_inner->reserve(N);                                                                  \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        auto list_nulls = ColumnUInt8::create();                                                 \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        list_nulls->reserve(N);                                                                  \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            boundary_is_list[i] = cvr->is_fixed_value_range();                                   \
+            if (boundary_is_list[i]) {                                                           \
+                list_result_begin[i] = list_row_count;                                           \
+                for (const auto& value : cvr->get_fixed_value_set()) {                           \
+                    list_inner->insert_value(value);                                             \
+                    list_nulls->get_data().push_back(0);                                         \
+                    ++list_row_count;                                                            \
+                }                                                                                \
+                list_result_end[i] = list_row_count;                                             \
+                continue;                                                                        \
+            }                                                                                    \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        add_dummy_columns(list_block, list_row_count);                                           \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            auto list_col =                                                                      \
+                    ColumnNullable::create(std::move(list_inner_base), std::move(list_nulls));   \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+            list_block.insert({std::move(list_col), input_type, "leaf_slot"});                   \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+            list_block.insert({std::move(list_inner_base), input_type, "leaf_slot"});            \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    DORIS_CHECK(input_built);
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    int list_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    ColumnPtr list_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        DORIS_CHECK(lo_result_id >= 0);
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        DORIS_CHECK(hi_result_id >= 0);
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    if (list_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &list_block, &list_result_id));
+        DORIS_CHECK(list_result_id >= 0);
+        list_result_col = list_block.get_by_position(list_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if (boundary_is_list[i]) {                                                          \
+                auto cvr = ColumnValueRange<TYPE_##OUTPUT_PT>::create_empty_column_value_range( \
+                        out_nullable, out_precision, out_scale);                                \
+                bool list_has_value = false;                                                    \
+                bool list_has_null = false;                                                     \
+                for (size_t row = list_result_begin[i]; row < list_result_end[i]; ++row) {      \
+                    if (list_result_col->is_null_at(row)) {                                     \
+                        list_has_null = true;                                                   \
+                        continue;                                                               \
+                    }                                                                           \
+                    auto projected_list_value = projected_value(list_result_col, row);          \
+                    static_cast<void>(cvr.add_fixed_value(projected_list_value));               \
+                    list_has_value = true;                                                      \
+                }                                                                               \
+                if (!list_has_value && !list_has_null) {                                        \
+                    continue;                                                                   \
+                }                                                                               \
+                ParsedBoundary projected_boundary;                                              \
+                projected_boundary.partition_id = orig_boundary.partition_id;                   \
+                projected_boundary.slot_id = leaf_slot_id;                                      \
+                projected_boundary.is_nullable = out_nullable;                                  \
+                projected_boundary.contains_null = list_has_null;                               \
+                projected_boundary.only_null = list_has_null && !list_has_value;                \
+                projected_boundary.boundary_cvr = std::move(cvr);                               \
+                projected.emplace_back(std::move(projected_boundary));                          \
+                continue;                                                                       \
+            }                                                                                   \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            /* Use closed projected finite bounds as a conservative envelope for */             \
+            /* non-strict monotonic expressions such as date ceil/floor/cast.    */             \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));       \
+                }                                                                               \
+            } else {                                                                            \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));     \
+                }                                                                               \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));       \
+                }                                                                               \
+            }                                                                                   \
+            ParsedBoundary projected_boundary;                                                  \
+            projected_boundary.partition_id = orig_boundary.partition_id;                       \
+            projected_boundary.slot_id = leaf_slot_id;                                          \
+            projected_boundary.is_nullable = out_nullable;                                      \
+            projected_boundary.only_null = orig_boundary.only_null;                             \
+            projected_boundary.contains_null = orig_boundary.contains_null;                     \
+            projected_boundary.boundary_cvr = std::move(cvr);                                   \
+            projected.emplace_back(std::move(projected_boundary));                              \
+        }                                                                                       \
+        output_built = true;                                                                    \
+        break;                                                                                  \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    DORIS_CHECK(output_built);
+
+    return store_projected_boundaries(std::move(projected));
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    std::optional<ColumnValueRangeType> rf_cvr;
+    if (!boundaries.empty()) {
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {
+                        auto typed_rf_cvr = CvrType::create_empty_column_value_range(
+                                boundaries.front().is_nullable, boundary_cvr.precision(),
+                                boundary_cvr.scale());
+                        auto* iter = hybrid_set->begin();
+                        while (iter->has_next()) {
+                            const void* value = iter->get_value();
+                            if (value) {
+                                if constexpr (std::is_same_v<CppType, String>) {
+                                    // String HybridSets store StringRef*, but
+                                    // ColumnValueRange<String>::CppType is
+                                    // std::string. Construct from the bytes.
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else {
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            *reinterpret_cast<const CppType*>(value)));
+                                }
+                            }
+                            iter->next();
+                        }
+                        rf_cvr.emplace(std::move(typed_rf_cvr));
+                    } else if ((impl->node_type() == TExprNodeType::BINARY_PRED ||
+                                impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) &&
+                               impl->children().size() == 2 && impl->children()[1]->is_literal()) {
+                        // MinMax filter: binary pred with literal bound
+                        auto* literal = assert_cast<VLiteral*>(impl->children()[1].get());
+                        auto col_ptr = literal->get_column_ptr();
+                        auto data = col_ptr->get_data_at(0);
+                        CppType val {};
+                        if constexpr (std::is_same_v<CppType, String>) {
+                            // get_data_at returns a StringRef pointing at the
+                            // raw character bytes; ColumnValueRange<String>'s
+                            // CppType is std::string, so construct one from
+                            // the bytes rather than dereferencing as String.
+                            val = CppType(data.data, data.size);
+                        } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                            val = CppType(data.data, data.size);
+                        } else {
+                            val = *reinterpret_cast<const CppType*>(data.data);
+                        }
+
+                        SQLFilterOp op = convert_opcode_to_filter_op(impl->op());

Review Comment:
   这个不是TExprNodeType::BINARY_PRED,为什么会变成in了呢



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4593476114

   # FE Regression Coverage Report
   Increment line coverage `  48.07% (224/466)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_b82194ce0fb5ac216aeed034490645f04c19f4e5_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_b82194ce0fb5ac216aeed034490645f04c19f4e5_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "morrySnow (via GitHub)" <gi...@apache.org>.
morrySnow commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3297174711


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/RuntimeFilterTranslator.java:
##########
@@ -195,12 +200,24 @@ private void createLegacyRuntimeFilterFromGroup(List<RuntimeFilter> group,
                             new RuntimeFilterExpressionTranslator(targetSlotRef);
                     targetExpr = curTargetExpression.accept(translator, ctx);
                 }
+                boolean adjustedAfterNonIdentity = false;
+                if (!src.getType().equals(targetExpr.getType()) && head.getType() != TRuntimeFilterType.BITMAP) {
+                    adjustedAfterNonIdentity = !isIdentityTarget;
+                }
                 targetExpr = castTargetToSourceTypeIfNeeded(src, targetExpr, head.getType());
                 TupleId targetTupleId = targetSlotRef.getDesc().getParentId();
                 SlotId targetSlotId = targetSlotRef.getSlotId();
                 scanNodeList.add(scanNode);
                 targetExprList.add(targetExpr);
+                nereidsTargetExprList.add(curTargetExpression);
                 targetTupleIdMapList.add(ImmutableMap.of(targetTupleId, ImmutableList.of(targetSlotId)));
+                targetAdjustedAfterNonIdentityList.add(adjustedAfterNonIdentity);
+                int scanNodeId = scanNode.getId().asInt();
+                String targetExprSql = targetExpr.debugString();

Review Comment:
   do not use debugstring in normal code flow, It not only has poor performance but also may throw exceptions. if u want generate sql from expr, you should use `ExprToSqlVisitor`



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/RuntimeFilterPartitionPruneClassifier.java:
##########
@@ -0,0 +1,295 @@
+// 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.glue.translator;
+
+import org.apache.doris.analysis.Expr;
+import org.apache.doris.analysis.FunctionCallExpr;
+import org.apache.doris.analysis.LiteralExpr;
+import org.apache.doris.analysis.SlotRef;
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.ListPartitionItem;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.PartitionInfo;
+import org.apache.doris.catalog.PartitionItem;
+import org.apache.doris.catalog.PartitionKey;
+import org.apache.doris.catalog.PartitionType;
+import org.apache.doris.catalog.RangePartitionItem;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.planner.OlapScanNode;
+import org.apache.doris.planner.PlanNode;
+import org.apache.doris.thrift.TTargetExprMonotonicity;
+
+import com.google.common.collect.Range;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Classifies whether one runtime-filter target can safely drive BE-side partition pruning.
+ *
+ * <p>This is the single FE gate for RF partition pruning. It intentionally reasons about the
+ * final legacy target expression that will be sent to BE, so late-added casts and partition
+ * expression domains cannot bypass the safety checks.
+ */
+final class RuntimeFilterPartitionPruneClassifier {
+    private RuntimeFilterPartitionPruneClassifier() {
+    }
+
+    static Classification classify(Expr targetExpr, Expression nereidsTargetExpr, PlanNode scanNode) {
+        if (!(scanNode instanceof OlapScanNode)) {
+            return Classification.unsupported("target scan is not an OlapScanNode");
+        }
+
+        OlapScanNode olapScanNode = (OlapScanNode) scanNode;
+        OlapTable table = olapScanNode.getOlapTable();
+        if (table == null) {
+            return Classification.unsupported("target scan has no OlapTable");
+        }
+
+        PartitionInfo partitionInfo = table.getPartitionInfo();
+        PartitionType partType = partitionInfo.getType();
+        if (partType != PartitionType.RANGE && partType != PartitionType.LIST) {
+            return Classification.unsupported("partition type is not RANGE or LIST");
+        }
+        if (hasUnsupportedAutomaticPartitionExpression(partitionInfo)) {
+            return Classification.unsupported("automatic partition expression boundary is not modeled");
+        }
+
+        if (targetExpr instanceof SlotRef) {
+            SlotRef slotRef = (SlotRef) targetExpr;
+            if (!isPartitionColumnSlot(slotRef, partitionInfo.getPartitionColumns())) {
+                return Classification.unsupported("target SlotRef is not a partition column");
+            }
+            if (!hasSerializedBoundary(slotRef, partitionInfo, partType)) {
+                return Classification.unsupported("target SlotRef has no serialized partition boundary");
+            }
+            Map<Long, TTargetExprMonotonicity> partitionMonotonicity =
+                    allSelectedPartitionsIncreasing(olapScanNode, partitionInfo);
+            if (partitionMonotonicity.isEmpty()) {
+                return Classification.unsupported("target SlotRef has no prunable selected partitions");
+            }
+            return Classification.supportedPartitions(slotRef, partitionMonotonicity);
+        }
+
+        SlotRef leafSlot = findUniqueSlotRef(targetExpr);
+        if (leafSlot == null || !isPartitionColumnSlot(leafSlot, partitionInfo.getPartitionColumns())) {
+            return Classification.unsupported("target expression is not rooted on one partition column");
+        }
+        if (partType != PartitionType.RANGE) {
+            return Classification.unsupported("local monotonicity is only supported for RANGE partition");
+        }
+        if (!hasSerializedBoundary(leafSlot, partitionInfo, partType)) {
+            return Classification.unsupported("target expression has no serialized partition boundary");
+        }
+
+        Map<Long, TTargetExprMonotonicity> partitionMonotonicity =
+                classifyLocalMonotonicity(nereidsTargetExpr, olapScanNode, partitionInfo, leafSlot);
+        if (partitionMonotonicity.isEmpty()) {
+            return Classification.unsupported("target expression is not monotonic on selected partitions");
+        }
+        return Classification.supportedPartitions(leafSlot, partitionMonotonicity);
+    }
+
+    private static boolean hasUnsupportedAutomaticPartitionExpression(PartitionInfo partitionInfo) {
+        if (!partitionInfo.enableAutomaticPartition()) {
+            return false;
+        }
+        for (Expr partitionExpr : partitionInfo.getPartitionExprs()) {
+            if (containsFunctionCall(partitionExpr)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean containsFunctionCall(Expr expr) {
+        if (expr instanceof FunctionCallExpr) {
+            return true;
+        }
+        for (Expr child : expr.getChildren()) {
+            if (containsFunctionCall(child)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasSerializedBoundary(SlotRef slotRef, PartitionInfo partitionInfo, PartitionType partType) {
+        if (partType != PartitionType.RANGE) {
+            return true;
+        }
+        List<Column> partitionColumns = partitionInfo.getPartitionColumns();
+        return !partitionColumns.isEmpty() && sameColumn(slotRef.getColumn(), partitionColumns.get(0));
+    }
+
+    private static boolean isPartitionColumnSlot(SlotRef slotRef, List<Column> partitionColumns) {
+        Column targetColumn = slotRef.getColumn();
+        if (targetColumn == null) {
+            return false;
+        }
+        for (Column partitionColumn : partitionColumns) {
+            if (sameColumn(targetColumn, partitionColumn)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean sameColumn(Column targetColumn, Column partitionColumn) {
+        if (targetColumn == partitionColumn) {
+            return true;
+        }
+        int targetUniqueId = targetColumn.getUniqueId();
+        int partitionUniqueId = partitionColumn.getUniqueId();
+        if (targetUniqueId != Column.COLUMN_UNIQUE_ID_INIT_VALUE
+                && partitionUniqueId != Column.COLUMN_UNIQUE_ID_INIT_VALUE
+                && targetUniqueId == partitionUniqueId) {
+            return true;
+        }
+        return targetColumn.equals(partitionColumn);
+    }
+
+    private static SlotRef findUniqueSlotRef(Expr expr) {
+        if (expr instanceof SlotRef) {
+            return (SlotRef) expr;
+        }
+        SlotRef result = null;
+        for (Expr child : expr.getChildren()) {
+            SlotRef childSlot = findUniqueSlotRef(child);
+            if (childSlot == null) {
+                continue;
+            }
+            if (result != null && result.getSlotId().asInt() != childSlot.getSlotId().asInt()) {
+                return null;
+            }
+            result = childSlot;
+        }
+        return result;
+    }
+
+    private static Map<Long, TTargetExprMonotonicity> classifyLocalMonotonicity(
+            Expression nereidsTargetExpr, OlapScanNode scanNode, PartitionInfo partitionInfo, SlotRef leafSlot) {
+        Map<Long, TTargetExprMonotonicity> result = new HashMap<>();
+        if (!(nereidsTargetExpr instanceof Monotonic) || nereidsTargetExpr.getInputSlots().size() != 1) {

Review Comment:
   getInputSlots return set. so if a expr has multi input with same slot, the getInputSlots().size() equals 1, but the expr maybe not Monotonicity for this slot.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4439141217

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 169452 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 2337ac55b6530b92cabafe14881c96951fab95c9, data reload: false
   
   query5	4308	637	487	487
   query6	430	204	175	175
   query7	4835	554	299	299
   query8	367	208	238	208
   query9	8827	4151	4164	4151
   query10	440	311	255	255
   query11	5944	2356	2270	2270
   query12	155	99	99	99
   query13	1255	599	435	435
   query14	6468	5324	5016	5016
   query14_1	4351	4316	4283	4283
   query15	202	201	178	178
   query16	1041	461	441	441
   query17	1145	756	624	624
   query18	2727	486	351	351
   query19	212	193	154	154
   query20	111	108	104	104
   query21	216	139	114	114
   query22	13684	13858	14600	13858
   query23	17394	16491	16177	16177
   query23_1	16280	16243	16195	16195
   query24	7527	1786	1362	1362
   query24_1	1358	1360	1393	1360
   query25	567	452	372	372
   query26	1303	307	167	167
   query27	2619	604	336	336
   query28	4413	2034	2000	2000
   query29	1055	608	489	489
   query30	302	238	202	202
   query31	1095	1053	934	934
   query32	101	56	55	55
   query33	533	323	245	245
   query34	1186	1171	655	655
   query35	738	772	662	662
   query36	1319	1325	1167	1167
   query37	148	101	84	84
   query38	3193	3147	3097	3097
   query39	917	915	899	899
   query39_1	873	863	867	863
   query40	218	136	109	109
   query41	62	59	63	59
   query42	91	90	89	89
   query43	313	315	277	277
   query44	
   query45	193	189	181	181
   query46	1125	1234	708	708
   query47	2264	2205	2248	2205
   query48	401	428	310	310
   query49	635	496	397	397
   query50	728	300	213	213
   query51	4293	4253	4175	4175
   query52	88	86	73	73
   query53	239	258	191	191
   query54	255	208	189	189
   query55	76	71	68	68
   query56	225	223	207	207
   query57	1398	1380	1265	1265
   query58	231	222	210	210
   query59	1568	1694	1410	1410
   query60	288	260	235	235
   query61	156	155	168	155
   query62	675	618	555	555
   query63	222	185	185	185
   query64	2529	800	632	632
   query65	
   query66	1709	477	357	357
   query67	29470	29955	29233	29233
   query68	
   query69	440	305	276	276
   query70	930	948	968	948
   query71	299	238	213	213
   query72	2942	2753	2387	2387
   query73	881	827	433	433
   query74	5109	4914	4744	4744
   query75	2747	2631	2300	2300
   query76	2347	1180	750	750
   query77	360	403	302	302
   query78	12964	12933	12370	12370
   query79	1491	1046	774	774
   query80	1331	484	390	390
   query81	527	278	242	242
   query82	623	156	122	122
   query83	332	265	253	253
   query84	262	140	112	112
   query85	900	492	425	425
   query86	424	305	288	288
   query87	3452	3275	3171	3171
   query88	3775	2845	2822	2822
   query89	423	372	326	326
   query90	1958	186	179	179
   query91	182	173	136	136
   query92	59	63	52	52
   query93	1133	958	573	573
   query94	712	335	314	314
   query95	682	372	424	372
   query96	1077	830	349	349
   query97	2659	2655	2506	2506
   query98	211	203	200	200
   query99	1095	1133	1005	1005
   Total cold run time: 251965 ms
   Total hot run time: 169452 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4433603193

   # FE Regression Coverage Report
   Increment line coverage `  67.35% (198/294)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_e7a084166584e3ccdc5ae8b6bd5fc50d3c0cb832_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_e7a084166584e3ccdc5ae8b6bd5fc50d3c0cb832_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4432339457

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 29820 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit e7a084166584e3ccdc5ae8b6bd5fc50d3c0cb832, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17582	3892	3875	3875
   q2	q3	10708	898	619	619
   q4	4666	458	347	347
   q5	7447	1331	1147	1147
   q6	193	169	143	143
   q7	915	952	761	761
   q8	9315	1438	1290	1290
   q9	5578	5360	5321	5321
   q10	6282	2080	1798	1798
   q11	478	275	261	261
   q12	631	431	296	296
   q13	18094	3313	2761	2761
   q14	295	291	263	263
   q15	q16	896	844	800	800
   q17	928	1004	774	774
   q18	6568	5768	5662	5662
   q19	1170	1204	1098	1098
   q20	541	408	274	274
   q21	4458	2446	1986	1986
   q22	506	405	344	344
   Total cold run time: 97251 ms
   Total hot run time: 29820 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4745	4756	4782	4756
   q2	q3	4670	4818	4232	4232
   q4	2141	2183	1401	1401
   q5	5045	5045	5313	5045
   q6	204	176	137	137
   q7	2033	1808	1604	1604
   q8	3405	3064	3136	3064
   q9	8527	8469	8510	8469
   q10	4583	4507	4260	4260
   q11	604	434	403	403
   q12	714	750	517	517
   q13	3185	3603	3004	3004
   q14	302	296	265	265
   q15	q16	769	782	718	718
   q17	1498	1317	1266	1266
   q18	8046	7136	7278	7136
   q19	1139	1162	1160	1160
   q20	2232	2224	1946	1946
   q21	6218	5510	4945	4945
   q22	575	529	418	418
   Total cold run time: 60635 ms
   Total hot run time: 54746 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4448452198

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 29653 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 5bfeb4d44af5550f9edd89c84a3d05b18135d530, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17629	3907	3949	3907
   q2	q3	10791	900	617	617
   q4	4729	472	348	348
   q5	8267	1343	1151	1151
   q6	370	177	138	138
   q7	940	957	768	768
   q8	11249	1501	1268	1268
   q9	7537	5388	5336	5336
   q10	6421	2122	1847	1847
   q11	473	260	254	254
   q12	687	426	302	302
   q13	18153	3269	2747	2747
   q14	294	288	268	268
   q15	q16	906	862	810	810
   q17	942	1041	653	653
   q18	6532	5742	5628	5628
   q19	1171	1374	1098	1098
   q20	541	422	287	287
   q21	4729	2320	1918	1918
   q22	435	367	308	308
   Total cold run time: 102796 ms
   Total hot run time: 29653 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4631	4573	4588	4573
   q2	q3	4703	4818	4236	4236
   q4	2122	2178	1385	1385
   q5	4989	4998	5277	4998
   q6	196	162	131	131
   q7	2054	1798	1663	1663
   q8	3497	3298	3166	3166
   q9	8484	8390	8426	8390
   q10	4508	4460	4273	4273
   q11	627	422	401	401
   q12	693	755	538	538
   q13	3287	3637	2950	2950
   q14	315	301	269	269
   q15	q16	769	801	705	705
   q17	1314	1290	1274	1274
   q18	7952	7144	7160	7144
   q19	1176	1141	1085	1085
   q20	2192	2230	1941	1941
   q21	6134	5392	4795	4795
   q22	526	472	404	404
   Total cold run time: 60169 ms
   Total hot run time: 54321 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3108102326


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,371 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <utility>
+
+#include "exprs/hybrid_set.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// Helper to extract a typed value from a ColumnPtr's first row.
+// Must be a template function so that `if constexpr` properly discards
+// the inapplicable branch during instantiation.
+template <PrimitiveType PT>
+static typename PrimitiveTypeTraits<PT>::CppType extract_value_from_column(const ColumnPtr& col) {
+    using CppType = typename PrimitiveTypeTraits<PT>::CppType;
+    auto data = col->get_data_at(0);
+    if constexpr (is_string_type(PT)) {
+        return CppType(data.data, data.size);
+    } else {
+        return *reinterpret_cast<const CppType*>(data.data);
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+void RuntimeFilterPartitionPruner::parse_boundaries(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            continue;
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            continue;
+        }
+        SlotDescriptor* slot = slot_it->second;
+        PrimitiveType ptype = slot->type()->get_primitive_type();
+        int precision = cast_set<int>(slot->type()->get_precision());
+        int scale = cast_set<int>(slot->type()->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) break;                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node =                                                                \
+                [&](const TExprNode& node) -> std::optional<std::pair<CppType, ColumnPtr>> {   \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            VLiteral literal(node);                                                            \
+            auto col_ptr = literal.get_column_ptr();                                           \
+            auto val = extract_value_from_column<TYPE_##NAME>(col_ptr);                        \
+            return std::make_optional(std::pair<CppType, ColumnPtr> {val, col_ptr});           \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                auto& [val, col_ptr] = *parsed;                                                \
+                boundary.literal_columns.push_back(std::move(col_ptr));                        \
+                static_cast<void>(empty_cvr.add_fixed_value(val));                             \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                cvr.set_contain_null(true);                                                    \

Review Comment:
   When a LIST boundary contains both NULL and concrete values, `cvr.intersection(empty_cvr)` first materializes the concrete fixed-value set, but `set_contain_null(true)` immediately calls `set_empty_value_range()` and discards those fixed values. That turns a partition like `{NULL, 5}` into a NULL-only boundary, so `_try_prune_by_single_rf()` can later prune it even when the runtime filter contains `5`. Please preserve both the concrete values and the NULL marker here instead of resetting the CVR.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4278332442

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4493991499

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4505745196

   # BE Regression && UT Coverage Report
   Increment line coverage `75.28% (603/801)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.50% (27845/37883) |
   | Line Coverage     | 57.40% (302409/526849) |
   | Region Coverage   | 54.25% (253050/466433) |
   | Branch Coverage   | 55.77% (109737/196776) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4507178536

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 168747 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 6be29ffaf962c85e6c243c68a1c26cf413cdb0b5, data reload: false
   
   query5	4335	663	518	518
   query6	469	215	193	193
   query7	4818	572	321	321
   query8	379	227	226	226
   query9	8841	4065	4063	4063
   query10	485	332	273	273
   query11	5940	2427	2218	2218
   query12	157	102	98	98
   query13	1350	566	431	431
   query14	6462	5326	5003	5003
   query14_1	4362	4361	4299	4299
   query15	206	196	179	179
   query16	999	496	464	464
   query17	1150	718	603	603
   query18	2505	489	375	375
   query19	214	205	150	150
   query20	115	106	107	106
   query21	219	140	125	125
   query22	13634	13531	13468	13468
   query23	17293	16304	15929	15929
   query23_1	16196	16084	16124	16084
   query24	7518	1773	1342	1342
   query24_1	1323	1339	1300	1300
   query25	591	479	423	423
   query26	1316	315	177	177
   query27	2660	546	333	333
   query28	4475	1962	1942	1942
   query29	1109	634	526	526
   query30	305	236	198	198
   query31	1107	1049	945	945
   query32	111	62	59	59
   query33	524	321	246	246
   query34	1167	1118	650	650
   query35	733	777	677	677
   query36	1322	1307	1187	1187
   query37	151	106	93	93
   query38	3216	3138	3063	3063
   query39	928	941	919	919
   query39_1	879	904	877	877
   query40	223	122	101	101
   query41	64	63	65	63
   query42	92	96	96	96
   query43	316	313	275	275
   query44	
   query45	194	185	176	176
   query46	1072	1218	749	749
   query47	2198	2207	2217	2207
   query48	400	421	296	296
   query49	626	468	346	346
   query50	1042	344	248	248
   query51	4347	4276	4246	4246
   query52	89	93	81	81
   query53	247	267	193	193
   query54	267	220	194	194
   query55	82	74	70	70
   query56	237	231	230	230
   query57	1391	1363	1282	1282
   query58	247	212	222	212
   query59	1550	1594	1378	1378
   query60	281	251	230	230
   query61	158	162	157	157
   query62	668	630	571	571
   query63	232	192	191	191
   query64	2574	790	608	608
   query65	
   query66	1787	455	342	342
   query67	30050	30035	29757	29757
   query68	
   query69	442	317	272	272
   query70	971	932	947	932
   query71	304	229	208	208
   query72	2900	2719	2438	2438
   query73	880	768	419	419
   query74	5067	4918	4725	4725
   query75	2643	2561	2258	2258
   query76	2329	1128	772	772
   query77	354	378	285	285
   query78	12155	12123	11684	11684
   query79	1458	1087	770	770
   query80	1277	489	386	386
   query81	522	282	241	241
   query82	606	156	122	122
   query83	328	266	243	243
   query84	257	143	108	108
   query85	909	571	438	438
   query86	430	305	294	294
   query87	3342	3385	3178	3178
   query88	3585	2666	2677	2666
   query89	423	377	322	322
   query90	1936	179	199	179
   query91	181	176	146	146
   query92	63	60	58	58
   query93	1540	1463	868	868
   query94	726	363	306	306
   query95	689	468	346	346
   query96	1009	756	356	356
   query97	2679	2683	2537	2537
   query98	216	209	209	209
   query99	1137	1088	955	955
   Total cold run time: 251840 ms
   Total hot run time: 168747 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4563131394

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3310231274


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,875 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary must contain partition_id and slot_id");
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary references unknown slot_id={}, "
+                    "partition_id={}",
+                    slot_id, tb.partition_id);
+        }
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) {                                                           \
+            return Status::InternalError(                                                      \
+                    "Runtime filter partition boundary must be RANGE or LIST, "                \
+                    "partition_id={}, slot_id={}",                                             \
+                    tb.partition_id, slot_id);                                                 \
+        }                                                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        } else {
+            return Status::InternalError(
+                    "Runtime filter partition boundary has unsupported type, partition_id={}, "
+                    "slot_id={}, type={}",
+                    tb.partition_id, slot_id, slot_type->get_name());
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (expr->is_slot_ref()) {
+        return assert_cast<const VSlotRef*>(expr);
+    }
+    const VSlotRef* found = nullptr;
+    for (const auto& child : expr->children()) {
+        const VSlotRef* c = find_unique_slot_ref(child.get());
+        if (c) {
+            if (found) {
+                return nullptr; // multiple slot refs, can't handle
+            }
+            found = c;
+        }
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        if (direction == TTargetExprMonotonicity::NON_MONOTONIC) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received NON_MONOTONIC partition "
+                    "metadata, filter_id={}, partition_id={}",
+                    filter_id, partition_id);
+        }
+        if (!boundary_partition_ids.contains(partition_id)) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received monotonicity for a partition "
+                    "without boundary, filter_id={}, partition_id={}, slot_id={}",
+                    filter_id, partition_id, leaf_slot_id);
+        }
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        if (slot_ref->slot_id() != leaf_slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning SlotRef target mismatch, filter_id={}, "
+                    "target_slot_id={}, leaf_slot_id={}",
+                    filter_id, slot_ref->slot_id(), leaf_slot_id);
+        }
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            if (direction_it->second != TTargetExprMonotonicity::MONOTONIC_INCREASING) {
+                return Status::InternalError(
+                        "Runtime filter partition pruning SlotRef target must use increasing "
+                        "monotonicity, filter_id={}, partition_id={}, monotonicity={}",
+                        filter_id, boundary.partition_id, direction_it->second);
+            }
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    if (slot_type_it == _slot_data_types.end()) {
+        return Status::InternalError(
+                "Runtime filter partition pruning target slot has no boundary type, filter_id={}, "
+                "slot_id={}, target_expr={}",
+                filter_id, leaf_slot_id, target_expr->expr_name());
+    }
+
+    // LIST partitions require regrouping projected values per partition and
+    // should not be marked as expression-prunable by FE yet.
+    for (const auto& selected_boundary : selected_boundaries) {
+        bool is_list = false;
+        std::visit([&](const auto& cvr) { is_list = cvr.is_fixed_value_range(); },
+                   orig_boundaries[selected_boundary.first].boundary_cvr);
+        if (is_list) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning expression target does not support LIST "

Review Comment:
   之前list没支持expr,现在顺便加上了



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4555489946

   # FE Regression Coverage Report
   Increment line coverage `  68.09% (224/329)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_36cd8599c1b5eba6dcfb83d6c88457494678e987_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_36cd8599c1b5eba6dcfb83d6c88457494678e987_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "HappenLee (via GitHub)" <gi...@apache.org>.
HappenLee commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3309070083


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,875 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary must contain partition_id and slot_id");
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary references unknown slot_id={}, "
+                    "partition_id={}",
+                    slot_id, tb.partition_id);
+        }
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) {                                                           \
+            return Status::InternalError(                                                      \
+                    "Runtime filter partition boundary must be RANGE or LIST, "                \
+                    "partition_id={}, slot_id={}",                                             \
+                    tb.partition_id, slot_id);                                                 \
+        }                                                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        } else {
+            return Status::InternalError(
+                    "Runtime filter partition boundary has unsupported type, partition_id={}, "
+                    "slot_id={}, type={}",
+                    tb.partition_id, slot_id, slot_type->get_name());
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (expr->is_slot_ref()) {
+        return assert_cast<const VSlotRef*>(expr);
+    }
+    const VSlotRef* found = nullptr;
+    for (const auto& child : expr->children()) {
+        const VSlotRef* c = find_unique_slot_ref(child.get());
+        if (c) {
+            if (found) {
+                return nullptr; // multiple slot refs, can't handle
+            }
+            found = c;
+        }
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        if (direction == TTargetExprMonotonicity::NON_MONOTONIC) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received NON_MONOTONIC partition "
+                    "metadata, filter_id={}, partition_id={}",
+                    filter_id, partition_id);
+        }
+        if (!boundary_partition_ids.contains(partition_id)) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received monotonicity for a partition "
+                    "without boundary, filter_id={}, partition_id={}, slot_id={}",
+                    filter_id, partition_id, leaf_slot_id);
+        }
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        if (slot_ref->slot_id() != leaf_slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning SlotRef target mismatch, filter_id={}, "
+                    "target_slot_id={}, leaf_slot_id={}",
+                    filter_id, slot_ref->slot_id(), leaf_slot_id);
+        }
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            if (direction_it->second != TTargetExprMonotonicity::MONOTONIC_INCREASING) {
+                return Status::InternalError(
+                        "Runtime filter partition pruning SlotRef target must use increasing "
+                        "monotonicity, filter_id={}, partition_id={}, monotonicity={}",
+                        filter_id, boundary.partition_id, direction_it->second);
+            }
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    if (slot_type_it == _slot_data_types.end()) {
+        return Status::InternalError(
+                "Runtime filter partition pruning target slot has no boundary type, filter_id={}, "
+                "slot_id={}, target_expr={}",
+                filter_id, leaf_slot_id, target_expr->expr_name());
+    }
+
+    // LIST partitions require regrouping projected values per partition and
+    // should not be marked as expression-prunable by FE yet.
+    for (const auto& selected_boundary : selected_boundaries) {
+        bool is_list = false;
+        std::visit([&](const auto& cvr) { is_list = cvr.is_fixed_value_range(); },
+                   orig_boundaries[selected_boundary.first].boundary_cvr);
+        if (is_list) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning expression target does not support LIST "

Review Comment:
   这个是单纯没实现还是什么情况



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4555425948

   # BE Regression && UT Coverage Report
   Increment line coverage `76.18% (662/869)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_36cd8599c1b5eba6dcfb83d6c88457494678e987_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_36cd8599c1b5eba6dcfb83d6c88457494678e987_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.73% (28080/38083) |
   | Line Coverage     | 57.67% (305229/529311) |
   | Region Coverage   | 54.50% (255612/469035) |
   | Branch Coverage   | 55.94% (110842/198130) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3115782627


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/RuntimeFilterTranslator.java:
##########
@@ -190,4 +199,66 @@ private org.apache.doris.planner.RuntimeFilter finalize(org.apache.doris.planner
         origFilter.extractTargetsPosition();
         return origFilter;
     }
+
+    /**
+     * Classify whether a runtime-filter target expression can drive partition
+     * pruning on the given scan node, by checking that the target expression
+     * resolves (possibly through a chain of Nereids {@link Monotonic} functions)
+     * to a single partition column and tracking the cumulative direction.
+     *
+     * <p>This must run while the original Nereids {@code Expression} is still
+     * available; legacy planner only sees an {@code analysis.Expr} after
+     * translation and cannot redo the classification. The result is stored on
+     * the legacy {@code RuntimeFilter} and consumed at thrift-serialization time
+     * (and by {@code OlapScanNode.toThrift} to gate boundary emission).
+     *
+     * <p>Trust the {@code Monotonic} contract — implementers (DateTrunc, Year,

Review Comment:
   This is a correctness issue. Several Nereids functions that implement Monotonic are only conditionally monotonic and rely on isMonotonic(lower, upper), for example from_unixtime, date_format, time_format, convert_tz, and yearweek. Because this classifier ignores that hook, a target like from_unixtime(part_col, %m) can still be marked monotonic and sent to BE. The BE then projects partition endpoints through the target expression and may false-prune partitions that cross a wrap boundary, for example a year-spanning partition whose month set is {12, 01, 02}. Please either consult the range-aware hook with the actual partition bounds before serializing monotonicity, or conservatively treat these shapes as NON_MONOTONIC.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4288162491

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4287487790

   # FE UT Coverage Report
   Increment line coverage `  25.95% (34/131)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6bb4482b83b38672c7368e51b346c7bea9dc9247/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6bb4482b83b38672c7368e51b346c7bea9dc9247/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4286988347

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4590021323

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 170798 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 586640a94f47548a444d49ee53d5474cf04e3ae0, data reload: false
   
   query5	4324	637	481	481
   query6	434	202	187	187
   query7	4868	575	315	315
   query8	366	221	208	208
   query9	8789	4064	4059	4059
   query10	460	325	268	268
   query11	5904	2369	2197	2197
   query12	158	100	99	99
   query13	1263	596	420	420
   query14	6444	5447	5064	5064
   query14_1	4377	4353	4385	4353
   query15	206	196	173	173
   query16	1034	466	425	425
   query17	1101	703	565	565
   query18	2505	498	338	338
   query19	198	184	142	142
   query20	111	106	104	104
   query21	224	137	119	119
   query22	13662	13677	13575	13575
   query23	17521	16481	16197	16197
   query23_1	16323	16249	16353	16249
   query24	7532	1790	1315	1315
   query24_1	1307	1305	1339	1305
   query25	600	468	408	408
   query26	1302	333	174	174
   query27	2656	540	359	359
   query28	4473	2053	2091	2053
   query29	1142	640	509	509
   query30	323	242	207	207
   query31	1141	1088	955	955
   query32	119	65	62	62
   query33	542	334	269	269
   query34	1231	1135	657	657
   query35	779	797	714	714
   query36	1462	1433	1262	1262
   query37	167	115	100	100
   query38	3264	3172	3073	3073
   query39	951	940	929	929
   query39_1	882	890	901	890
   query40	232	134	109	109
   query41	71	69	69	69
   query42	105	100	101	100
   query43	342	334	289	289
   query44	
   query45	211	190	192	190
   query46	1140	1242	810	810
   query47	2349	2338	2314	2314
   query48	404	417	310	310
   query49	659	492	375	375
   query50	976	364	259	259
   query51	4332	4329	4261	4261
   query52	97	99	80	80
   query53	258	283	195	195
   query54	297	243	220	220
   query55	83	82	76	76
   query56	273	249	256	249
   query57	1425	1429	1324	1324
   query58	269	228	226	226
   query59	1630	1719	1472	1472
   query60	305	272	252	252
   query61	178	223	156	156
   query62	700	650	578	578
   query63	222	186	192	186
   query64	2565	791	622	622
   query65	
   query66	1784	475	348	348
   query67	30007	29918	29675	29675
   query68	
   query69	454	313	273	273
   query70	1012	978	956	956
   query71	316	231	211	211
   query72	2985	2703	2365	2365
   query73	874	772	436	436
   query74	5154	4940	4752	4752
   query75	2738	2608	2245	2245
   query76	2342	1223	823	823
   query77	354	387	289	289
   query78	12433	12443	11891	11891
   query79	1474	1062	767	767
   query80	596	492	395	395
   query81	457	288	251	251
   query82	567	157	125	125
   query83	363	285	251	251
   query84	265	142	113	113
   query85	876	528	440	440
   query86	371	313	270	270
   query87	3394	3360	3188	3188
   query88	3724	2741	2786	2741
   query89	446	381	334	334
   query90	1873	187	180	180
   query91	179	170	142	142
   query92	67	63	62	62
   query93	1599	1568	889	889
   query94	566	350	302	302
   query95	714	377	344	344
   query96	1010	780	352	352
   query97	2688	2670	2557	2557
   query98	214	210	216	210
   query99	1185	1146	1024	1024
   Total cold run time: 252861 ms
   Total hot run time: 170798 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4428616760

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c_2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c_2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.05% (1849/2369) |
   | Line Coverage     | 64.72% (33221/51327) |
   | Region Coverage   | 65.24% (16440/25198) |
   | Branch Coverage   | 55.80% (8778/15732) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4440760469

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/ca30c183fbab4ffab1f11b3d91b0561736c7c199_ca30c183fbab4ffab1f11b3d91b0561736c7c199_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/ca30c183fbab4ffab1f11b3d91b0561736c7c199_ca30c183fbab4ffab1f11b3d91b0561736c7c199_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.03% (1850/2371) |
   | Line Coverage     | 64.68% (33223/51362) |
   | Region Coverage   | 65.23% (16446/25214) |
   | Branch Coverage   | 55.71% (8778/15758) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4540807328

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 31741 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit f779760a4db0a4e1f4605c63a8a9bc21f51af126, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17606	4087	4150	4087
   q2	q3	10979	1409	876	876
   q4	4817	481	351	351
   q5	10399	2289	2173	2173
   q6	375	180	138	138
   q7	941	792	660	660
   q8	9650	1872	1634	1634
   q9	7055	5026	5207	5026
   q10	6572	2203	1903	1903
   q11	438	280	239	239
   q12	671	427	302	302
   q13	18204	3550	2842	2842
   q14	270	260	244	244
   q15	q16	827	779	711	711
   q17	1002	888	972	888
   q18	7298	5715	5568	5568
   q19	1171	1210	1106	1106
   q20	522	403	262	262
   q21	5576	2635	2423	2423
   q22	441	362	308	308
   Total cold run time: 104814 ms
   Total hot run time: 31741 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4430	4373	4322	4322
   q2	q3	4589	4975	4362	4362
   q4	2163	2251	1429	1429
   q5	4565	5024	5087	5024
   q6	238	185	141	141
   q7	2026	1854	1607	1607
   q8	2547	2329	2232	2232
   q9	8132	8125	8316	8125
   q10	4896	4840	4321	4321
   q11	590	488	413	413
   q12	764	788	569	569
   q13	3404	3655	3059	3059
   q14	315	295	275	275
   q15	q16	738	753	688	688
   q17	1414	1390	1376	1376
   q18	8004	7261	6903	6903
   q19	1160	1109	1090	1090
   q20	2256	2264	1972	1972
   q21	5427	4771	4657	4657
   q22	535	462	426	426
   Total cold run time: 58193 ms
   Total hot run time: 52991 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4267225874

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4278836048

   # BE UT Coverage Report
   Increment line coverage `1.42% (5/353)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd_bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd_bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 53.19% (20328/38216) |
   | Line Coverage     | 36.74% (191335/520768) |
   | Region Coverage   | 33.02% (148832/450786) |
   | Branch Coverage   | 33.99% (65012/191278) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4279454767

   # BE Regression && UT Coverage Report
   Increment line coverage `81.59% (288/353)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.81% (27625/37429) |
   | Line Coverage     | 57.53% (298704/519189) |
   | Region Coverage   | 54.62% (248515/454969) |
   | Branch Coverage   | 56.06% (107555/191868) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3107948892


##########
be/src/exec/operator/olap_scan_operator.cpp:
##########
@@ -91,6 +95,9 @@ Status OlapScanLocalState::init(RuntimeState* state, LocalStateInfo& info) {
 
     RETURN_IF_ERROR(Base::init(state, info));
     RETURN_IF_ERROR(_sync_cloud_tablets(state));

Review Comment:
   This new OLAP path bypasses the existing `enable_runtime_filter_partition_prune` session flag. `FileScanner` still gates runtime-filter partition pruning on `query_options().enable_runtime_filter_partition_prune`, but here we always parse partition boundaries and later run `_do_partition_pruning_by_rf()` unconditionally from `_process_conjuncts()` / `_on_runtime_filter_update()`. As a result, `SET enable_runtime_filter_partition_prune = false` no longer disables the optimization for OLAP scans, which is a user-visible behavior regression.



##########
fe/fe-core/src/main/java/org/apache/doris/planner/OlapScanNode.java:
##########
@@ -1201,9 +1208,131 @@ protected void toThrift(TPlanNode msg) {
 
         msg.olap_scan_node.setDistributeColumnIds(new ArrayList<>(distributionColumnIds));
 
+        // Populate partition boundaries for BE-side runtime filter partition pruning.
+        // Only serialize when this scan node actually has runtime filters, to avoid
+        // bloating thrift for tables with many partitions but no RF consumers.
+        if (!runtimeFilters.isEmpty()) {
+            setPartitionBoundaries(msg.olap_scan_node);
+        }
+
         super.toThrift(msg);
     }
 
+    private void setPartitionBoundaries(TOlapScanNode olapScanNode) {
+        PartitionInfo partitionInfo = olapTable.getPartitionInfo();
+        PartitionType partType = partitionInfo.getType();
+        if (partType != PartitionType.RANGE && partType != PartitionType.LIST) {
+            return;
+        }
+        List<Column> partColumns = partitionInfo.getPartitionColumns();
+        if (partColumns.isEmpty()) {
+            return;
+        }
+
+        // Build partition column name → slot ID mapping
+        Map<String, Integer> partColToSlotId = Maps.newHashMap();
+        for (SlotDescriptor slot : desc.getSlots()) {
+            if (slot.getColumn() == null) {
+                continue;
+            }
+            for (Column partCol : partColumns) {
+                if (slot.getColumn().getName().equalsIgnoreCase(partCol.getName())) {
+                    partColToSlotId.put(partCol.getName(), slot.getId().asInt());
+                    break;
+                }
+            }
+        }
+        if (partColToSlotId.isEmpty()) {
+            return;
+        }
+
+        List<TPartitionBoundary> boundaries = new ArrayList<>();
+        for (Long partitionId : selectedPartitionIds) {
+            PartitionItem item = partitionInfo.getItem(partitionId);
+            if (item == null) {
+                continue;
+            }
+            if (item instanceof RangePartitionItem) {
+                addRangeBoundaries(boundaries, partitionId, (RangePartitionItem) item,
+                        partColumns, partColToSlotId);
+            } else if (item instanceof ListPartitionItem) {
+                addListBoundaries(boundaries, partitionId, (ListPartitionItem) item,
+                        partColumns, partColToSlotId);
+            }
+        }
+        if (!boundaries.isEmpty()) {
+            olapScanNode.setPartitionBoundaries(boundaries);
+        }
+    }
+
+    private void addRangeBoundaries(List<TPartitionBoundary> boundaries, long partitionId,
+            RangePartitionItem rangeItem, List<Column> partColumns,

Review Comment:
   This is not safe for multi-column RANGE partitions. Doris range partition membership is based on lexicographic `PartitionKey` comparison, not on an independent range over the first column. For example, a legal partition like `[(1, 1), (1, 5))` becomes a first-column boundary `[1, 1)`, so BE will see an empty interval and can prune the partition even when `k1 = 1` should still match rows inside it. Unless FE can serialize a provably correct projection, this optimization needs to be disabled for multi-column RANGE partitions.



##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,306 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include "exprs/hybrid_set.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// Helper to extract a typed value from a ColumnPtr's first row.
+// Must be a template function so that `if constexpr` properly discards
+// the inapplicable branch during instantiation.
+template <PrimitiveType PT>
+static typename PrimitiveTypeTraits<PT>::CppType extract_value_from_column(const ColumnPtr& col) {
+    using CppType = typename PrimitiveTypeTraits<PT>::CppType;
+    auto data = col->get_data_at(0);
+    if constexpr (is_string_type(PT)) {
+        return CppType(data.data, data.size);
+    } else {
+        return *reinterpret_cast<const CppType*>(data.data);
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+void RuntimeFilterPartitionPruner::parse_boundaries(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            continue;
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            continue;
+        }
+        SlotDescriptor* slot = slot_it->second;
+        PrimitiveType ptype = slot->type()->get_primitive_type();
+        int precision = cast_set<int>(slot->type()->get_precision());
+        int scale = cast_set<int>(slot->type()->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                              \
+    case TYPE_##NAME: {                                                                       \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                   \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                     \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                       \
+        if (!is_list && !is_range) break;                                                     \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);   \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::pair<CppType, ColumnPtr> { \

Review Comment:
   FE now emits `NULL_LITERAL` entries for nullable LIST partitions (`OlapScanNode.addListBoundaries()`), but this parser assumes every boundary literal can be converted into a concrete `CppType`. For a `NULL_LITERAL`, `VLiteral` builds a `ColumnNullable`; `get_data_at(0)` returns `{nullptr, 0}`, so the non-string branch dereferences a null pointer and the string branch collapses NULL to `""`. That makes nullable LIST partitions unsafe here. We need to propagate NULL through `ColumnValueRange::contain_null()` (or skip NULL boundaries for now) instead of treating it as an ordinary fixed value.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4286497520

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4553767121

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4556146615

   # FE UT Coverage Report
   Increment line coverage `  31.37% (96/306)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_36cd8599c1b5eba6dcfb83d6c88457494678e987/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_36cd8599c1b5eba6dcfb83d6c88457494678e987/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4494578659

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 169714 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit ef2529ca3c35ebe1f89cb9fa4212f239fce43a97, data reload: false
   
   query5	4330	661	512	512
   query6	331	232	212	212
   query7	4394	555	323	323
   query8	344	239	225	225
   query9	8844	4045	4084	4045
   query10	455	347	319	319
   query11	5812	2380	2265	2265
   query12	188	133	130	130
   query13	1344	594	434	434
   query14	6071	5427	5107	5107
   query14_1	4403	4409	4390	4390
   query15	210	201	183	183
   query16	1027	476	463	463
   query17	1153	739	621	621
   query18	2746	500	377	377
   query19	226	214	174	174
   query20	142	135	135	135
   query21	216	141	124	124
   query22	13699	13572	13413	13413
   query23	17149	16341	16070	16070
   query23_1	16167	16189	16210	16189
   query24	7775	1789	1306	1306
   query24_1	1318	1315	1313	1313
   query25	596	500	454	454
   query26	1315	336	179	179
   query27	2682	539	361	361
   query28	4340	2006	1989	1989
   query29	1032	651	523	523
   query30	316	255	208	208
   query31	1146	1066	951	951
   query32	91	80	78	78
   query33	570	362	303	303
   query34	1192	1123	634	634
   query35	738	770	673	673
   query36	1309	1321	1157	1157
   query37	150	103	89	89
   query38	3219	3120	3051	3051
   query39	946	925	896	896
   query39_1	883	877	862	862
   query40	224	151	125	125
   query41	66	65	63	63
   query42	117	115	110	110
   query43	315	328	294	294
   query44	
   query45	212	207	194	194
   query46	1089	1168	732	732
   query47	2271	2337	2231	2231
   query48	396	407	296	296
   query49	633	498	382	382
   query50	1003	360	251	251
   query51	4387	4310	4215	4215
   query52	107	105	97	97
   query53	267	282	208	208
   query54	319	274	255	255
   query55	94	92	84	84
   query56	307	306	317	306
   query57	1398	1432	1300	1300
   query58	295	270	276	270
   query59	1536	1613	1400	1400
   query60	314	330	307	307
   query61	163	156	159	156
   query62	687	623	567	567
   query63	252	206	201	201
   query64	2389	801	645	645
   query65	
   query66	1708	485	356	356
   query67	29372	29979	29867	29867
   query68	
   query69	466	330	309	309
   query70	1034	1013	1001	1001
   query71	309	270	258	258
   query72	2938	2727	2382	2382
   query73	858	763	447	447
   query74	5059	4904	4729	4729
   query75	2669	2638	2272	2272
   query76	2286	1130	764	764
   query77	396	397	333	333
   query78	12159	12278	11674	11674
   query79	1408	1154	728	728
   query80	641	566	475	475
   query81	457	275	241	241
   query82	1382	161	124	124
   query83	357	267	255	255
   query84	258	144	114	114
   query85	891	552	454	454
   query86	389	346	331	331
   query87	3382	3437	3196	3196
   query88	3526	2658	2690	2658
   query89	432	398	340	340
   query90	1941	179	186	179
   query91	182	172	137	137
   query92	80	77	76	76
   query93	1437	1419	977	977
   query94	553	332	317	317
   query95	700	390	430	390
   query96	1033	763	320	320
   query97	2718	2717	2589	2589
   query98	234	235	236	235
   query99	1098	1100	974	974
   Total cold run time: 252808 ms
   Total hot run time: 169714 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4428348636

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4564687984

   # FE Regression Coverage Report
   Increment line coverage `  64.50% (218/338)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_2ede6c8cafb5ff16b7121d7edac1018fc2e592e0_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_2ede6c8cafb5ff16b7121d7edac1018fc2e592e0_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4563546636

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/2ede6c8cafb5ff16b7121d7edac1018fc2e592e0_2ede6c8cafb5ff16b7121d7edac1018fc2e592e0_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/2ede6c8cafb5ff16b7121d7edac1018fc2e592e0_2ede6c8cafb5ff16b7121d7edac1018fc2e592e0_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.06% (1854/2375) |
   | Line Coverage     | 64.53% (33337/51665) |
   | Region Coverage   | 65.23% (16530/25343) |
   | Branch Coverage   | 55.75% (8838/15854) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4563736546

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 169925 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 2ede6c8cafb5ff16b7121d7edac1018fc2e592e0, data reload: false
   
   query5	4340	650	478	478
   query6	443	219	205	205
   query7	4833	592	305	305
   query8	377	229	219	219
   query9	8821	4076	4068	4068
   query10	483	321	256	256
   query11	5936	2597	2250	2250
   query12	165	106	100	100
   query13	1275	626	440	440
   query14	6737	5445	5107	5107
   query14_1	4433	4431	4438	4431
   query15	210	197	193	193
   query16	1034	482	451	451
   query17	1206	729	608	608
   query18	2729	490	365	365
   query19	243	196	150	150
   query20	112	107	110	107
   query21	220	144	122	122
   query22	13571	13566	13399	13399
   query23	17405	16505	16161	16161
   query23_1	16349	16249	16271	16249
   query24	7450	1792	1340	1340
   query24_1	1357	1328	1338	1328
   query25	570	472	422	422
   query26	1290	313	175	175
   query27	2594	582	347	347
   query28	4400	2008	2014	2008
   query29	1108	637	503	503
   query30	308	239	197	197
   query31	1139	1084	968	968
   query32	112	65	62	62
   query33	525	370	251	251
   query34	1189	1136	658	658
   query35	754	774	687	687
   query36	1392	1388	1237	1237
   query37	159	106	93	93
   query38	3212	3120	3081	3081
   query39	936	935	894	894
   query39_1	867	870	855	855
   query40	217	122	100	100
   query41	71	62	61	61
   query42	95	95	92	92
   query43	321	321	278	278
   query44	
   query45	200	188	182	182
   query46	1101	1212	767	767
   query47	2343	2362	2237	2237
   query48	389	438	325	325
   query49	662	467	356	356
   query50	973	364	272	272
   query51	4314	4241	4260	4241
   query52	88	86	77	77
   query53	250	272	188	188
   query54	266	224	214	214
   query55	78	77	68	68
   query56	257	245	210	210
   query57	1445	1380	1332	1332
   query58	242	219	215	215
   query59	1647	1715	1436	1436
   query60	303	247	237	237
   query61	169	152	160	152
   query62	689	648	588	588
   query63	225	186	188	186
   query64	2487	784	641	641
   query65	
   query66	1744	471	371	371
   query67	29720	29729	29571	29571
   query68	
   query69	434	309	280	280
   query70	960	940	966	940
   query71	294	221	207	207
   query72	3031	2752	2474	2474
   query73	837	775	438	438
   query74	5167	4971	4792	4792
   query75	2680	2587	2242	2242
   query76	2262	1145	791	791
   query77	365	375	290	290
   query78	12355	12323	11992	11992
   query79	1475	1093	773	773
   query80	1260	495	387	387
   query81	499	284	239	239
   query82	568	160	120	120
   query83	369	291	251	251
   query84	275	146	114	114
   query85	956	549	451	451
   query86	415	303	283	283
   query87	3435	3359	3219	3219
   query88	3619	2712	2702	2702
   query89	447	376	325	325
   query90	1845	182	179	179
   query91	182	174	144	144
   query92	69	66	59	59
   query93	1533	1450	899	899
   query94	693	356	310	310
   query95	703	462	370	370
   query96	1011	791	338	338
   query97	2729	2694	2591	2591
   query98	211	219	211	211
   query99	1194	1171	1064	1064
   Total cold run time: 252953 ms
   Total hot run time: 169925 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4570966993

   # FE UT Coverage Report
   Increment line coverage `  31.37% (96/306)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4571779736

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 171574 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008, data reload: false
   
   query5	4338	632	476	476
   query6	448	200	190	190
   query7	4866	583	316	316
   query8	369	222	207	207
   query9	8765	4027	4032	4027
   query10	460	327	265	265
   query11	5950	2585	2228	2228
   query12	165	107	106	106
   query13	1287	610	456	456
   query14	6550	5434	5121	5121
   query14_1	4438	4471	4351	4351
   query15	206	197	177	177
   query16	1016	454	435	435
   query17	1101	690	554	554
   query18	2626	476	340	340
   query19	213	186	146	146
   query20	114	109	107	107
   query21	222	139	120	120
   query22	13658	13608	13381	13381
   query23	17386	16489	16339	16339
   query23_1	16464	16334	16385	16334
   query24	7717	1770	1319	1319
   query24_1	1309	1339	1328	1328
   query25	567	449	383	383
   query26	1306	303	168	168
   query27	2674	561	336	336
   query28	4448	1999	2008	1999
   query29	1088	605	500	500
   query30	309	244	203	203
   query31	1144	1080	955	955
   query32	110	63	60	60
   query33	535	344	265	265
   query34	1192	1133	672	672
   query35	764	797	708	708
   query36	1415	1413	1223	1223
   query37	156	107	91	91
   query38	3228	3137	3110	3110
   query39	933	916	914	914
   query39_1	877	888	897	888
   query40	234	127	110	110
   query41	84	70	69	69
   query42	100	96	96	96
   query43	331	325	279	279
   query44	
   query45	201	197	184	184
   query46	1097	1178	752	752
   query47	2427	2425	2305	2305
   query48	402	441	312	312
   query49	661	480	370	370
   query50	971	360	275	275
   query51	4392	4316	4271	4271
   query52	92	92	80	80
   query53	262	272	197	197
   query54	288	234	219	219
   query55	83	78	75	75
   query56	252	240	246	240
   query57	1459	1416	1340	1340
   query58	251	232	228	228
   query59	1605	1722	1479	1479
   query60	300	265	255	255
   query61	190	182	184	182
   query62	699	654	585	585
   query63	238	189	192	189
   query64	2610	836	608	608
   query65	
   query66	1752	469	345	345
   query67	29810	29625	29656	29625
   query68	
   query69	435	314	273	273
   query70	956	973	965	965
   query71	310	222	217	217
   query72	3013	2714	2644	2644
   query73	883	803	440	440
   query74	5147	4962	4801	4801
   query75	2704	2559	2231	2231
   query76	2310	1149	788	788
   query77	362	383	289	289
   query78	12443	12602	11954	11954
   query79	1506	1070	751	751
   query80	1300	486	424	424
   query81	524	282	239	239
   query82	663	160	123	123
   query83	331	288	251	251
   query84	266	150	112	112
   query85	944	530	453	453
   query86	450	304	295	295
   query87	3371	3357	3166	3166
   query88	3708	2755	2804	2755
   query89	432	381	333	333
   query90	1920	183	187	183
   query91	182	168	142	142
   query92	67	63	61	61
   query93	1637	1372	889	889
   query94	705	355	314	314
   query95	689	480	347	347
   query96	1118	799	338	338
   query97	2713	2700	2630	2630
   query98	220	213	212	212
   query99	1159	1157	1024	1024
   Total cold run time: 254021 ms
   Total hot run time: 171574 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4507080080

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 31614 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 6be29ffaf962c85e6c243c68a1c26cf413cdb0b5, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17631	3976	3952	3952
   q2	q3	10748	1394	805	805
   q4	4685	472	354	354
   q5	7574	2317	2143	2143
   q6	238	180	142	142
   q7	961	778	649	649
   q8	9369	1846	1672	1672
   q9	5198	4956	4911	4911
   q10	6404	2110	1809	1809
   q11	446	281	244	244
   q12	642	431	312	312
   q13	18129	3430	2769	2769
   q14	266	254	240	240
   q15	q16	811	767	709	709
   q17	935	853	957	853
   q18	7129	5665	5616	5616
   q19	1180	1234	1131	1131
   q20	543	597	305	305
   q21	6078	2786	2679	2679
   q22	482	379	319	319
   Total cold run time: 99449 ms
   Total hot run time: 31614 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4730	4714	4751	4714
   q2	q3	4882	5255	4639	4639
   q4	2188	2248	1446	1446
   q5	4859	4797	4755	4755
   q6	226	184	137	137
   q7	1859	1759	1577	1577
   q8	2495	2134	2111	2111
   q9	7250	7305	7269	7269
   q10	4517	4415	4013	4013
   q11	559	393	374	374
   q12	718	735	524	524
   q13	3041	3459	2846	2846
   q14	271	284	256	256
   q15	q16	678	704	624	624
   q17	1312	1284	1286	1284
   q18	7392	6973	6835	6835
   q19	1136	1107	1105	1105
   q20	2225	2223	1945	1945
   q21	5500	4751	4722	4722
   q22	522	484	434	434
   Total cold run time: 56360 ms
   Total hot run time: 51610 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4505245547

   # BE Regression && UT Coverage Report
   Increment line coverage `75.28% (603/801)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.50% (27894/37949) |
   | Line Coverage     | 57.49% (302778/526635) |
   | Region Coverage   | 54.40% (253555/466103) |
   | Branch Coverage   | 55.95% (109952/196522) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3220461861


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/RuntimeFilterTranslator.java:
##########
@@ -322,6 +336,18 @@ public void createLegacyRuntimeFilter(RuntimeFilter filter, PlanNode node, PlanT
                     Expr targetExpr = targetExprList.get(i);
                     origFilter.addTarget(new RuntimeFilterTarget(
                             scanNode, targetExpr, true, isLocalTarget));
+                    RuntimeFilterPartitionPruneClassifier.Classification classification =
+                            RuntimeFilterPartitionPruneClassifier.classify(
+                                    targetExpr, filter.getTargetExpressions().get(i), scanNode);

Review Comment:
   The classifier is proving monotonicity from `filter.getTargetExpressions().get(i)`, but the BE will project partition boundaries through the final legacy `targetExpr` stored in the RF, including any `CastExpr` injected by the type-adjustment block above. That means a target expression that is locally monotonic before type adjustment can still be marked safe even though the actual expression evaluated in `ParsedPartitionBoundaries::get_or_compute_projection()` has one more cast on top that was not checked against the partition range.
   
   Please either classify an equivalent Nereids expression that includes the inserted cast, or conservatively skip RF partition pruning whenever the adjustment at line 307 wraps a non-identity target. The current tests cover the bare-slot implicit-cast case, but not a locally monotonic non-slot target with an added outer cast.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4421051778

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4421055083

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4422660576

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 29730 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit b29520eb8c2fd176695ee6c263c89bab10446d4a, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17704	4060	3972	3972
   q2	q3	10788	893	615	615
   q4	4708	460	357	357
   q5	8237	1345	1148	1148
   q6	347	174	140	140
   q7	954	975	760	760
   q8	11064	1456	1303	1303
   q9	7476	5451	5307	5307
   q10	6314	2088	1802	1802
   q11	469	274	254	254
   q12	684	417	290	290
   q13	18192	3343	2765	2765
   q14	293	282	262	262
   q15	q16	896	868	782	782
   q17	1013	1084	780	780
   q18	6544	5684	5602	5602
   q19	1205	1253	1123	1123
   q20	525	395	274	274
   q21	4830	2296	1881	1881
   q22	429	355	313	313
   Total cold run time: 102672 ms
   Total hot run time: 29730 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4323	4210	4256	4210
   q2	q3	4651	4786	4182	4182
   q4	2156	2198	1413	1413
   q5	4989	4969	5609	4969
   q6	219	178	148	148
   q7	2059	1835	1657	1657
   q8	3444	3164	3170	3164
   q9	8339	8540	8450	8450
   q10	4501	4538	4254	4254
   q11	661	422	412	412
   q12	694	755	529	529
   q13	3240	3600	2901	2901
   q14	304	307	298	298
   q15	q16	776	792	693	693
   q17	1418	1369	1527	1369
   q18	8050	7276	7155	7155
   q19	1192	1180	1151	1151
   q20	2282	2261	1996	1996
   q21	6273	5636	5043	5043
   q22	577	513	468	468
   Total cold run time: 60148 ms
   Total hot run time: 54462 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4268937326

   # FE Regression Coverage Report
   Increment line coverage `  77.32% (75/97)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_141c78da888bc5cd8198ffebb607430c90015f0d_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_141c78da888bc5cd8198ffebb607430c90015f0d_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4267253429

   OpenCode automated review failed and did not complete.
   
   Error: Review step was skipped (possibly timeout or cancelled)
   Workflow run: https://github.com/apache/doris/actions/runs/24560451334
   
   Please inspect the workflow logs and rerun the review after the underlying issue is resolved.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4269319798

   OpenCode automated review failed and did not complete.
   
   Error: Review step was failure (possibly timeout or cancelled)
   Workflow run: https://github.com/apache/doris/actions/runs/24567893050
   
   Please inspect the workflow logs and rerun the review after the underlying issue is resolved.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4278766869

   # FE UT Coverage Report
   Increment line coverage `  22.33% (23/103)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4278332872

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3116865659


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,736 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vruntimefilter_wrapper.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+void ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            continue;
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            continue;
+        }
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) break;                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _partition_column_slot_ids.insert(slot_id);
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_partition_column_slot_ids.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (expr->is_slot_ref()) {
+        return assert_cast<const VSlotRef*>(expr);
+    }
+    const VSlotRef* found = nullptr;
+    for (const auto& child : expr->children()) {
+        const VSlotRef* c = find_unique_slot_ref(child.get());
+        if (c) {
+            if (found) {
+                return nullptr; // multiple slot refs, can't handle
+            }
+            found = c;
+        }
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+const std::vector<ParsedBoundary>* ParsedPartitionBoundaries::get_or_compute_projection(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        TTargetExprMonotonicity::type direction, VExprContext* ctx) const {
+    {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        auto it = _projection_cache.find(filter_id);
+        if (it != _projection_cache.end()) {
+            return &it->second;
+        }
+    }
+
+    // Build projection
+    std::vector<ParsedBoundary> projected;
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    if (slot_type_it == _slot_data_types.end()) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    // LIST partitions: TODO(rf-partition-prune) -- projecting through a
+    // monotonic function preserves set membership but requires re-grouping
+    // the projected values back per-partition. Skip for now (caller treats
+    // empty cache as "no projection available").
+    {
+        bool any_list = false;
+        std::visit([&](const auto& cvr) { any_list = cvr.is_fixed_value_range(); },
+                   orig_boundaries[0].boundary_cvr);
+        if (any_list) {
+            std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+            _projection_cache[filter_id] = std::move(projected);
+            return &_projection_cache[filter_id];
+        }
+    }
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = orig_boundaries.size();
+
+    // Track open bounds (low_value == TYPE_MIN or high_value == TYPE_MAX)
+    // externally so we can propagate null_in_projection regardless of whether
+    // the input column is nullable.
+    std::vector<bool> ext_null_lo(N, false);
+    std::vector<bool> ext_null_hi(N, false);
+
+    // Build input blocks for lo and hi
+    Block lo_block;
+    Block hi_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+        auto add_dummy = [&](Block& blk) {
+            auto col = ColumnUInt8::create(N, 0);
+            blk.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                        fmt::format("dummy_{}", col_idx)});
+        };
+        add_dummy(lo_block);
+        add_dummy(hi_block);
+    }
+
+    // Macro-dispatch on input PrimitiveType to build the typed value column
+    // for each side. Inserts NULL (when input column is nullable) or default
+    // (when not) for boundaries that are only_null / open / type-mismatched;
+    // ext_null_lo/hi tracks these so we can mark them null_in_projection
+    // after executing the projection.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[i];                                           \
+            bool null_lo = boundary.only_null;                                                   \
+            bool null_hi = boundary.only_null;                                                   \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            if (cvr == nullptr) {                                                                \
+                null_lo = null_hi = true;                                                        \
+            } else {                                                                             \
+                if (cvr->is_low_value_minimum()) null_lo = true;                                 \
+                if (cvr->is_high_value_maximum()) null_hi = true;                                \
+            }                                                                                    \
+            ext_null_lo[i] = null_lo;                                                            \
+            ext_null_hi[i] = null_hi;                                                            \
+            if (null_lo) {                                                                       \
+                lo_inner->insert_default();                                                      \
+                lo_nulls->get_data().push_back(1);                                               \
+            } else {                                                                             \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (null_hi) {                                                                       \
+                hi_inner->insert_default();                                                      \
+                hi_nulls->get_data().push_back(1);                                               \
+            } else {                                                                             \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    if (!input_built) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    auto status_lo = target_expr->execute(ctx, &lo_block, &lo_result_id);
+    auto status_hi = target_expr->execute(ctx, &hi_block, &hi_result_id);
+    if (!status_lo.ok() || !status_hi.ok() || lo_result_id < 0 || hi_result_id < 0) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    const auto& lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    const auto& hi_result_col = hi_block.get_by_position(hi_result_id).column;
+
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.resize(N);
+
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                         \
+    case TYPE_##OUTPUT_PT: {                                                                   \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;         \
+        for (size_t i = 0; i < N; ++i) {                                                       \
+            projected[i].partition_id = orig_boundaries[i].partition_id;                       \
+            projected[i].slot_id = leaf_slot_id;                                               \
+            projected[i].is_nullable = out_nullable;                                           \
+            projected[i].only_null = orig_boundaries[i].only_null;                             \
+            projected[i].contains_null = orig_boundaries[i].contains_null;                     \
+            bool lo_is_null = ext_null_lo[i] || lo_result_col->is_null_at(i);                  \
+            bool hi_is_null = ext_null_hi[i] || hi_result_col->is_null_at(i);                  \
+            if (lo_is_null || hi_is_null) {                                                    \
+                projected[i].null_in_projection = true;                                        \
+                ColumnValueRange<TYPE_##OUTPUT_PT> full_range("", out_nullable, out_precision, \
+                                                              out_scale);                      \
+                projected[i].boundary_cvr = std::move(full_range);                             \
+            } else {                                                                           \
+                Field lo_field = (*lo_result_col)[i];                                          \
+                Field hi_field = (*hi_result_col)[i];                                          \
+                OutputCppType lo_projected = lo_field.get<TYPE_##OUTPUT_PT>();                 \
+                OutputCppType hi_projected = hi_field.get<TYPE_##OUTPUT_PT>();                 \
+                ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision,        \
+                                                       out_scale);                             \
+                if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {              \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));    \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));      \
+                } else {                                                                       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));    \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));      \
+                }                                                                              \
+                projected[i].boundary_cvr = std::move(cvr);                                    \
+            }                                                                                  \
+        }                                                                                      \
+        break;                                                                                 \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+    _projection_cache[filter_id] = std::move(projected);
+    return &_projection_cache[filter_id];
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    for (const auto& pb : boundaries) {
+        if (_pruned_partition_ids.contains(pb.partition_id) ||
+            newly_pruned.contains(pb.partition_id)) {
+            continue;
+        }
+
+        // Skip partitions with NULL projection conservatively
+        if (pb.null_in_projection) {
+            continue;
+        }
+
+        // NULL handling:
+        //   A partition row whose key is NULL matches the RF iff `rf_contains_null`.
+        //   - only_null partition (rows are exclusively NULL): prunable iff !rf_contains_null.
+        //   - mixed (NULL + concrete values): if rf_contains_null, NULL rows alone
+        //     prevent pruning. Otherwise NULL rows can never match, so we ignore
+        //     the NULL portion and let the regular non-NULL intersection decide.
+        if (pb.only_null) {
+            if (!rf_contains_null) {
+                newly_pruned.insert(pb.partition_id);
+            }
+            continue;
+        }
+        if (pb.contains_null && rf_contains_null) {
+            continue;
+        }
+
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {
+                        // IN filter: build a fixed-value CVR from the HybridSet
+                        auto rf_cvr = CvrType::create_empty_column_value_range(
+                                pb.is_nullable, boundary_cvr.precision(), boundary_cvr.scale());
+                        auto* iter = hybrid_set->begin();
+                        while (iter->has_next()) {
+                            const void* value = iter->get_value();
+                            if (value) {
+                                if constexpr (std::is_same_v<CppType, StringRef>) {
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else {
+                                    static_cast<void>(rf_cvr.add_fixed_value(
+                                            *reinterpret_cast<const CppType*>(value)));
+                                }
+                            }
+                            iter->next();
+                        }
+                        auto boundary_copy = boundary_cvr;
+                        boundary_copy.intersection(rf_cvr);
+                        if (boundary_copy.is_empty_value_range()) {
+                            newly_pruned.insert(pb.partition_id);
+                        }
+                    } else if ((impl->node_type() == TExprNodeType::BINARY_PRED ||
+                                impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) &&
+                               impl->children().size() == 2 && impl->children()[1]->is_literal()) {
+                        // MinMax filter: binary pred with literal bound
+                        auto* literal = assert_cast<VLiteral*>(impl->children()[1].get());
+                        auto col_ptr = literal->get_column_ptr();
+                        auto data = col_ptr->get_data_at(0);
+                        CppType val {};
+                        if constexpr (std::is_same_v<CppType, StringRef>) {
+                            val = CppType(data.data, data.size);
+                        } else {
+                            val = *reinterpret_cast<const CppType*>(data.data);

Review Comment:
   `PrimitiveTypeTraits<TYPE_CHAR/VARCHAR/STRING>::CppType` is `std::string`, not `StringRef` (`be/src/core/data_type/primitive_type.h`). In this MIN/MAX branch the literal comes from `create_literal()`, which builds a `ColumnConst(ColumnString)` (`be/src/exec/runtime_filter/runtime_filter_consumer.cpp`, `be/src/exprs/vliteral.cpp`, `be/src/core/data_type/data_type.cpp`), so `get_data_at(0)` here returns a `StringRef` pointing at the character bytes. Reinterpreting `data.data` as `const CppType*` on the `else` branch is therefore invalid for `CHAR`/`VARCHAR`/`STRING` and can read garbage or crash.
   
   This path is reachable for string join keys because `RuntimeFilterConsumer` builds `BINARY_PRED` / `NULL_AWARE_BINARY_PRED` literals for `MIN_MAX` filters on those types. Please construct string-like values from the returned `StringRef` (for example `CppType(data.data, data.size)`) instead of dereferencing `data.data` as `CppType*`, and add a string `MIN_MAX` partition-pruning regression case so this stays covered.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4433010755

   # BE UT Coverage Report
   Increment line coverage `1.15% (8/698)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/e7a084166584e3ccdc5ae8b6bd5fc50d3c0cb832_e7a084166584e3ccdc5ae8b6bd5fc50d3c0cb832/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/e7a084166584e3ccdc5ae8b6bd5fc50d3c0cb832_e7a084166584e3ccdc5ae8b6bd5fc50d3c0cb832/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 53.53% (20636/38548) |
   | Line Coverage     | 37.14% (194951/524884) |
   | Region Coverage   | 33.43% (152450/456017) |
   | Branch Coverage   | 34.24% (66453/194070) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4449420993

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/572ab7793a850dc7d5ad35a35834f642fa194f82_572ab7793a850dc7d5ad35a35834f642fa194f82_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/572ab7793a850dc7d5ad35a35834f642fa194f82_572ab7793a850dc7d5ad35a35834f642fa194f82_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.03% (1850/2371) |
   | Line Coverage     | 64.70% (33231/51362) |
   | Region Coverage   | 65.27% (16457/25214) |
   | Branch Coverage   | 55.76% (8786/15758) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4448527492

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 169889 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 5bfeb4d44af5550f9edd89c84a3d05b18135d530, data reload: false
   
   query5	4309	624	471	471
   query6	440	199	179	179
   query7	4829	564	289	289
   query8	374	225	201	201
   query9	8824	4109	4084	4084
   query10	466	327	267	267
   query11	5918	2393	2281	2281
   query12	165	105	99	99
   query13	1256	604	398	398
   query14	6911	5395	5055	5055
   query14_1	4397	4409	4443	4409
   query15	201	198	178	178
   query16	1020	471	437	437
   query17	1134	711	623	623
   query18	2709	470	351	351
   query19	214	185	146	146
   query20	114	107	105	105
   query21	217	147	130	130
   query22	13705	13640	13393	13393
   query23	17119	16323	15983	15983
   query23_1	16139	16112	16181	16112
   query24	7536	1804	1391	1391
   query24_1	1389	1400	1381	1381
   query25	591	490	441	441
   query26	1289	327	167	167
   query27	2654	583	331	331
   query28	4435	1997	1983	1983
   query29	1102	633	519	519
   query30	312	251	202	202
   query31	1134	1074	952	952
   query32	107	64	62	62
   query33	529	344	265	265
   query34	1179	1173	657	657
   query35	769	771	689	689
   query36	1282	1308	1133	1133
   query37	154	110	93	93
   query38	3238	3152	3056	3056
   query39	939	932	921	921
   query39_1	873	876	862	862
   query40	239	138	126	126
   query41	71	69	70	69
   query42	98	99	95	95
   query43	329	333	292	292
   query44	
   query45	194	196	182	182
   query46	1107	1218	754	754
   query47	2297	2352	2154	2154
   query48	409	417	320	320
   query49	659	525	421	421
   query50	714	293	228	228
   query51	4334	4260	4158	4158
   query52	91	92	79	79
   query53	254	275	203	203
   query54	289	252	220	220
   query55	80	78	71	71
   query56	253	235	236	235
   query57	1393	1377	1278	1278
   query58	256	226	231	226
   query59	1602	1672	1442	1442
   query60	296	255	245	245
   query61	152	160	174	160
   query62	666	666	569	569
   query63	225	194	190	190
   query64	2522	811	669	669
   query65	
   query66	1760	492	370	370
   query67	30173	30048	29895	29895
   query68	
   query69	438	323	276	276
   query70	975	977	903	903
   query71	298	223	207	207
   query72	2895	2791	2531	2531
   query73	813	717	402	402
   query74	5077	4955	4748	4748
   query75	2760	2609	2299	2299
   query76	2308	1131	786	786
   query77	375	388	310	310
   query78	12930	13050	12339	12339
   query79	1433	1008	736	736
   query80	1313	515	407	407
   query81	523	280	243	243
   query82	653	158	118	118
   query83	355	272	246	246
   query84	267	137	115	115
   query85	931	511	428	428
   query86	425	294	270	270
   query87	3390	3371	3221	3221
   query88	3532	2662	2663	2662
   query89	426	371	327	327
   query90	1918	183	195	183
   query91	185	201	143	143
   query92	66	63	53	53
   query93	1120	987	588	588
   query94	716	353	315	315
   query95	689	475	348	348
   query96	992	781	348	348
   query97	2716	2642	2562	2562
   query98	217	203	204	203
   query99	1122	1094	969	969
   Total cold run time: 252700 ms
   Total hot run time: 169889 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3240798438


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/RuntimeFilterPartitionPruneClassifier.java:
##########
@@ -0,0 +1,281 @@
+// 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.glue.translator;
+
+import org.apache.doris.analysis.Expr;
+import org.apache.doris.analysis.FunctionCallExpr;
+import org.apache.doris.analysis.LiteralExpr;
+import org.apache.doris.analysis.SlotRef;
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.ListPartitionItem;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.PartitionInfo;
+import org.apache.doris.catalog.PartitionItem;
+import org.apache.doris.catalog.PartitionKey;
+import org.apache.doris.catalog.PartitionType;
+import org.apache.doris.catalog.RangePartitionItem;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
+import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.planner.OlapScanNode;
+import org.apache.doris.planner.PlanNode;
+import org.apache.doris.thrift.TTargetExprMonotonicity;
+
+import com.google.common.collect.Range;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Classifies whether one runtime-filter target can safely drive BE-side partition pruning.
+ *
+ * <p>This is the single FE gate for RF partition pruning. It intentionally reasons about the
+ * final legacy target expression that will be sent to BE, so late-added casts and partition
+ * expression domains cannot bypass the safety checks.
+ */
+final class RuntimeFilterPartitionPruneClassifier {
+    private RuntimeFilterPartitionPruneClassifier() {
+    }
+
+    static Classification classify(Expr targetExpr, Expression nereidsTargetExpr, PlanNode scanNode) {
+        if (!(scanNode instanceof OlapScanNode)) {
+            return Classification.unsupported("target scan is not an OlapScanNode");
+        }
+
+        OlapScanNode olapScanNode = (OlapScanNode) scanNode;
+        OlapTable table = olapScanNode.getOlapTable();
+        if (table == null) {
+            return Classification.unsupported("target scan has no OlapTable");
+        }
+
+        PartitionInfo partitionInfo = table.getPartitionInfo();
+        PartitionType partType = partitionInfo.getType();
+        if (partType != PartitionType.RANGE && partType != PartitionType.LIST) {
+            return Classification.unsupported("partition type is not RANGE or LIST");
+        }
+        if (hasUnsupportedAutomaticPartitionExpression(partitionInfo)) {
+            return Classification.unsupported("automatic partition expression boundary is not modeled");
+        }
+
+        if (targetExpr instanceof SlotRef) {
+            SlotRef slotRef = (SlotRef) targetExpr;
+            if (!isPartitionColumnSlot(slotRef, partitionInfo.getPartitionColumns())) {

Review Comment:
   This accepts any partition column for RANGE targets, but `OlapScanNode.addRangeBoundaries()` serializes a `TPartitionBoundary` only for `partColumns.get(0)`. For a table such as `PARTITION BY RANGE(k1, k2)`, a runtime filter on `k2` reaches this branch, `RuntimeFilter.toThrift()` emits per-partition monotonicity for the scan, and `OlapScanNode` still sends only the `k1` boundary. BE then looks up `slot_to_boundaries` for the `k2` slot in `RuntimeFilterPartitionPruner::prune_by_runtime_filters()` and returns `InternalError("target slot is not a partition boundary slot")`, so a valid join on the second RANGE partition column fails when RF partition pruning is enabled. This is distinct from the earlier first-column multi-range projection thread: even with the conservative first-column projection, non-first RANGE partition targets need to be rejected here (or FE must serialize correct boundaries for that slot) and covered by a regression test.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4495329806

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 168638 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 6be29ffaf962c85e6c243c68a1c26cf413cdb0b5, data reload: false
   
   query5	4341	632	490	490
   query6	428	209	190	190
   query7	4811	537	286	286
   query8	390	223	207	207
   query9	8809	4033	4034	4033
   query10	436	310	263	263
   query11	5987	2409	2248	2248
   query12	159	105	101	101
   query13	1270	651	394	394
   query14	6369	5300	4984	4984
   query14_1	4335	4291	4311	4291
   query15	197	195	172	172
   query16	989	450	420	420
   query17	1089	703	574	574
   query18	2428	480	352	352
   query19	198	179	141	141
   query20	113	106	102	102
   query21	211	136	121	121
   query22	13566	13541	13377	13377
   query23	17296	16444	16024	16024
   query23_1	16207	16223	16158	16158
   query24	7527	1735	1318	1318
   query24_1	1334	1310	1298	1298
   query25	577	451	427	427
   query26	1327	330	167	167
   query27	2667	559	340	340
   query28	4466	1975	1983	1975
   query29	1113	642	525	525
   query30	313	245	202	202
   query31	1115	1071	976	976
   query32	116	62	60	60
   query33	543	329	265	265
   query34	1174	1127	662	662
   query35	755	773	673	673
   query36	1330	1319	1126	1126
   query37	152	108	92	92
   query38	3206	3160	3043	3043
   query39	933	921	918	918
   query39_1	876	882	877	877
   query40	228	127	105	105
   query41	72	68	69	68
   query42	96	99	97	97
   query43	319	317	278	278
   query44	
   query45	196	193	182	182
   query46	1053	1250	748	748
   query47	2284	2343	2131	2131
   query48	386	419	290	290
   query49	651	479	374	374
   query50	978	343	249	249
   query51	4344	4282	4184	4184
   query52	90	90	77	77
   query53	248	266	195	195
   query54	288	247	217	217
   query55	81	77	71	71
   query56	246	250	239	239
   query57	1423	1372	1288	1288
   query58	252	230	233	230
   query59	1518	1587	1407	1407
   query60	304	264	282	264
   query61	158	154	154	154
   query62	657	621	562	562
   query63	234	184	185	184
   query64	2543	772	627	627
   query65	
   query66	1789	468	340	340
   query67	30094	29916	29928	29916
   query68	
   query69	428	298	275	275
   query70	964	946	938	938
   query71	296	221	211	211
   query72	2887	2688	2422	2422
   query73	867	766	426	426
   query74	5089	4929	4752	4752
   query75	2649	2561	2260	2260
   query76	2315	1113	750	750
   query77	354	373	281	281
   query78	12206	12269	11723	11723
   query79	1403	1026	786	786
   query80	1295	481	387	387
   query81	515	280	242	242
   query82	578	160	121	121
   query83	325	281	251	251
   query84	261	143	113	113
   query85	921	514	471	471
   query86	432	284	269	269
   query87	3382	3428	3154	3154
   query88	3509	2663	2643	2643
   query89	433	370	323	323
   query90	1922	178	179	178
   query91	188	169	140	140
   query92	61	64	54	54
   query93	1457	1513	807	807
   query94	734	345	303	303
   query95	688	398	445	398
   query96	1089	780	343	343
   query97	2668	2662	2519	2519
   query98	222	204	204	204
   query99	1105	1092	974	974
   Total cold run time: 251434 ms
   Total hot run time: 168638 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4543796226

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 171358 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 3d38dc5ab813ed88625762426d63d5324bfff4fe, data reload: false
   
   query5	4338	638	527	527
   query6	451	200	206	200
   query7	4851	566	295	295
   query8	371	222	206	206
   query9	8818	4183	4158	4158
   query10	456	306	276	276
   query11	5870	2415	2232	2232
   query12	158	105	100	100
   query13	1274	611	442	442
   query14	6482	5499	5165	5165
   query14_1	4492	4457	4437	4437
   query15	210	199	180	180
   query16	1041	469	421	421
   query17	1114	701	565	565
   query18	2534	479	342	342
   query19	205	180	137	137
   query20	111	106	104	104
   query21	213	136	119	119
   query22	13685	13597	13508	13508
   query23	17485	16548	16064	16064
   query23_1	16330	16338	16259	16259
   query24	7646	1794	1338	1338
   query24_1	1317	1305	1320	1305
   query25	554	442	390	390
   query26	1324	331	173	173
   query27	2652	591	333	333
   query28	4507	2029	2002	2002
   query29	1064	616	512	512
   query30	300	245	191	191
   query31	1126	1074	989	989
   query32	113	62	58	58
   query33	514	322	261	261
   query34	1186	1167	660	660
   query35	780	780	691	691
   query36	1414	1411	1245	1245
   query37	151	102	90	90
   query38	3239	3127	3086	3086
   query39	923	909	930	909
   query39_1	874	874	894	874
   query40	222	125	98	98
   query41	74	66	64	64
   query42	97	95	93	93
   query43	326	325	273	273
   query44	
   query45	201	187	177	177
   query46	1124	1157	699	699
   query47	2368	2435	2273	2273
   query48	425	402	307	307
   query49	638	471	362	362
   query50	977	357	251	251
   query51	4364	4302	4278	4278
   query52	87	91	79	79
   query53	255	275	193	193
   query54	268	221	213	213
   query55	82	82	70	70
   query56	238	238	211	211
   query57	1422	1404	1328	1328
   query58	260	228	218	218
   query59	1593	1701	1460	1460
   query60	301	270	234	234
   query61	189	187	191	187
   query62	704	656	593	593
   query63	234	192	197	192
   query64	2664	827	690	690
   query65	
   query66	1802	501	358	358
   query67	29736	29681	29585	29585
   query68	
   query69	441	312	284	284
   query70	971	948	901	901
   query71	312	234	217	217
   query72	3233	2924	2462	2462
   query73	845	753	430	430
   query74	5126	4999	4792	4792
   query75	2711	2594	2253	2253
   query76	2300	1160	786	786
   query77	369	383	271	271
   query78	12469	12621	11864	11864
   query79	1446	1085	792	792
   query80	586	508	401	401
   query81	454	282	251	251
   query82	558	153	120	120
   query83	353	281	255	255
   query84	259	144	117	117
   query85	874	561	486	486
   query86	371	316	275	275
   query87	3422	3351	3218	3218
   query88	3682	2749	2769	2749
   query89	430	377	330	330
   query90	1960	171	178	171
   query91	181	167	142	142
   query92	65	63	58	58
   query93	1469	1430	901	901
   query94	538	348	279	279
   query95	694	466	354	354
   query96	1140	837	348	348
   query97	2685	2676	2567	2567
   query98	225	214	210	210
   query99	1196	1140	978	978
   Total cold run time: 252443 ms
   Total hot run time: 171358 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4507327150

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "HappenLee (via GitHub)" <gi...@apache.org>.
HappenLee commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3308916954


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,875 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary must contain partition_id and slot_id");
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary references unknown slot_id={}, "
+                    "partition_id={}",
+                    slot_id, tb.partition_id);
+        }
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) {                                                           \
+            return Status::InternalError(                                                      \
+                    "Runtime filter partition boundary must be RANGE or LIST, "                \
+                    "partition_id={}, slot_id={}",                                             \
+                    tb.partition_id, slot_id);                                                 \
+        }                                                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        } else {
+            return Status::InternalError(
+                    "Runtime filter partition boundary has unsupported type, partition_id={}, "
+                    "slot_id={}, type={}",
+                    tb.partition_id, slot_id, slot_type->get_name());
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (expr->is_slot_ref()) {
+        return assert_cast<const VSlotRef*>(expr);
+    }
+    const VSlotRef* found = nullptr;
+    for (const auto& child : expr->children()) {
+        const VSlotRef* c = find_unique_slot_ref(child.get());
+        if (c) {
+            if (found) {
+                return nullptr; // multiple slot refs, can't handle
+            }
+            found = c;
+        }
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        if (direction == TTargetExprMonotonicity::NON_MONOTONIC) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received NON_MONOTONIC partition "
+                    "metadata, filter_id={}, partition_id={}",
+                    filter_id, partition_id);
+        }
+        if (!boundary_partition_ids.contains(partition_id)) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received monotonicity for a partition "
+                    "without boundary, filter_id={}, partition_id={}, slot_id={}",
+                    filter_id, partition_id, leaf_slot_id);
+        }
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        if (slot_ref->slot_id() != leaf_slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning SlotRef target mismatch, filter_id={}, "
+                    "target_slot_id={}, leaf_slot_id={}",
+                    filter_id, slot_ref->slot_id(), leaf_slot_id);
+        }
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            if (direction_it->second != TTargetExprMonotonicity::MONOTONIC_INCREASING) {
+                return Status::InternalError(
+                        "Runtime filter partition pruning SlotRef target must use increasing "
+                        "monotonicity, filter_id={}, partition_id={}, monotonicity={}",
+                        filter_id, boundary.partition_id, direction_it->second);
+            }
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    if (slot_type_it == _slot_data_types.end()) {
+        return Status::InternalError(
+                "Runtime filter partition pruning target slot has no boundary type, filter_id={}, "
+                "slot_id={}, target_expr={}",
+                filter_id, leaf_slot_id, target_expr->expr_name());
+    }
+
+    // LIST partitions require regrouping projected values per partition and
+    // should not be marked as expression-prunable by FE yet.
+    for (const auto& selected_boundary : selected_boundaries) {
+        bool is_list = false;
+        std::visit([&](const auto& cvr) { is_list = cvr.is_fixed_value_range(); },
+                   orig_boundaries[selected_boundary.first].boundary_cvr);
+        if (is_list) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning expression target does not support LIST "
+                    "partition boundaries, filter_id={}, partition_id={}, target_expr={}",
+                    filter_id, orig_boundaries[selected_boundary.first].partition_id,
+                    target_expr->expr_name());
+        }
+    }
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+
+    // Build input blocks for lo and hi
+    Block lo_block;
+    Block hi_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns. Open endpoints are not executed through target_expr; they stay
+    // open after projection and swap sides for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    if (!input_built) {
+        return Status::InternalError(
+                "Runtime filter partition pruning expression target has unsupported input type, "
+                "filter_id={}, input_type={}, target_expr={}",
+                filter_id, input_type->get_name(), target_expr->expr_name());
+    }
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        if (lo_result_id < 0) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning failed to project lower boundary, "
+                    "filter_id={}, target_expr={}",
+                    filter_id, target_expr->expr_name());
+        }
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        if (hi_result_id < 0) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning failed to project upper boundary, "
+                    "filter_id={}, target_expr={}",
+                    filter_id, target_expr->expr_name());
+        }
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));       \
+                }                                                                               \
+            } else {                                                                            \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));     \
+                }                                                                               \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));       \
+                }                                                                               \
+            }                                                                                   \
+            ParsedBoundary projected_boundary;                                                  \
+            projected_boundary.partition_id = orig_boundary.partition_id;                       \
+            projected_boundary.slot_id = leaf_slot_id;                                          \
+            projected_boundary.is_nullable = out_nullable;                                      \
+            projected_boundary.only_null = orig_boundary.only_null;                             \
+            projected_boundary.contains_null = orig_boundary.contains_null;                     \
+            projected_boundary.boundary_cvr = std::move(cvr);                                   \
+            projected.emplace_back(std::move(projected_boundary));                              \
+        }                                                                                       \
+        output_built = true;                                                                    \
+        break;                                                                                  \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    if (!output_built) {
+        return Status::InternalError(
+                "Runtime filter partition pruning expression target has unsupported output type, "
+                "filter_id={}, output_type={}, target_expr={}",
+                filter_id, target_expr->data_type()->get_name(), target_expr->expr_name());
+    }
+
+    return store_projected_boundaries(std::move(projected));
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    for (const auto& pb : boundaries) {
+        if (_pruned_partition_ids.contains(pb.partition_id) ||
+            newly_pruned.contains(pb.partition_id)) {
+            continue;
+        }
+
+        // NULL handling:
+        //   A partition row whose key is NULL matches the RF iff `rf_contains_null`.
+        //   - only_null partition (rows are exclusively NULL): prunable iff !rf_contains_null.
+        //   - mixed (NULL + concrete values): if rf_contains_null, NULL rows alone
+        //     prevent pruning. Otherwise NULL rows can never match, so we ignore
+        //     the NULL portion and let the regular non-NULL intersection decide.
+        if (pb.only_null) {
+            if (!rf_contains_null) {
+                newly_pruned.insert(pb.partition_id);
+            }
+            continue;
+        }
+        if (pb.contains_null && rf_contains_null) {
+            continue;
+        }
+
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {

Review Comment:
   For循环能不能放在外面,这个runtime filter的这个range只需要生成一次,后续的这个每一个partition的都复用这个生成的来计算,而不是每一个partition算一次



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4473676379

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4474545969

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 168106 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit ef2529ca3c35ebe1f89cb9fa4212f239fce43a97, data reload: false
   
   query5	4352	651	488	488
   query6	463	202	189	189
   query7	4932	585	300	300
   query8	373	223	209	209
   query9	8800	4054	4084	4054
   query10	456	313	267	267
   query11	5891	2380	2179	2179
   query12	161	106	103	103
   query13	1258	592	430	430
   query14	6427	5358	5035	5035
   query14_1	4339	4381	4375	4375
   query15	211	205	178	178
   query16	1029	464	485	464
   query17	1142	723	618	618
   query18	2674	502	360	360
   query19	212	189	151	151
   query20	113	111	107	107
   query21	231	149	126	126
   query22	13677	13580	13413	13413
   query23	17219	16378	15888	15888
   query23_1	16128	16072	15998	15998
   query24	7567	1796	1300	1300
   query24_1	1317	1310	1331	1310
   query25	578	488	406	406
   query26	1299	355	179	179
   query27	2616	555	336	336
   query28	4452	1988	1955	1955
   query29	1148	646	526	526
   query30	307	250	206	206
   query31	1127	1077	946	946
   query32	108	62	65	62
   query33	534	341	271	271
   query34	1183	1140	651	651
   query35	758	808	666	666
   query36	1354	1297	1118	1118
   query37	157	106	94	94
   query38	3211	3134	3030	3030
   query39	937	939	905	905
   query39_1	900	868	869	868
   query40	224	123	101	101
   query41	66	68	64	64
   query42	96	92	92	92
   query43	321	319	274	274
   query44	
   query45	196	182	177	177
   query46	1047	1165	712	712
   query47	2357	2349	2173	2173
   query48	402	414	291	291
   query49	635	457	367	367
   query50	1032	361	244	244
   query51	4294	4276	4374	4276
   query52	89	90	78	78
   query53	234	299	195	195
   query54	272	219	203	203
   query55	79	78	69	69
   query56	240	221	221	221
   query57	1392	1374	1310	1310
   query58	244	238	214	214
   query59	1569	1635	1430	1430
   query60	287	253	240	240
   query61	165	161	157	157
   query62	670	647	556	556
   query63	232	196	188	188
   query64	2534	772	602	602
   query65	
   query66	1742	469	341	341
   query67	30155	29907	29826	29826
   query68	
   query69	426	302	279	279
   query70	971	987	934	934
   query71	304	223	217	217
   query72	3067	2748	2426	2426
   query73	870	755	395	395
   query74	5091	4933	4732	4732
   query75	2651	2564	2220	2220
   query76	2331	1188	786	786
   query77	371	400	284	284
   query78	12185	12184	11610	11610
   query79	1386	1033	712	712
   query80	1140	479	381	381
   query81	506	279	243	243
   query82	578	160	125	125
   query83	359	274	248	248
   query84	268	143	118	118
   query85	939	533	473	473
   query86	415	290	274	274
   query87	3403	3418	3206	3206
   query88	3625	2676	2642	2642
   query89	446	375	322	322
   query90	1824	190	192	190
   query91	183	170	146	146
   query92	63	63	61	61
   query93	1486	1562	890	890
   query94	633	372	294	294
   query95	674	384	431	384
   query96	1045	818	319	319
   query97	2641	2679	2502	2502
   query98	211	207	220	207
   query99	1088	1111	980	980
   Total cold run time: 251635 ms
   Total hot run time: 168106 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4564615062

   # BE Regression && UT Coverage Report
   Increment line coverage `84.81% (653/770)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_2ede6c8cafb5ff16b7121d7edac1018fc2e592e0_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_2ede6c8cafb5ff16b7121d7edac1018fc2e592e0_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 72.20% (27496/38085) |
   | Line Coverage     | 55.56% (294085/529273) |
   | Region Coverage   | 52.38% (245813/469294) |
   | Branch Coverage   | 53.58% (106213/198248) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4560806209

   # BE UT Coverage Report
   Increment line coverage `66.47% (557/838)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/9af6867af36b4da0dec52406116ba2d158e2e2b9_9af6867af36b4da0dec52406116ba2d158e2e2b9/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/9af6867af36b4da0dec52406116ba2d158e2e2b9_9af6867af36b4da0dec52406116ba2d158e2e2b9/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 53.94% (20972/38883) |
   | Line Coverage     | 37.55% (199239/530647) |
   | Region Coverage   | 33.75% (156810/464641) |
   | Branch Coverage   | 34.75% (68597/197411) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4570295597

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4570404102

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4333359613

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3331437214


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,866 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            bool range_has_null = false;                                                       \
+            if (is_nullable && !tb.__isset.range_start) {                                      \
+                range_has_null = true;                                                         \
+            }                                                                                  \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                } else if (is_nullable) {                                                      \
+                    range_has_null = true;                                                     \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+            if (range_has_null) {                                                              \
+                boundary.contains_null = true;                                                 \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        DORIS_CHECK(parsed_ok);
+        _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static bool find_unique_slot_ref_impl(const VExpr* expr, const VSlotRef** found) {
+    if (!expr) {
+        return true;
+    }
+    if (expr->is_slot_ref()) {
+        const auto* slot = assert_cast<const VSlotRef*>(expr);
+        if (*found == nullptr) {
+            *found = slot;
+            return true;
+        }
+        if ((*found)->slot_id() != slot->slot_id()) {
+            return false;
+        }
+        DORIS_CHECK((*found)->column_id() == slot->column_id());
+        return true;
+    }
+    for (const auto& child : expr->children()) {
+        if (!find_unique_slot_ref_impl(child.get(), found)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    const VSlotRef* found = nullptr;
+    if (!find_unique_slot_ref_impl(expr, &found)) {
+        return nullptr;
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        DORIS_CHECK(direction != TTargetExprMonotonicity::NON_MONOTONIC);
+        DORIS_CHECK(boundary_partition_ids.contains(partition_id));
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        DORIS_CHECK(slot_ref->slot_id() == leaf_slot_id);
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            DORIS_CHECK(direction_it->second == TTargetExprMonotonicity::MONOTONIC_INCREASING);
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    DORIS_CHECK(slot_type_it != _slot_data_types.end());
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<bool> boundary_is_list(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    std::vector<size_t> list_result_begin(N, 0);
+    std::vector<size_t> list_result_end(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+    size_t list_row_count = 0;
+
+    // Build input blocks for RANGE endpoints and LIST values.
+    Block lo_block;
+    Block hi_block;
+    Block list_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns and LIST value columns. Unbounded RANGE endpoints are not executed
+    // through target_expr; they stay unbounded after projection and swap sides
+    // for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr list_inner_base = inner_type->create_column();                          \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        auto* list_inner = assert_cast<InCol*>(list_inner_base.get());                           \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        list_inner->reserve(N);                                                                  \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        auto list_nulls = ColumnUInt8::create();                                                 \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        list_nulls->reserve(N);                                                                  \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            boundary_is_list[i] = cvr->is_fixed_value_range();                                   \
+            if (boundary_is_list[i]) {                                                           \
+                list_result_begin[i] = list_row_count;                                           \
+                for (const auto& value : cvr->get_fixed_value_set()) {                           \
+                    list_inner->insert_value(value);                                             \
+                    list_nulls->get_data().push_back(0);                                         \
+                    ++list_row_count;                                                            \
+                }                                                                                \
+                list_result_end[i] = list_row_count;                                             \
+                continue;                                                                        \
+            }                                                                                    \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        add_dummy_columns(list_block, list_row_count);                                           \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            auto list_col =                                                                      \
+                    ColumnNullable::create(std::move(list_inner_base), std::move(list_nulls));   \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+            list_block.insert({std::move(list_col), input_type, "leaf_slot"});                   \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+            list_block.insert({std::move(list_inner_base), input_type, "leaf_slot"});            \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    DORIS_CHECK(input_built);
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    int list_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    ColumnPtr list_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        DORIS_CHECK(lo_result_id >= 0);
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        DORIS_CHECK(hi_result_id >= 0);
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    if (list_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &list_block, &list_result_id));
+        DORIS_CHECK(list_result_id >= 0);
+        list_result_col = list_block.get_by_position(list_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if (boundary_is_list[i]) {                                                          \
+                auto cvr = ColumnValueRange<TYPE_##OUTPUT_PT>::create_empty_column_value_range( \
+                        out_nullable, out_precision, out_scale);                                \
+                bool list_has_value = false;                                                    \
+                bool list_has_null = false;                                                     \
+                for (size_t row = list_result_begin[i]; row < list_result_end[i]; ++row) {      \
+                    if (list_result_col->is_null_at(row)) {                                     \
+                        list_has_null = true;                                                   \
+                        continue;                                                               \
+                    }                                                                           \
+                    auto projected_list_value = projected_value(list_result_col, row);          \
+                    static_cast<void>(cvr.add_fixed_value(projected_list_value));               \
+                    list_has_value = true;                                                      \
+                }                                                                               \
+                if (!list_has_value && !list_has_null) {                                        \
+                    continue;                                                                   \
+                }                                                                               \
+                ParsedBoundary projected_boundary;                                              \
+                projected_boundary.partition_id = orig_boundary.partition_id;                   \
+                projected_boundary.slot_id = leaf_slot_id;                                      \
+                projected_boundary.is_nullable = out_nullable;                                  \
+                projected_boundary.contains_null = list_has_null;                               \
+                projected_boundary.only_null = list_has_null && !list_has_value;                \
+                projected_boundary.boundary_cvr = std::move(cvr);                               \
+                projected.emplace_back(std::move(projected_boundary));                          \
+                continue;                                                                       \
+            }                                                                                   \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            /* Use closed projected finite bounds as a conservative envelope for */             \
+            /* non-strict monotonic expressions such as date ceil/floor/cast.    */             \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));       \
+                }                                                                               \
+            } else {                                                                            \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));     \
+                }                                                                               \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));       \
+                }                                                                               \
+            }                                                                                   \
+            ParsedBoundary projected_boundary;                                                  \
+            projected_boundary.partition_id = orig_boundary.partition_id;                       \
+            projected_boundary.slot_id = leaf_slot_id;                                          \
+            projected_boundary.is_nullable = out_nullable;                                      \
+            projected_boundary.only_null = orig_boundary.only_null;                             \
+            projected_boundary.contains_null = orig_boundary.contains_null;                     \
+            projected_boundary.boundary_cvr = std::move(cvr);                                   \
+            projected.emplace_back(std::move(projected_boundary));                              \
+        }                                                                                       \
+        output_built = true;                                                                    \
+        break;                                                                                  \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    DORIS_CHECK(output_built);
+
+    return store_projected_boundaries(std::move(projected));
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    std::optional<ColumnValueRangeType> rf_cvr;
+    if (!boundaries.empty()) {
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {
+                        auto typed_rf_cvr = CvrType::create_empty_column_value_range(
+                                boundaries.front().is_nullable, boundary_cvr.precision(),
+                                boundary_cvr.scale());
+                        auto* iter = hybrid_set->begin();
+                        while (iter->has_next()) {
+                            const void* value = iter->get_value();
+                            if (value) {
+                                if constexpr (std::is_same_v<CppType, String>) {
+                                    // String HybridSets store StringRef*, but
+                                    // ColumnValueRange<String>::CppType is
+                                    // std::string. Construct from the bytes.
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);

Review Comment:
   删了



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4591610750

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4494113441

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/fe71e9b789993be3f389260e0a34e371580cef7c_fe71e9b789993be3f389260e0a34e371580cef7c_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/fe71e9b789993be3f389260e0a34e371580cef7c_fe71e9b789993be3f389260e0a34e371580cef7c_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.06% (1854/2375) |
   | Line Coverage     | 64.51% (33327/51662) |
   | Region Coverage   | 65.22% (16527/25341) |
   | Branch Coverage   | 55.72% (8833/15852) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4504349159

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4426081842

   # BE Regression && UT Coverage Report
   Increment line coverage `43.45% (305/702)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_b29520eb8c2fd176695ee6c263c89bab10446d4a_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_b29520eb8c2fd176695ee6c263c89bab10446d4a_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.71% (27826/37749) |
   | Line Coverage     | 57.52% (301121/523489) |
   | Region Coverage   | 54.51% (251038/460506) |
   | Branch Coverage   | 55.80% (108707/194808) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4429604601

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 29564 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17646	3922	3937	3922
   q2	q3	10581	917	616	616
   q4	4668	460	354	354
   q5	7467	1334	1131	1131
   q6	196	181	146	146
   q7	929	950	766	766
   q8	9301	1439	1250	1250
   q9	5563	5369	5363	5363
   q10	6301	2060	1792	1792
   q11	486	272	264	264
   q12	637	420	299	299
   q13	18094	3323	2717	2717
   q14	307	281	269	269
   q15	q16	911	877	792	792
   q17	1181	1066	702	702
   q18	6487	5704	5622	5622
   q19	1614	1302	1084	1084
   q20	538	410	268	268
   q21	4875	2268	1903	1903
   q22	429	359	304	304
   Total cold run time: 98211 ms
   Total hot run time: 29564 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4282	4183	4198	4183
   q2	q3	4605	4733	4191	4191
   q4	2102	2176	1404	1404
   q5	4969	4981	5285	4981
   q6	202	174	138	138
   q7	2009	1912	1680	1680
   q8	3479	3199	3139	3139
   q9	8467	8369	8349	8349
   q10	4475	4563	4259	4259
   q11	598	432	424	424
   q12	715	776	530	530
   q13	3365	3607	2863	2863
   q14	316	312	272	272
   q15	q16	764	782	699	699
   q17	1377	1370	1337	1337
   q18	8320	7169	7087	7087
   q19	1164	1158	1152	1152
   q20	2233	2267	1978	1978
   q21	6196	5441	4942	4942
   q22	550	574	476	476
   Total cold run time: 60188 ms
   Total hot run time: 54084 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4344994621

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3322030867


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,857 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \

Review Comment:
   Nullable RANGE partition columns are still unsafe here. `allow_partition_column_nullable` is enabled by default for RANGE partitions, and `NullLiteral.compareLiteral()` sorts NULL below every concrete value, so a `VALUES LESS THAN (...)` first partition can contain NULL rows. FE will still classify a direct partition `SlotRef` as prunable and serialize this boundary with no `range_start`; BE builds only the non-NULL CVR and leaves `ParsedBoundary::contains_null` false. For a null-safe join such as `f.k <=> d.k` with an IN runtime filter whose build side contains only NULL, `rf_contains_null` is true but the first RANGE partition reaches the regular CVR intersection path, intersects an empty non-NULL RF value set, and gets pruned even though its NULL rows should match. Please record nullable MINVALUE-side RANGE boundaries as containing NULL (or conservatively skip RF partition pruning for nullable RANGE partition columns) and add a regression case for a nullable RANGE partition plu
 s `<=>` runtime filter.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3302353005


##########
be/test/exec/runtime_filter/runtime_filter_partition_pruner_test.cpp:
##########
@@ -0,0 +1,450 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "core/data_type/data_type_factory.hpp"
+#include "core/string_ref.h"
+#include "core/types.h"
+#include "exec/runtime_filter/utils.h"
+#include "exprs/create_predicate_function.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vdirect_in_predicate.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+class IdentityWrapperExpr final : public VExpr {
+public:
+    explicit IdentityWrapperExpr(DataTypePtr type) : VExpr(std::move(type), false) {}
+
+    const std::string& expr_name() const override { return _expr_name; }
+
+    Status execute_column_impl(VExprContext* context, const Block* block, const Selector* selector,
+                               size_t count, ColumnPtr& result_column) const override {
+        return get_child(0)->execute_column(context, block, selector, count, result_column);
+    }
+
+private:
+    std::string _expr_name = "identity_wrapper";
+};
+
+class RuntimeFilterPartitionPrunerTest : public testing::Test {
+protected:
+    static constexpr SlotId SLOT_ID = 1;
+
+    template <PrimitiveType PT>
+    using CppType = typename PrimitiveTypeTraits<PT>::CppType;
+
+    template <PrimitiveType PT>
+    TExprNode literal_node(const CppType<PT>& value, int precision = 0, int scale = 0) {
+        TExprNode node;
+        EXPECT_TRUE(create_texpr_literal_node<PT>(&value, &node, precision, scale).ok());
+        return node;
+    }
+
+    TExprNode null_node(PrimitiveType ptype, int precision = 0, int scale = 0) {
+        TExprNode node;
+        node.__set_node_type(TExprNodeType::NULL_LITERAL);
+        node.__set_type(create_type_desc(ptype, precision, scale));
+        return node;
+    }
+
+    template <PrimitiveType PT>
+    TPartitionBoundary list_boundary(int64_t partition_id, const std::vector<TExprNode>& values) {
+        TPartitionBoundary boundary;
+        boundary.__set_partition_id(partition_id);
+        boundary.__set_slot_id(SLOT_ID);
+        boundary.__set_list_values(values);
+        return boundary;
+    }
+
+    template <PrimitiveType PT>
+    TPartitionBoundary range_boundary(int64_t partition_id, const CppType<PT>& start,
+                                      const CppType<PT>& end, int precision = 0, int scale = 0) {
+        TPartitionBoundary boundary;
+        boundary.__set_partition_id(partition_id);
+        boundary.__set_slot_id(SLOT_ID);
+        boundary.__set_range_start(literal_node<PT>(start, precision, scale));
+        boundary.__set_range_end(literal_node<PT>(end, precision, scale));
+        return boundary;
+    }
+
+    template <PrimitiveType PT>
+    TPartitionBoundary lower_unbounded_range_boundary(int64_t partition_id, const CppType<PT>& end,
+                                                      int precision = 0, int scale = 0) {
+        TPartitionBoundary boundary;
+        boundary.__set_partition_id(partition_id);
+        boundary.__set_slot_id(SLOT_ID);
+        boundary.__set_range_end(literal_node<PT>(end, precision, scale));
+        return boundary;
+    }
+
+    template <PrimitiveType PT>
+    TPartitionBoundary upper_unbounded_range_boundary(int64_t partition_id,
+                                                      const CppType<PT>& start, int precision = 0,
+                                                      int scale = 0) {
+        TPartitionBoundary boundary;
+        boundary.__set_partition_id(partition_id);
+        boundary.__set_slot_id(SLOT_ID);
+        boundary.__set_range_start(literal_node<PT>(start, precision, scale));
+        return boundary;
+    }
+
+    SlotDescriptor slot_desc(PrimitiveType ptype, bool nullable, int precision = 0, int scale = 0) {
+        SlotDescriptor slot;
+        slot._id = SLOT_ID;
+        slot._type = DataTypeFactory::instance().create_data_type(
+                create_type_desc(ptype, precision, scale), nullable);
+        slot._col_name = "part_col";
+        return slot;
+    }
+
+    std::unique_ptr<ParsedPartitionBoundaries> parse_boundaries(
+            PrimitiveType ptype, const std::vector<TPartitionBoundary>& boundaries,
+            bool nullable = false, int precision = 0, int scale = 0) {
+        auto slot = slot_desc(ptype, nullable, precision, scale);
+        phmap::flat_hash_map<int, SlotDescriptor*> slots;
+        slots[SLOT_ID] = &slot;
+        auto parsed = std::make_unique<ParsedPartitionBoundaries>();
+        EXPECT_TRUE(parsed->parse(boundaries, slots).ok());
+        return parsed;
+    }
+
+    template <PrimitiveType PT>
+    std::shared_ptr<HybridSetBase> in_filter(const CppType<PT>& value, bool contain_null = false) {
+        std::shared_ptr<HybridSetBase> set(create_set(PT, contain_null));
+        if constexpr (std::is_same_v<CppType<PT>, String>) {
+            StringRef ref(value.data(), value.size());
+            set->insert(&ref);
+        } else {
+            set->insert(&value);
+        }
+        if (contain_null) {
+            set->insert(static_cast<const void*>(nullptr));
+        }
+        return set;
+    }
+
+    template <PrimitiveType PT>
+    VExprSPtr in_predicate(const CppType<PT>& value, bool contain_null = false) {
+        TExprNode node;
+        node.__set_type(create_type_desc(PrimitiveType::TYPE_BOOLEAN));
+        node.__set_node_type(contain_null ? TExprNodeType::NULL_AWARE_IN_PRED
+                                          : TExprNodeType::IN_PRED);
+        node.in_predicate.__set_is_not_in(false);
+        node.__set_opcode(TExprOpcode::FILTER_IN);
+        node.__set_is_nullable(false);
+        return VDirectInPredicate::create_shared(node, in_filter<PT>(value, contain_null));
+    }
+
+    template <PrimitiveType PT>
+    VExprSPtr minmax_predicate_le(const CppType<PT>& value, const DataTypePtr& type) {
+        VExprSPtr pred;
+        TExprNode node;
+        EXPECT_TRUE(create_vbin_predicate(type, TExprOpcode::LE, pred, &node, false).ok());
+        VExprSPtr lhs;
+        VExprSPtr rhs;
+        EXPECT_TRUE(create_literal(type, &value, lhs).ok());
+        EXPECT_TRUE(create_literal(type, &value, rhs).ok());
+        pred->add_child(lhs);
+        pred->add_child(rhs);
+        return pred;
+    }
+
+    VExprSPtr slot_ref_expr(const DataTypePtr& type) {
+        TSlotRef slot_ref_thrift;
+        slot_ref_thrift.__set_slot_id(SLOT_ID);
+        slot_ref_thrift.__set_tuple_id(0);
+        TExprNode node;
+        node.__set_type(create_type_desc(type->get_primitive_type(), type->get_precision(),
+                                         type->get_scale()));
+        node.__set_node_type(TExprNodeType::SLOT_REF);
+        node.__set_num_children(0);
+        node.__set_slot_ref(slot_ref_thrift);
+        auto slot_ref = VSlotRef::create_shared(node);
+        slot_ref->set_slot_id(SLOT_ID);
+        slot_ref->set_column_id(0);
+        return slot_ref;
+    }
+
+    VExprSPtr identity_wrapper_expr(const DataTypePtr& type) {
+        auto expr = std::make_shared<IdentityWrapperExpr>(type);
+        expr->add_child(slot_ref_expr(type));
+        return expr;
+    }
+
+    TRuntimeFilterDesc rf_desc_with_monotonicity(int filter_id, int scan_node_id,
+                                                 const std::vector<int64_t>& partition_ids) {
+        TRuntimeFilterDesc desc;
+        desc.__set_filter_id(filter_id);
+        std::vector<TPartitionTargetExprMonotonicity> entries;
+        entries.reserve(partition_ids.size());
+        for (int64_t partition_id : partition_ids) {
+            TPartitionTargetExprMonotonicity entry;
+            entry.__set_partition_id(partition_id);
+            entry.__set_monotonicity(TTargetExprMonotonicity::MONOTONIC_INCREASING);
+            entries.emplace_back(std::move(entry));
+        }
+        desc.__set_planId_to_partition_target_monotonicity({{scan_node_id, std::move(entries)}});
+        return desc;
+    }
+
+    template <PrimitiveType PT>
+    void assert_parse_and_prune_type(const CppType<PT>& keep_value, const CppType<PT>& prune_value,
+                                     int precision = 0, int scale = 0) {
+        auto boundaries = std::vector<TPartitionBoundary> {
+                list_boundary<PT>(1, {literal_node<PT>(keep_value, precision, scale)}),
+                list_boundary<PT>(2, {literal_node<PT>(prune_value, precision, scale)})};
+        auto parsed = parse_boundaries(PT, boundaries, false, precision, scale);
+        ASSERT_FALSE(parsed->empty());
+        ASSERT_EQ(parsed->total_partitions(), 2);
+        const auto& parsed_boundaries = parsed->slot_to_boundaries().at(SLOT_ID);
+
+        RuntimeFilterPartitionPruner in_pruner;
+        phmap::flat_hash_set<int64_t> in_pruned;
+        in_pruner._try_prune_by_single_rf(parsed_boundaries, in_predicate<PT>(keep_value),

Review Comment:
   This test calls `_try_prune_by_single_rf()` directly, but that method is declared `private` in `RuntimeFilterPartitionPruner` (`be/src/exec/runtime_filter/runtime_filter_partition_pruner.h`). The same private helper is also called later in this file, so `run-be-ut` should fail at compile time before these cases can execute. Please either exercise the public `prune_by_runtime_filters()` API here (as the later public-path test does), or explicitly expose/friend a test-only helper.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4597972782

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4560666855

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 171927 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 9af6867af36b4da0dec52406116ba2d158e2e2b9, data reload: false
   
   query5	4344	632	488	488
   query6	450	207	193	193
   query7	4836	569	337	337
   query8	372	222	207	207
   query9	8817	4225	4171	4171
   query10	472	321	264	264
   query11	5933	2457	2260	2260
   query12	157	102	104	102
   query13	1263	585	447	447
   query14	6427	5500	5170	5170
   query14_1	4535	4472	4530	4472
   query15	201	197	177	177
   query16	1049	458	442	442
   query17	1096	710	579	579
   query18	2724	481	348	348
   query19	208	180	142	142
   query20	112	107	109	107
   query21	218	140	118	118
   query22	13713	13674	13398	13398
   query23	17413	16538	16257	16257
   query23_1	16420	16359	16311	16311
   query24	7507	1795	1334	1334
   query24_1	1341	1309	1347	1309
   query25	555	453	402	402
   query26	1306	314	163	163
   query27	2657	579	336	336
   query28	4474	1996	2025	1996
   query29	1053	599	468	468
   query30	302	243	197	197
   query31	1120	1089	960	960
   query32	103	58	60	58
   query33	512	325	258	258
   query34	1204	1166	650	650
   query35	759	798	694	694
   query36	1427	1431	1218	1218
   query37	152	106	89	89
   query38	3210	3163	3034	3034
   query39	939	916	918	916
   query39_1	882	876	877	876
   query40	214	125	101	101
   query41	70	63	64	63
   query42	97	97	94	94
   query43	327	333	287	287
   query44	
   query45	203	189	183	183
   query46	1130	1232	751	751
   query47	2348	2413	2240	2240
   query48	416	406	297	297
   query49	633	471	367	367
   query50	1031	353	250	250
   query51	4360	4268	4178	4178
   query52	89	91	78	78
   query53	244	267	194	194
   query54	274	234	199	199
   query55	82	77	72	72
   query56	235	234	241	234
   query57	1460	1419	1337	1337
   query58	252	224	213	213
   query59	1632	1689	1475	1475
   query60	286	271	254	254
   query61	194	210	155	155
   query62	704	674	601	601
   query63	232	185	191	185
   query64	2543	804	634	634
   query65	
   query66	1758	485	349	349
   query67	29896	29759	29568	29568
   query68	
   query69	432	309	274	274
   query70	967	927	928	927
   query71	296	213	211	211
   query72	3043	2718	2454	2454
   query73	856	747	434	434
   query74	5168	4970	4838	4838
   query75	2704	2621	2266	2266
   query76	2331	1194	794	794
   query77	365	392	297	297
   query78	12383	12565	11909	11909
   query79	1229	1038	743	743
   query80	555	510	427	427
   query81	452	287	243	243
   query82	241	161	129	129
   query83	282	289	260	260
   query84	265	148	123	123
   query85	936	637	532	532
   query86	338	301	301	301
   query87	3405	3349	3202	3202
   query88	3701	2750	2745	2745
   query89	424	371	331	331
   query90	2146	185	181	181
   query91	180	170	137	137
   query92	62	60	60	60
   query93	1503	1496	896	896
   query94	552	336	309	309
   query95	718	382	361	361
   query96	1029	757	356	356
   query97	2692	2698	2583	2583
   query98	221	209	209	209
   query99	1217	1156	1040	1040
   Total cold run time: 252077 ms
   Total hot run time: 171927 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4563657682

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 32179 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 2ede6c8cafb5ff16b7121d7edac1018fc2e592e0, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17518	4158	4101	4101
   q2	q3	10666	1484	852	852
   q4	4679	482	350	350
   q5	7621	2352	2169	2169
   q6	252	180	144	144
   q7	964	788	643	643
   q8	9463	1713	1550	1550
   q9	5361	5060	5058	5058
   q10	6418	2199	1893	1893
   q11	441	280	248	248
   q12	633	435	298	298
   q13	18108	3452	2790	2790
   q14	270	257	242	242
   q15	q16	828	781	710	710
   q17	995	965	968	965
   q18	7007	5693	5595	5595
   q19	1173	1256	1260	1256
   q20	537	452	295	295
   q21	5869	2886	2701	2701
   q22	470	382	319	319
   Total cold run time: 99273 ms
   Total hot run time: 32179 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	5175	4985	4963	4963
   q2	q3	4909	5351	4744	4744
   q4	2192	2237	1440	1440
   q5	5188	4884	4812	4812
   q6	241	187	134	134
   q7	2063	1866	1641	1641
   q8	2652	2336	2321	2321
   q9	8182	7540	7564	7540
   q10	4763	4689	4238	4238
   q11	596	420	390	390
   q12	737	748	536	536
   q13	2980	3355	2815	2815
   q14	277	282	262	262
   q15	q16	689	707	630	630
   q17	1336	1296	1292	1292
   q18	7644	7042	6997	6997
   q19	1132	1127	1136	1127
   q20	2255	2234	1972	1972
   q21	5422	4709	4624	4624
   q22	529	461	441	441
   Total cold run time: 58962 ms
   Total hot run time: 52919 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4561189550

   # BE Regression && UT Coverage Report
   Increment line coverage `78.52% (658/838)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_9af6867af36b4da0dec52406116ba2d158e2e2b9_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_9af6867af36b4da0dec52406116ba2d158e2e2b9_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.86% (28128/38084) |
   | Line Coverage     | 57.80% (305957/529292) |
   | Region Coverage   | 54.72% (256664/469064) |
   | Branch Coverage   | 56.15% (111251/198137) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4561206733

   # FE Regression Coverage Report
   Increment line coverage `  59.10% (224/379)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_9af6867af36b4da0dec52406116ba2d158e2e2b9_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_9af6867af36b4da0dec52406116ba2d158e2e2b9_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4571144792

   # FE UT Coverage Report
   Increment line coverage `  31.37% (96/306)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4440179533

   # BE Regression && UT Coverage Report
   Increment line coverage `43.30% (304/702)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_2337ac55b6530b92cabafe14881c96951fab95c9_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_2337ac55b6530b92cabafe14881c96951fab95c9_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.69% (27793/37714) |
   | Line Coverage     | 57.59% (301348/523263) |
   | Region Coverage   | 54.74% (252002/460385) |
   | Branch Coverage   | 55.91% (108906/194791) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4439057855

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 29785 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 2337ac55b6530b92cabafe14881c96951fab95c9, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17641	4152	3973	3973
   q2	q3	10714	888	607	607
   q4	4666	461	346	346
   q5	7452	1360	1142	1142
   q6	191	169	142	142
   q7	917	973	761	761
   q8	9315	1391	1318	1318
   q9	5603	5398	5342	5342
   q10	6269	2100	1818	1818
   q11	481	267	262	262
   q12	626	422	292	292
   q13	18065	3323	2760	2760
   q14	294	288	263	263
   q15	q16	871	852	783	783
   q17	1026	1137	736	736
   q18	6392	5738	5543	5543
   q19	1159	1301	1129	1129
   q20	519	398	273	273
   q21	4090	2409	1968	1968
   q22	492	428	327	327
   Total cold run time: 96783 ms
   Total hot run time: 29785 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4814	4758	4988	4758
   q2	q3	4684	4816	4225	4225
   q4	2157	2220	1422	1422
   q5	4975	5069	5258	5069
   q6	199	171	141	141
   q7	2086	1781	1674	1674
   q8	3354	3105	3072	3072
   q9	8354	8391	8407	8391
   q10	4505	4470	4254	4254
   q11	612	426	380	380
   q12	685	757	516	516
   q13	3312	3692	2895	2895
   q14	298	302	276	276
   q15	q16	767	951	691	691
   q17	1359	1298	1295	1295
   q18	7931	7161	7199	7161
   q19	1184	1167	1183	1167
   q20	2300	2214	1923	1923
   q21	6160	5399	4822	4822
   q22	551	507	422	422
   Total cold run time: 60287 ms
   Total hot run time: 54554 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4440827738

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 29532 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit ca30c183fbab4ffab1f11b3d91b0561736c7c199, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17463	4060	3989	3989
   q2	q3	10553	874	614	614
   q4	4655	457	361	361
   q5	7454	1320	1128	1128
   q6	193	169	145	145
   q7	928	958	758	758
   q8	9331	1407	1277	1277
   q9	5588	5379	5297	5297
   q10	6257	2073	1798	1798
   q11	475	267	257	257
   q12	628	423	297	297
   q13	18117	3344	2721	2721
   q14	299	279	262	262
   q15	q16	909	862	788	788
   q17	1005	1024	786	786
   q18	6509	5691	5571	5571
   q19	1164	1243	1035	1035
   q20	534	396	265	265
   q21	4437	2266	1878	1878
   q22	422	355	305	305
   Total cold run time: 96921 ms
   Total hot run time: 29532 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4187	4110	4115	4110
   q2	q3	4633	4750	4161	4161
   q4	2105	2178	1391	1391
   q5	4977	4961	5306	4961
   q6	185	163	130	130
   q7	2020	1773	1727	1727
   q8	3564	3193	3206	3193
   q9	8493	8452	8378	8378
   q10	4443	4458	4234	4234
   q11	612	440	406	406
   q12	697	741	549	549
   q13	3213	3699	2935	2935
   q14	326	308	274	274
   q15	q16	755	787	702	702
   q17	1372	1327	1325	1325
   q18	8064	7274	7070	7070
   q19	1139	1147	1173	1147
   q20	2268	2220	1978	1978
   q21	6158	5371	4853	4853
   q22	556	525	433	433
   Total cold run time: 59767 ms
   Total hot run time: 53957 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4449156322

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "Copilot (via GitHub)" <gi...@apache.org>.
Copilot commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3099229731


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,293 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include "exprs/hybrid_set.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// Helper to extract a typed value from a ColumnPtr's first row.
+// Must be a template function so that `if constexpr` properly discards
+// the inapplicable branch during instantiation.
+template <PrimitiveType PT>
+static typename PrimitiveTypeTraits<PT>::CppType extract_value_from_column(const ColumnPtr& col) {
+    using CppType = typename PrimitiveTypeTraits<PT>::CppType;
+    auto data = col->get_data_at(0);
+    if constexpr (is_string_type(PT)) {
+        return CppType(data.data, data.size);
+    } else {
+        return *reinterpret_cast<const CppType*>(data.data);
+    }
+}
+
+void RuntimeFilterPartitionPruner::parse_boundaries(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            continue;
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            continue;
+        }
+        SlotDescriptor* slot = slot_it->second;
+        PrimitiveType ptype = slot->type()->get_primitive_type();
+        int precision = cast_set<int>(slot->type()->get_precision());
+        int scale = cast_set<int>(slot->type()->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) break;                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::pair<CppType, ColumnPtr> { \
+            VLiteral literal(node);                                                            \
+            auto col_ptr = literal.get_column_ptr();                                           \
+            auto val = extract_value_from_column<TYPE_##NAME>(col_ptr);                        \
+            return {val, col_ptr};                                                             \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(    \
+                    is_nullable, precision, scale);                                             \
+            for (const auto& node : tb.list_values) {                                          \
+                auto [val, col_ptr] = parse_texpr_node(node);                                  \
+                boundary.literal_columns.push_back(std::move(col_ptr));                        \
+                static_cast<void>(empty_cvr.add_fixed_value(val));                             \
+            }                                                                                  \
+            cvr.intersection(empty_cvr);                                                       \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto [val, col_ptr] = parse_texpr_node(tb.range_start);                        \
+                boundary.literal_columns.push_back(std::move(col_ptr));                        \
+                static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, val));                 \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto [val, col_ptr] = parse_texpr_node(tb.range_end);                          \
+                boundary.literal_columns.push_back(std::move(col_ptr));                        \
+                static_cast<void>(cvr.add_range(FILTER_LESS, val));                            \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _partition_column_slot_ids.insert(slot_id);
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_partition_column_slot_ids.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+}
+
+int64_t RuntimeFilterPartitionPruner::prune_by_runtime_filters(
+        const VExprContextSPtrs& conjuncts) {
+    if (_partition_column_slot_ids.empty()) {
+        return 0;
+    }
+
+    phmap::flat_hash_set<int64_t> newly_pruned;
+
+    for (const auto& conjunct_ctx : conjuncts) {
+        VExprSPtr root = conjunct_ctx->root();
+        if (!root->is_rf_wrapper()) {
+            continue;
+        }
+
+        VExprSPtr impl = root->get_impl();
+        if (!impl) {
+            continue;
+        }
+
+        // Determine the target slot_id from the RF's probe expression.
+        // For IN filter: impl is VDirectInPredicate, children()[0] is VSlotRef
+        // For MinMax filter: impl is VectorizedFnCall (binary pred), children()[0] is VSlotRef
+        if (impl->children().empty() || !impl->children()[0]->is_slot_ref()) {
+            continue;
+        }
+        auto* slot_ref = assert_cast<VSlotRef*>(impl->children()[0].get());
+        SlotId slot_id = slot_ref->slot_id();
+        if (!_partition_column_slot_ids.contains(slot_id)) {
+            continue;
+        }
+
+        auto boundaries_it = _slot_to_boundaries.find(slot_id);
+        if (boundaries_it == _slot_to_boundaries.end()) {
+            continue;
+        }
+
+        for (const auto& pb : boundaries_it->second) {
+            if (_pruned_partition_ids.contains(pb.partition_id) ||
+                newly_pruned.contains(pb.partition_id)) {
+                continue;
+            }

Review Comment:
   `prune_by_runtime_filters()` reads `_pruned_partition_ids` (e.g. `.contains()`) without holding `_prune_mutex`, but the same set can be written under the mutex at the end of the method and read concurrently by `is_partition_pruned()`/`pruned_partition_count()`. This is a data race because `phmap::flat_hash_set` is not thread-safe for concurrent read/write. Please guard *all* accesses to `_pruned_partition_ids` with the mutex (e.g. take a `shared_lock` for read checks or a `unique_lock` for the whole pruning pass).



##########
gensrc/thrift/PlanNodes.thrift:
##########
@@ -1386,6 +1418,18 @@ enum TMinMaxRuntimeFilterType {
   MIN_MAX = 4
 }
 
+// Monotonicity of a runtime filter's target expression, used by BE-side
+// partition pruning.  For Range partitions, only MONOTONIC_INCREASING or
+// MONOTONIC_DECREASING target expressions allow safe boundary transformation
+// and pruning.  List partitions can always be pruned regardless of monotonicity.
+// When the target expression is a plain SlotRef (identity), FE may omit this
+// field; BE treats an absent value as NON_MONOTONIC (conservative).

Review Comment:
   The comments about `target_expr_monotonicity` conflict: the enum comment says FE may omit the field for a SlotRef and BE treats it as NON_MONOTONIC, but the `TRuntimeFilterDesc` comment later says FE should set MONOTONIC_INCREASING for a SlotRef. Please reconcile these docstrings so they reflect the intended behavior (and don’t encourage omitting a field that disables pruning).
   



##########
fe/fe-core/src/main/java/org/apache/doris/planner/RuntimeFilter.java:
##########
@@ -280,6 +285,36 @@ public TRuntimeFilterDesc toThrift() {
         } else if (builderNode instanceof SetOperationNode) {
             tFilter.setNullAware(true);
         }
+
+        // Set target_expr_monotonicity for partition column identity path.
+        // When the RF target is a direct SlotRef on a partition column, the
+        // mapping from column value to partition is monotonic (identity), so
+        // the BE can use the RF values to prune partitions.
+        for (RuntimeFilterTarget target : targets) {
+            if (target.expr instanceof SlotRef && target.node instanceof OlapScanNode) {
+                SlotRef slotRef = (SlotRef) target.expr;
+                Column col = slotRef.getColumn();
+                if (col != null) {
+                    OlapScanNode scanNode = (OlapScanNode) target.node;
+                    OlapTable table = scanNode.getOlapTable();
+                    PartitionType partType = table.getPartitionInfo().getType();
+                    if (partType == PartitionType.RANGE || partType == PartitionType.LIST) {
+                        for (Column partCol : table.getPartitionInfo().getPartitionColumns()) {
+                            if (partCol.getName().equalsIgnoreCase(col.getName())) {
+                                tFilter.setTargetExprMonotonicity(
+                                        TTargetExprMonotonicity.MONOTONIC_INCREASING);
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+            // Only check the first target; if there are multiple targets on
+            // different tables, monotonicity may differ. We conservatively
+            // set it based on the first target only.
+            break;
+        }

Review Comment:
   The loop that sets `target_expr_monotonicity` always `break`s after the first target, even when the first target is not an `OlapScanNode` / partition-column `SlotRef`. That means the field may stay unset even if a later target is eligible, unnecessarily disabling partition pruning. Consider scanning targets until an eligible partition-column SlotRef is found (or explicitly selecting the intended target deterministically) before deciding to stop.



##########
regression-test/conf/regression-conf.groovy:
##########
@@ -24,8 +24,8 @@ defaultDb = "regression_test"
 // init cmd like: select @@session.tx_read_only
 // at each time we connect.
 // add allowLoadLocalInfile so that the jdbc can execute mysql load data from client.
-jdbcUrl = "jdbc:mysql://127.0.0.1:9030/?useLocalSessionState=true&allowLoadLocalInfile=true&zeroDateTimeBehavior=round"
-targetJdbcUrl = "jdbc:mysql://127.0.0.1:9030/?useLocalSessionState=true&allowLoadLocalInfile=true&zeroDateTimeBehavior=round"
+jdbcUrl = "jdbc:mysql://127.0.0.1:9333/?useLocalSessionState=true&allowLoadLocalInfile=true&zeroDateTimeBehavior=round"
+targetJdbcUrl = "jdbc:mysql://127.0.0.1:9333/?useLocalSessionState=true&allowLoadLocalInfile=true&zeroDateTimeBehavior=round"

Review Comment:
   `jdbcUrl` / `targetJdbcUrl` were changed from 9030 to 9333, but other related settings in this same config (e.g. `ccrDownstreamUrl`, `doris_port`, and `ccrDownstreamFeThriftAddress`) still use 9030. This makes the regression test config internally inconsistent and will likely break CI environments that expect default ports. Please revert these port changes or make them configurable (e.g. via env/property) while keeping repo defaults consistent.
   



##########
gensrc/thrift/PlanNodes.thrift:
##########
@@ -913,6 +938,13 @@ struct TOlapScanNode {
   24: optional bool enable_mor_value_predicate_pushdown
   // Read MOR table as DUP table: skip merge, skip delete sign
   25: optional bool read_mor_as_dup
+  // Partition-id → tablet-id list mapping; used together with partition_boundaries
+  // for BE-side runtime filter partition pruning.
+  26: optional map<Types.TPartitionId, list<Types.TTabletId>> partiton_to_tablets
+  // Partition boundary descriptors for BE-side runtime filter partition pruning.
+  // Only partitions that are candidates for pruning are included; partitions FE
+  // does not want pruned (e.g. default catch-all) are omitted from this list.
+  27: optional list<TPartitionBoundary> partition_boundaries

Review Comment:
   The field name `partiton_to_tablets` appears to be a typo (missing 'i' in 'partition'). Even though Thrift field names don't affect wire compatibility, this typo becomes part of generated code APIs and will be hard to undo later. Please rename it to `partition_to_tablets` before merging (keeping field id 26 unchanged).



##########
regression-test/conf/regression-conf.groovy:
##########
@@ -35,7 +35,7 @@ syncerAddress = "127.0.0.1:9190"
 feSyncerUser = "root"
 feSyncerPassword = ""
 
-feHttpAddress = "127.0.0.1:8030"
+feHttpAddress = "127.0.0.1:8333"

Review Comment:
   `feHttpAddress` was changed from 8030 to 8333 while other regression-test defaults remain on standard ports. This will cause profile-related and HTTP-based regression tests to fail unless the environment is customized. Please keep the committed default `feHttpAddress` aligned with the standard FE HTTP port (or make it configurable without changing the default).
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4277390461

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4277647155

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3108286524


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,374 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <utility>
+
+#include "exprs/hybrid_set.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// Helper to extract a typed value from a ColumnPtr's first row.
+// Must be a template function so that `if constexpr` properly discards
+// the inapplicable branch during instantiation.
+template <PrimitiveType PT>
+static typename PrimitiveTypeTraits<PT>::CppType extract_value_from_column(const ColumnPtr& col) {
+    using CppType = typename PrimitiveTypeTraits<PT>::CppType;
+    auto data = col->get_data_at(0);
+    if constexpr (is_string_type(PT)) {
+        return CppType(data.data, data.size);
+    } else {
+        return *reinterpret_cast<const CppType*>(data.data);
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+void RuntimeFilterPartitionPruner::parse_boundaries(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            continue;
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            continue;
+        }
+        SlotDescriptor* slot = slot_it->second;
+        PrimitiveType ptype = slot->type()->get_primitive_type();
+        int precision = cast_set<int>(slot->type()->get_precision());
+        int scale = cast_set<int>(slot->type()->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) break;                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node =                                                                \
+                [&](const TExprNode& node) -> std::optional<std::pair<CppType, ColumnPtr>> {   \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            VLiteral literal(node);                                                            \
+            auto col_ptr = literal.get_column_ptr();                                           \
+            auto val = extract_value_from_column<TYPE_##NAME>(col_ptr);                        \
+            return std::make_optional(std::pair<CppType, ColumnPtr> {val, col_ptr});           \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                auto& [val, col_ptr] = *parsed;                                                \
+                boundary.literal_columns.push_back(std::move(col_ptr));                        \
+                static_cast<void>(empty_cvr.add_fixed_value(val));                             \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    auto& [val, col_ptr] = *parsed;                                            \
+                    boundary.literal_columns.push_back(std::move(col_ptr));                    \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, val));             \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    auto& [val, col_ptr] = *parsed;                                            \
+                    boundary.literal_columns.push_back(std::move(col_ptr));                    \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, val));                           \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _partition_column_slot_ids.insert(slot_id);
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_partition_column_slot_ids.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const VExprSPtr& impl, SlotId slot_id, phmap::flat_hash_set<int64_t>& newly_pruned) {
+    auto boundaries_it = _slot_to_boundaries.find(slot_id);
+    if (boundaries_it == _slot_to_boundaries.end()) {
+        return;
+    }
+
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. This is encoded by the set's
+    // contain_null() (see FilterBase::contain_null = _null_aware && _contain_null).
+    // For BINARY_PRED predicates (=, <, >, <=, >=) NULL never compares true,
+    // so they never match a NULL probe row -- treat as not containing NULL.
+    auto hybrid_set_for_null = impl->get_set_func();

Review Comment:
   `rf_contains_null` is only derived from `get_set_func()`, which means it works for direct-IN filters but stays false for the other null-aware RF implementations. In particular, `BLOOM_PRED` carries its NULL bit on `get_bloom_filter_func()->contain_null()`, and null-aware min/max filters arrive as `NULL_AWARE_BINARY_PRED` rather than `BINARY_PRED`. With the current logic, a LIST partition like `VALUES IN ((NULL))` is pruned by the `pb.only_null` branch for a null-safe `<=>` join as soon as the RF is realized as bloom or null-aware min/max, even though NULL probe rows should match and the partition must be kept.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4288723552

   # BE UT Coverage Report
   Increment line coverage `0.91% (6/658)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/6f985006ec32cea727f90e22b3cd688c1a0626e1_6f985006ec32cea727f90e22b3cd688c1a0626e1/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/6f985006ec32cea727f90e22b3cd688c1a0626e1_6f985006ec32cea727f90e22b3cd688c1a0626e1/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 53.26% (20358/38221) |
   | Line Coverage     | 36.80% (191822/521235) |
   | Region Coverage   | 32.99% (149152/452110) |
   | Branch Coverage   | 33.93% (65243/192281) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4589224686

   PR approved by at least one committer and no changes requested.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4589485613

   # FE UT Coverage Report
   Increment line coverage `  31.37% (96/306)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_586640a94f47548a444d49ee53d5474cf04e3ae0/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_586640a94f47548a444d49ee53d5474cf04e3ae0/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4593403730

   # BE Regression && UT Coverage Report
   Increment line coverage `85.48% (677/792)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_b82194ce0fb5ac216aeed034490645f04c19f4e5_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_b82194ce0fb5ac216aeed034490645f04c19f4e5_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.89% (28188/38147) |
   | Line Coverage     | 57.90% (306849/529922) |
   | Region Coverage   | 54.90% (258055/470057) |
   | Branch Coverage   | 56.21% (111568/198490) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4591829542

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/b82194ce0fb5ac216aeed034490645f04c19f4e5_b82194ce0fb5ac216aeed034490645f04c19f4e5_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/b82194ce0fb5ac216aeed034490645f04c19f4e5_b82194ce0fb5ac216aeed034490645f04c19f4e5_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.34% (1906/2433) |
   | Line Coverage     | 64.77% (33982/52468) |
   | Region Coverage   | 65.28% (17516/26833) |
   | Branch Coverage   | 53.98% (9300/17230) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4590327122

   # BE Regression && UT Coverage Report
   Increment line coverage `85.48% (677/792)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_586640a94f47548a444d49ee53d5474cf04e3ae0_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_586640a94f47548a444d49ee53d5474cf04e3ae0_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.74% (28127/38142) |
   | Line Coverage     | 57.74% (305911/529827) |
   | Region Coverage   | 54.55% (256290/469837) |
   | Branch Coverage   | 56.01% (111148/198431) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3331436714


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,866 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            bool range_has_null = false;                                                       \
+            if (is_nullable && !tb.__isset.range_start) {                                      \
+                range_has_null = true;                                                         \
+            }                                                                                  \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                } else if (is_nullable) {                                                      \
+                    range_has_null = true;                                                     \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+            if (range_has_null) {                                                              \
+                boundary.contains_null = true;                                                 \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        DORIS_CHECK(parsed_ok);
+        _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static bool find_unique_slot_ref_impl(const VExpr* expr, const VSlotRef** found) {
+    if (!expr) {

Review Comment:
   改成check了



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4589177856

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4589959994

   # BE UT Coverage Report
   Increment line coverage `71.09% (563/792)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/586640a94f47548a444d49ee53d5474cf04e3ae0_586640a94f47548a444d49ee53d5474cf04e3ae0/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/586640a94f47548a444d49ee53d5474cf04e3ae0_586640a94f47548a444d49ee53d5474cf04e3ae0/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 54.05% (21051/38950) |
   | Line Coverage     | 37.65% (199993/531209) |
   | Region Coverage   | 33.80% (157295/465432) |
   | Branch Coverage   | 34.80% (68799/197712) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4440924332

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 169090 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit ca30c183fbab4ffab1f11b3d91b0561736c7c199, data reload: false
   
   query5	4299	626	472	472
   query6	427	203	179	179
   query7	4819	572	281	281
   query8	361	230	202	202
   query9	8799	4043	4048	4043
   query10	459	317	260	260
   query11	5963	2328	2214	2214
   query12	151	102	100	100
   query13	1253	612	430	430
   query14	7265	5302	4977	4977
   query14_1	4288	4278	4299	4278
   query15	211	200	175	175
   query16	1027	469	474	469
   query17	1312	765	638	638
   query18	2725	499	363	363
   query19	319	191	152	152
   query20	114	107	113	107
   query21	217	140	130	130
   query22	13680	13991	14408	13991
   query23	17372	16423	16078	16078
   query23_1	16207	16199	16218	16199
   query24	7723	1803	1411	1411
   query24_1	1429	1360	1392	1360
   query25	564	476	394	394
   query26	1265	299	158	158
   query27	2608	565	352	352
   query28	4319	1946	1946	1946
   query29	1080	611	521	521
   query30	304	236	193	193
   query31	1104	1050	936	936
   query32	105	57	54	54
   query33	504	320	255	255
   query34	1131	1102	640	640
   query35	735	755	654	654
   query36	1282	1282	1110	1110
   query37	144	104	88	88
   query38	3176	3085	3041	3041
   query39	939	916	874	874
   query39_1	876	870	858	858
   query40	236	127	112	112
   query41	63	59	59	59
   query42	90	90	90	90
   query43	307	314	262	262
   query44	
   query45	198	183	175	175
   query46	1057	1218	707	707
   query47	2309	2204	2170	2170
   query48	406	419	295	295
   query49	624	504	391	391
   query50	700	278	216	216
   query51	4240	4225	4216	4216
   query52	85	85	74	74
   query53	228	272	184	184
   query54	254	214	195	195
   query55	76	73	67	67
   query56	236	220	220	220
   query57	1380	1366	1296	1296
   query58	237	216	213	213
   query59	1511	1576	1387	1387
   query60	317	255	243	243
   query61	160	160	156	156
   query62	671	616	565	565
   query63	229	182	190	182
   query64	2498	787	660	660
   query65	
   query66	1750	482	353	353
   query67	30151	29910	29223	29223
   query68	
   query69	412	297	275	275
   query70	993	985	945	945
   query71	306	232	218	218
   query72	3149	2899	2620	2620
   query73	868	788	448	448
   query74	5071	4893	4712	4712
   query75	2749	2628	2291	2291
   query76	2260	1132	759	759
   query77	354	381	281	281
   query78	12868	12904	12261	12261
   query79	1432	944	747	747
   query80	1343	493	403	403
   query81	522	275	243	243
   query82	619	161	123	123
   query83	318	270	244	244
   query84	256	140	111	111
   query85	913	475	416	416
   query86	418	293	275	275
   query87	3413	3286	3128	3128
   query88	3568	2685	2650	2650
   query89	415	367	325	325
   query90	1929	173	179	173
   query91	179	171	137	137
   query92	63	60	57	57
   query93	1137	935	550	550
   query94	719	321	295	295
   query95	667	368	349	349
   query96	1010	757	333	333
   query97	2652	2678	2532	2532
   query98	214	208	207	207
   query99	1106	1101	970	970
   Total cold run time: 252917 ms
   Total hot run time: 169090 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4448034358

   # FE UT Coverage Report
   Increment line coverage `  28.38% (84/296)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_5bfeb4d44af5550f9edd89c84a3d05b18135d530/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_5bfeb4d44af5550f9edd89c84a3d05b18135d530/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4450130663

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/c090ab6440346db9f2c052585ec7b6dc593d0df3_c090ab6440346db9f2c052585ec7b6dc593d0df3_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/c090ab6440346db9f2c052585ec7b6dc593d0df3_c090ab6440346db9f2c052585ec7b6dc593d0df3_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.03% (1850/2371) |
   | Line Coverage     | 64.69% (33224/51362) |
   | Region Coverage   | 65.24% (16449/25214) |
   | Branch Coverage   | 55.72% (8780/15758) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4474503045

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 31285 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit ef2529ca3c35ebe1f89cb9fa4212f239fce43a97, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17680	3996	3868	3868
   q2	q3	10949	1453	859	859
   q4	4839	475	349	349
   q5	10672	2271	2116	2116
   q6	398	180	139	139
   q7	999	767	640	640
   q8	9586	1698	1684	1684
   q9	6893	4962	4942	4942
   q10	6388	2125	1840	1840
   q11	429	280	245	245
   q12	645	423	293	293
   q13	18123	3390	2754	2754
   q14	267	247	233	233
   q15	q16	820	775	709	709
   q17	1009	1029	1004	1004
   q18	6904	5727	5619	5619
   q19	1185	1229	1094	1094
   q20	517	416	280	280
   q21	5543	2686	2313	2313
   q22	454	373	304	304
   Total cold run time: 104300 ms
   Total hot run time: 31285 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4210	4140	4228	4140
   q2	q3	4512	4910	4304	4304
   q4	2105	2224	1406	1406
   q5	4445	4331	5112	4331
   q6	253	201	148	148
   q7	1957	1782	1556	1556
   q8	2509	2223	2231	2223
   q9	7718	7714	7789	7714
   q10	4792	4739	4090	4090
   q11	567	433	382	382
   q12	772	731	518	518
   q13	3322	3685	3067	3067
   q14	294	300	293	293
   q15	q16	735	726	655	655
   q17	1375	1400	1328	1328
   q18	8077	7109	6764	6764
   q19	1177	1085	1087	1085
   q20	2235	2242	1947	1947
   q21	5339	4636	4500	4500
   q22	538	470	424	424
   Total cold run time: 56932 ms
   Total hot run time: 50875 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4450215622

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4268412666

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4277606248

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4563844786

   # BE UT Coverage Report
   Increment line coverage `71.69% (552/770)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/2ede6c8cafb5ff16b7121d7edac1018fc2e592e0_2ede6c8cafb5ff16b7121d7edac1018fc2e592e0/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/2ede6c8cafb5ff16b7121d7edac1018fc2e592e0_2ede6c8cafb5ff16b7121d7edac1018fc2e592e0/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 54.02% (21007/38884) |
   | Line Coverage     | 37.66% (199814/530627) |
   | Region Coverage   | 33.84% (157312/464877) |
   | Branch Coverage   | 34.83% (68805/197522) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "HappenLee (via GitHub)" <gi...@apache.org>.
HappenLee commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3330634932


##########
be/src/exec/scan/scanner_scheduler.cpp:
##########
@@ -202,6 +202,9 @@ void ScannerScheduler::_scanner_scan(std::shared_ptr<ScannerContext> ctx,
                              << rf_status.to_string();
             }
 
+            // After processing late RFs, check if this scanner's partition was pruned.
+            if (!eos && scanner->check_partition_pruned()) { eos = true; }

Review Comment:
   这个是不是应该先于199行被调用呢?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4494083348

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4540604665

   # FE Regression Coverage Report
   Increment line coverage `  67.33% (204/303)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_f779760a4db0a4e1f4605c63a8a9bc21f51af126_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_f779760a4db0a4e1f4605c63a8a9bc21f51af126_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4540917267

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 170883 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit f779760a4db0a4e1f4605c63a8a9bc21f51af126, data reload: false
   
   query5	4323	656	502	502
   query6	448	200	189	189
   query7	4864	570	312	312
   query8	386	211	206	206
   query9	8828	4168	4147	4147
   query10	456	311	260	260
   query11	5937	2558	2250	2250
   query12	174	108	106	106
   query13	1271	624	457	457
   query14	6559	5565	5232	5232
   query14_1	4533	4504	4503	4503
   query15	207	201	177	177
   query16	1051	483	414	414
   query17	1138	734	594	594
   query18	2727	479	354	354
   query19	221	189	153	153
   query20	120	114	108	108
   query21	214	140	125	125
   query22	13685	13713	13402	13402
   query23	17360	16682	16228	16228
   query23_1	16465	16334	16474	16334
   query24	7554	1787	1338	1338
   query24_1	1382	1331	1356	1331
   query25	591	472	415	415
   query26	1325	316	173	173
   query27	2621	559	350	350
   query28	4451	2010	2036	2010
   query29	1117	639	508	508
   query30	310	238	209	209
   query31	1129	1091	980	980
   query32	110	67	61	61
   query33	550	342	266	266
   query34	1199	1170	660	660
   query35	800	781	660	660
   query36	1413	1427	1235	1235
   query37	151	100	89	89
   query38	3229	3164	3087	3087
   query39	927	962	892	892
   query39_1	874	884	877	877
   query40	230	122	99	99
   query41	65	63	63	63
   query42	94	92	94	92
   query43	319	326	286	286
   query44	
   query45	197	190	175	175
   query46	1112	1203	755	755
   query47	2439	2385	2231	2231
   query48	399	413	305	305
   query49	620	476	348	348
   query50	1042	367	254	254
   query51	4344	4320	4223	4223
   query52	89	88	79	79
   query53	242	275	193	193
   query54	291	223	195	195
   query55	78	74	69	69
   query56	224	228	258	228
   query57	1432	1415	1339	1339
   query58	242	219	216	216
   query59	1641	1719	1534	1534
   query60	291	252	225	225
   query61	161	157	159	157
   query62	688	652	591	591
   query63	233	187	194	187
   query64	2495	786	610	610
   query65	
   query66	1750	478	337	337
   query67	29882	29864	29727	29727
   query68	
   query69	440	308	271	271
   query70	986	942	942	942
   query71	299	219	211	211
   query72	2939	2778	2295	2295
   query73	882	748	442	442
   query74	5133	4974	4790	4790
   query75	2694	2589	2259	2259
   query76	2344	1153	759	759
   query77	343	363	286	286
   query78	12603	12527	11842	11842
   query79	1462	1080	738	738
   query80	1298	468	387	387
   query81	522	282	243	243
   query82	638	166	120	120
   query83	349	276	246	246
   query84	273	144	117	117
   query85	945	546	447	447
   query86	424	309	294	294
   query87	3403	3380	3261	3261
   query88	3657	2770	2757	2757
   query89	433	382	338	338
   query90	1958	187	185	185
   query91	182	168	141	141
   query92	62	65	55	55
   query93	1558	1556	799	799
   query94	735	334	296	296
   query95	682	466	353	353
   query96	1028	760	340	340
   query97	2721	2724	2632	2632
   query98	219	211	220	211
   query99	1187	1147	1034	1034
   Total cold run time: 254074 ms
   Total hot run time: 170883 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4422754922

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 170147 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit b29520eb8c2fd176695ee6c263c89bab10446d4a, data reload: false
   
   query5	4342	648	493	493
   query6	450	198	184	184
   query7	4906	607	300	300
   query8	366	221	204	204
   query9	8797	4064	4088	4064
   query10	449	331	270	270
   query11	6135	2414	2240	2240
   query12	167	111	103	103
   query13	1269	605	421	421
   query14	7355	5364	5051	5051
   query14_1	4327	4321	4332	4321
   query15	209	208	188	188
   query16	1017	475	457	457
   query17	1295	784	624	624
   query18	2716	515	368	368
   query19	317	196	149	149
   query20	115	108	106	106
   query21	220	146	124	124
   query22	13837	14051	14629	14051
   query23	17507	16536	16107	16107
   query23_1	16289	16185	16299	16185
   query24	7730	1892	1396	1396
   query24_1	1383	1337	1363	1337
   query25	557	466	393	393
   query26	1299	315	162	162
   query27	2550	630	317	317
   query28	4306	1988	1974	1974
   query29	1057	605	491	491
   query30	307	236	195	195
   query31	1111	1062	951	951
   query32	108	58	62	58
   query33	509	317	255	255
   query34	1188	1147	636	636
   query35	762	770	658	658
   query36	1274	1300	1099	1099
   query37	150	101	85	85
   query38	3188	3123	3017	3017
   query39	931	912	894	894
   query39_1	901	865	896	865
   query40	233	131	107	107
   query41	64	62	59	59
   query42	93	93	92	92
   query43	316	331	273	273
   query44	
   query45	195	184	171	171
   query46	1113	1213	709	709
   query47	2304	2276	2230	2230
   query48	385	405	291	291
   query49	634	511	394	394
   query50	733	303	223	223
   query51	4251	4240	4429	4240
   query52	87	90	75	75
   query53	245	280	192	192
   query54	264	228	196	196
   query55	78	72	70	70
   query56	239	237	235	235
   query57	1402	1393	1290	1290
   query58	237	213	209	209
   query59	1566	1606	1426	1426
   query60	294	266	237	237
   query61	160	170	156	156
   query62	670	610	555	555
   query63	229	189	200	189
   query64	2485	799	667	667
   query65	
   query66	1744	512	355	355
   query67	30046	29993	29814	29814
   query68	
   query69	428	334	288	288
   query70	993	929	941	929
   query71	306	230	213	213
   query72	2985	2735	2431	2431
   query73	829	793	414	414
   query74	5122	4958	4730	4730
   query75	2881	2648	2308	2308
   query76	2304	1216	784	784
   query77	384	400	294	294
   query78	12887	12912	12464	12464
   query79	1461	1016	752	752
   query80	628	528	405	405
   query81	450	286	246	246
   query82	583	166	123	123
   query83	363	279	256	256
   query84	262	139	115	115
   query85	853	544	508	508
   query86	390	300	291	291
   query87	3374	3335	3166	3166
   query88	3618	2704	2669	2669
   query89	435	377	329	329
   query90	1860	194	185	185
   query91	196	189	157	157
   query92	66	66	60	60
   query93	993	976	565	565
   query94	567	379	337	337
   query95	676	463	349	349
   query96	1036	789	353	353
   query97	2689	2645	2508	2508
   query98	218	208	205	205
   query99	1134	1107	953	953
   Total cold run time: 252807 ms
   Total hot run time: 170147 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4429463631

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 168108 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c, data reload: false
   
   query5	4332	630	473	473
   query6	450	217	182	182
   query7	4888	556	308	308
   query8	369	238	219	219
   query9	8816	4123	4108	4108
   query10	453	313	269	269
   query11	5912	2391	2260	2260
   query12	158	112	101	101
   query13	1273	608	407	407
   query14	6881	5304	4996	4996
   query14_1	4345	4328	4342	4328
   query15	205	201	177	177
   query16	1050	439	497	439
   query17	1157	768	613	613
   query18	2724	509	356	356
   query19	240	185	148	148
   query20	125	109	107	107
   query21	226	147	123	123
   query22	13653	13593	13410	13410
   query23	17261	16443	15965	15965
   query23_1	16098	16201	16188	16188
   query24	7615	1760	1348	1348
   query24_1	1368	1355	1354	1354
   query25	576	453	409	409
   query26	1284	305	168	168
   query27	2617	623	328	328
   query28	4389	1967	1913	1913
   query29	1087	612	489	489
   query30	306	246	197	197
   query31	1108	1066	934	934
   query32	115	70	63	63
   query33	525	332	258	258
   query34	1198	1165	626	626
   query35	739	772	669	669
   query36	1332	1363	1144	1144
   query37	154	103	105	103
   query38	3209	3130	2993	2993
   query39	934	917	897	897
   query39_1	882	884	875	875
   query40	230	151	117	117
   query41	69	66	64	64
   query42	97	91	92	91
   query43	317	317	279	279
   query44	
   query45	193	185	182	182
   query46	1101	1185	734	734
   query47	2320	2308	2164	2164
   query48	419	408	305	305
   query49	635	489	403	403
   query50	706	292	221	221
   query51	4261	4249	4203	4203
   query52	93	90	76	76
   query53	238	275	196	196
   query54	266	229	203	203
   query55	78	75	71	71
   query56	233	234	225	225
   query57	1401	1391	1300	1300
   query58	244	245	197	197
   query59	1518	1631	1369	1369
   query60	312	265	244	244
   query61	161	155	154	154
   query62	664	626	558	558
   query63	223	180	187	180
   query64	2550	829	652	652
   query65	
   query66	1740	485	360	360
   query67	30082	30049	29150	29150
   query68	
   query69	415	308	274	274
   query70	1025	948	939	939
   query71	298	221	212	212
   query72	2947	2732	2447	2447
   query73	831	761	428	428
   query74	5065	4954	4800	4800
   query75	2730	2631	2299	2299
   query76	2311	1125	756	756
   query77	350	385	295	295
   query78	12861	12857	12401	12401
   query79	1296	1081	743	743
   query80	570	513	396	396
   query81	452	278	247	247
   query82	231	163	127	127
   query83	356	276	245	245
   query84	271	140	111	111
   query85	870	501	427	427
   query86	339	290	289	289
   query87	3337	3308	3152	3152
   query88	3500	2643	2649	2643
   query89	409	381	322	322
   query90	2036	187	181	181
   query91	183	169	140	140
   query92	64	58	59	58
   query93	940	971	565	565
   query94	514	362	316	316
   query95	669	399	356	356
   query96	981	785	333	333
   query97	2661	2676	2552	2552
   query98	212	212	207	207
   query99	1109	1092	1003	1003
   Total cold run time: 250346 ms
   Total hot run time: 168108 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4431447780

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4423693430

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/b29520eb8c2fd176695ee6c263c89bab10446d4a_b29520eb8c2fd176695ee6c263c89bab10446d4a_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/b29520eb8c2fd176695ee6c263c89bab10446d4a_b29520eb8c2fd176695ee6c263c89bab10446d4a_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.05% (1849/2369) |
   | Line Coverage     | 64.70% (33208/51327) |
   | Region Coverage   | 65.27% (16446/25198) |
   | Branch Coverage   | 55.81% (8780/15732) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4433541179

   # BE Regression && UT Coverage Report
   Increment line coverage `45.58% (320/702)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_e7a084166584e3ccdc5ae8b6bd5fc50d3c0cb832_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_e7a084166584e3ccdc5ae8b6bd5fc50d3c0cb832_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.76% (27847/37751) |
   | Line Coverage     | 57.56% (301344/523508) |
   | Region Coverage   | 54.57% (251237/460436) |
   | Branch Coverage   | 55.81% (108720/194798) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4289108407

   # BE Regression && UT Coverage Report
   Increment line coverage `47.73% (316/662)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6f985006ec32cea727f90e22b3cd688c1a0626e1_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6f985006ec32cea727f90e22b3cd688c1a0626e1_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 71.79% (26875/37436) |
   | Line Coverage     | 55.00% (285806/519640) |
   | Region Coverage   | 51.74% (236083/456269) |
   | Branch Coverage   | 52.96% (102142/192854) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4288649703

   # FE UT Coverage Report
   Increment line coverage `  25.95% (34/131)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6f985006ec32cea727f90e22b3cd688c1a0626e1/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6f985006ec32cea727f90e22b3cd688c1a0626e1/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4543756152

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 32071 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 3d38dc5ab813ed88625762426d63d5324bfff4fe, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17620	4074	4057	4057
   q2	q3	10947	1517	831	831
   q4	4841	480	344	344
   q5	10640	2405	2179	2179
   q6	390	177	137	137
   q7	999	772	661	661
   q8	9576	1746	1680	1680
   q9	7087	5036	5020	5020
   q10	6466	2230	1889	1889
   q11	448	296	247	247
   q12	651	443	302	302
   q13	18212	3968	2782	2782
   q14	272	264	245	245
   q15	q16	824	767	702	702
   q17	970	983	1036	983
   q18	6910	5684	6315	5684
   q19	1257	1370	1079	1079
   q20	536	414	282	282
   q21	5863	2857	2659	2659
   q22	461	363	308	308
   Total cold run time: 104970 ms
   Total hot run time: 32071 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	5181	4820	4891	4820
   q2	q3	4868	5293	4619	4619
   q4	2196	2272	1455	1455
   q5	4982	4743	4807	4743
   q6	239	181	126	126
   q7	1876	1598	1444	1444
   q8	2301	2004	1973	1973
   q9	7493	7530	7452	7452
   q10	4768	4704	4269	4269
   q11	561	388	357	357
   q12	822	755	543	543
   q13	3025	3377	2820	2820
   q14	264	288	250	250
   q15	q16	682	700	620	620
   q17	1307	1281	1290	1281
   q18	7388	6854	6867	6854
   q19	1115	1118	1092	1092
   q20	2231	2225	1960	1960
   q21	5368	4637	4589	4589
   q22	544	465	415	415
   Total cold run time: 57211 ms
   Total hot run time: 51682 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3309562083


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,875 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary must contain partition_id and slot_id");
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            return Status::InternalError(
+                    "Runtime filter partition boundary references unknown slot_id={}, "
+                    "partition_id={}",
+                    slot_id, tb.partition_id);
+        }
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) {                                                           \
+            return Status::InternalError(                                                      \
+                    "Runtime filter partition boundary must be RANGE or LIST, "                \
+                    "partition_id={}, slot_id={}",                                             \
+                    tb.partition_id, slot_id);                                                 \
+        }                                                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        } else {
+            return Status::InternalError(
+                    "Runtime filter partition boundary has unsupported type, partition_id={}, "
+                    "slot_id={}, type={}",
+                    tb.partition_id, slot_id, slot_type->get_name());
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (expr->is_slot_ref()) {
+        return assert_cast<const VSlotRef*>(expr);
+    }
+    const VSlotRef* found = nullptr;
+    for (const auto& child : expr->children()) {
+        const VSlotRef* c = find_unique_slot_ref(child.get());
+        if (c) {
+            if (found) {
+                return nullptr; // multiple slot refs, can't handle
+            }
+            found = c;
+        }
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        if (direction == TTargetExprMonotonicity::NON_MONOTONIC) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received NON_MONOTONIC partition "
+                    "metadata, filter_id={}, partition_id={}",
+                    filter_id, partition_id);
+        }
+        if (!boundary_partition_ids.contains(partition_id)) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning received monotonicity for a partition "
+                    "without boundary, filter_id={}, partition_id={}, slot_id={}",
+                    filter_id, partition_id, leaf_slot_id);
+        }
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        if (slot_ref->slot_id() != leaf_slot_id) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning SlotRef target mismatch, filter_id={}, "
+                    "target_slot_id={}, leaf_slot_id={}",
+                    filter_id, slot_ref->slot_id(), leaf_slot_id);
+        }
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            if (direction_it->second != TTargetExprMonotonicity::MONOTONIC_INCREASING) {
+                return Status::InternalError(
+                        "Runtime filter partition pruning SlotRef target must use increasing "
+                        "monotonicity, filter_id={}, partition_id={}, monotonicity={}",
+                        filter_id, boundary.partition_id, direction_it->second);
+            }
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    if (slot_type_it == _slot_data_types.end()) {
+        return Status::InternalError(
+                "Runtime filter partition pruning target slot has no boundary type, filter_id={}, "
+                "slot_id={}, target_expr={}",
+                filter_id, leaf_slot_id, target_expr->expr_name());
+    }
+
+    // LIST partitions require regrouping projected values per partition and
+    // should not be marked as expression-prunable by FE yet.
+    for (const auto& selected_boundary : selected_boundaries) {
+        bool is_list = false;
+        std::visit([&](const auto& cvr) { is_list = cvr.is_fixed_value_range(); },
+                   orig_boundaries[selected_boundary.first].boundary_cvr);
+        if (is_list) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning expression target does not support LIST "
+                    "partition boundaries, filter_id={}, partition_id={}, target_expr={}",
+                    filter_id, orig_boundaries[selected_boundary.first].partition_id,
+                    target_expr->expr_name());
+        }
+    }
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+
+    // Build input blocks for lo and hi
+    Block lo_block;
+    Block hi_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns. Open endpoints are not executed through target_expr; they stay
+    // open after projection and swap sides for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    if (!input_built) {
+        return Status::InternalError(
+                "Runtime filter partition pruning expression target has unsupported input type, "
+                "filter_id={}, input_type={}, target_expr={}",
+                filter_id, input_type->get_name(), target_expr->expr_name());
+    }
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        if (lo_result_id < 0) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning failed to project lower boundary, "
+                    "filter_id={}, target_expr={}",
+                    filter_id, target_expr->expr_name());
+        }
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        if (hi_result_id < 0) {
+            return Status::InternalError(
+                    "Runtime filter partition pruning failed to project upper boundary, "
+                    "filter_id={}, target_expr={}",
+                    filter_id, target_expr->expr_name());
+        }
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \

Review Comment:
   │ 假设分区列是 dt,原始 RANGE 分区是:                                                                                                                                             │
   │                                                                                                                                                                                  │
   │  P1: dt in [2024-01-01 00:00:00, 2024-01-02 00:00:00)                                                                                                                            │
   │                                                                                                                                                                                  │
   │ 上界 2024-01-02 00:00:00 是开区间,这一点没错。                                                                                                                                  │
   │                                                                                                                                                                                  │
   │ 但 RF target 不是 dt 本身,而是一个非严格单调表达式,例如按天向上取整:                                                                                                          │
   │                                                                                                                                                                                  │
   │  target_expr = date_ceil(dt, 'day')                                                                                                                                              │
   │                                                                                                                                                                                  │
   │ 这个函数是单调递增的,但不是严格递增。分区里的真实行可能有:                                                                                                                     │
   │                                                                                                                                                                                  │
   │  dt = 2024-01-01 12:00:00                                                                                                                                                        │
   │                                                                                                                                                                                  │
   │ 它满足原始分区范围,因为它小于开上界 2024-01-02 00:00:00。但投影后:                                                                                                             │
   │                                                                                                                                                                                  │
   │  date_ceil(2024-01-01 12:00:00, 'day') = 2024-01-02 00:00:00                                                                                                                     │
   │                                                                                                                                                                                  │
   │ 也就是说,虽然原始上界是开的,投影结果里仍然可能出现 target_expr(range_end) 这个值。                                                                                             │
   │                                                                                                                                                                                  │
   │ 如果 BE 把投影后的范围做成:                                                                                                                                                     │
   │                                                                                                                                                                                  │
   │  [date_ceil(start), date_ceil(end))   -- 上界仍然用 <                                                                                                                            │
   │                                                                                                                                                                                  │
   │ 那就会变成不包含 2024-01-02 00:00:00。假如 RF build side 正好只有:                                                                                                              │
   │                                                                                                                                                                                  │
   │  RF = {2024-01-02 00:00:00}                                                                                                                                                      │
   │                                                                                                                                                                                  │
   │ BE 会认为投影分区范围和 RF 没交集,从而裁掉 P1;但实际上 P1 里 2024-01-01 12:00:00 这类行投影后能命中 RF,所以这是误裁剪。                                                       │
   │                                                                                                                                                                                  │
   │ 所以当前用 <= target_expr(end) 是一个安全的 over-approx:可能多保留一点分区,但不会把可能命中的分区裁掉。只有当我们能证明 target expr                                            │
   │ 是严格单调且开边界不会映射出端点值时,才可以安全地保留 <。  



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4279540844

   # FE Regression Coverage Report
   Increment line coverage `  57.89% (77/133)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_bcfae3c60326b6ef46e5c8dfc68aaf68a3be4fdd_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4288331395

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/6f985006ec32cea727f90e22b3cd688c1a0626e1_6f985006ec32cea727f90e22b3cd688c1a0626e1_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/6f985006ec32cea727f90e22b3cd688c1a0626e1_6f985006ec32cea727f90e22b3cd688c1a0626e1_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.06% (1843/2361) |
   | Line Coverage     | 64.76% (32993/50947) |
   | Region Coverage   | 65.27% (16374/25085) |
   | Branch Coverage   | 55.85% (8741/15652) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3117280644


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,750 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vruntimefilter_wrapper.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+void ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            continue;
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            continue;
+        }
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) break;                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _partition_column_slot_ids.insert(slot_id);
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_partition_column_slot_ids.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (expr->is_slot_ref()) {
+        return assert_cast<const VSlotRef*>(expr);
+    }
+    const VSlotRef* found = nullptr;
+    for (const auto& child : expr->children()) {
+        const VSlotRef* c = find_unique_slot_ref(child.get());
+        if (c) {
+            if (found) {
+                return nullptr; // multiple slot refs, can't handle
+            }
+            found = c;
+        }
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+const std::vector<ParsedBoundary>* ParsedPartitionBoundaries::get_or_compute_projection(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        TTargetExprMonotonicity::type direction, VExprContext* ctx) const {
+    {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        auto it = _projection_cache.find(filter_id);
+        if (it != _projection_cache.end()) {
+            return &it->second;
+        }
+    }
+
+    // Build projection
+    std::vector<ParsedBoundary> projected;
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    if (slot_type_it == _slot_data_types.end()) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    // LIST partitions: TODO(rf-partition-prune) -- projecting through a
+    // monotonic function preserves set membership but requires re-grouping
+    // the projected values back per-partition. Skip for now (caller treats
+    // empty cache as "no projection available").
+    {
+        bool any_list = false;
+        std::visit([&](const auto& cvr) { any_list = cvr.is_fixed_value_range(); },
+                   orig_boundaries[0].boundary_cvr);
+        if (any_list) {
+            std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+            _projection_cache[filter_id] = std::move(projected);
+            return &_projection_cache[filter_id];
+        }
+    }
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = orig_boundaries.size();
+
+    // Track open bounds (low_value == TYPE_MIN or high_value == TYPE_MAX)
+    // externally so we can propagate null_in_projection regardless of whether
+    // the input column is nullable.
+    std::vector<bool> ext_null_lo(N, false);
+    std::vector<bool> ext_null_hi(N, false);
+
+    // Build input blocks for lo and hi
+    Block lo_block;
+    Block hi_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+        auto add_dummy = [&](Block& blk) {
+            auto col = ColumnUInt8::create(N, 0);
+            blk.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                        fmt::format("dummy_{}", col_idx)});
+        };
+        add_dummy(lo_block);
+        add_dummy(hi_block);
+    }
+
+    // Macro-dispatch on input PrimitiveType to build the typed value column
+    // for each side. Inserts NULL (when input column is nullable) or default
+    // (when not) for boundaries that are only_null / open / type-mismatched;
+    // ext_null_lo/hi tracks these so we can mark them null_in_projection
+    // after executing the projection.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[i];                                           \
+            bool null_lo = boundary.only_null;                                                   \
+            bool null_hi = boundary.only_null;                                                   \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            if (cvr == nullptr) {                                                                \
+                null_lo = null_hi = true;                                                        \
+            } else {                                                                             \
+                if (cvr->is_low_value_minimum()) null_lo = true;                                 \
+                if (cvr->is_high_value_maximum()) null_hi = true;                                \
+            }                                                                                    \
+            ext_null_lo[i] = null_lo;                                                            \
+            ext_null_hi[i] = null_hi;                                                            \
+            if (null_lo) {                                                                       \
+                lo_inner->insert_default();                                                      \
+                lo_nulls->get_data().push_back(1);                                               \
+            } else {                                                                             \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (null_hi) {                                                                       \
+                hi_inner->insert_default();                                                      \
+                hi_nulls->get_data().push_back(1);                                               \
+            } else {                                                                             \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    if (!input_built) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    auto status_lo = target_expr->execute(ctx, &lo_block, &lo_result_id);
+    auto status_hi = target_expr->execute(ctx, &hi_block, &hi_result_id);
+    if (!status_lo.ok() || !status_hi.ok() || lo_result_id < 0 || hi_result_id < 0) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    const auto& lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    const auto& hi_result_col = hi_block.get_by_position(hi_result_id).column;
+
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.resize(N);
+
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                         \
+    case TYPE_##OUTPUT_PT: {                                                                   \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;         \
+        for (size_t i = 0; i < N; ++i) {                                                       \
+            projected[i].partition_id = orig_boundaries[i].partition_id;                       \
+            projected[i].slot_id = leaf_slot_id;                                               \
+            projected[i].is_nullable = out_nullable;                                           \
+            projected[i].only_null = orig_boundaries[i].only_null;                             \
+            projected[i].contains_null = orig_boundaries[i].contains_null;                     \
+            bool lo_is_null = ext_null_lo[i] || lo_result_col->is_null_at(i);                  \
+            bool hi_is_null = ext_null_hi[i] || hi_result_col->is_null_at(i);                  \
+            if (lo_is_null || hi_is_null) {                                                    \
+                projected[i].null_in_projection = true;                                        \
+                ColumnValueRange<TYPE_##OUTPUT_PT> full_range("", out_nullable, out_precision, \
+                                                              out_scale);                      \
+                projected[i].boundary_cvr = std::move(full_range);                             \
+            } else {                                                                           \
+                Field lo_field = (*lo_result_col)[i];                                          \
+                Field hi_field = (*hi_result_col)[i];                                          \
+                OutputCppType lo_projected = lo_field.get<TYPE_##OUTPUT_PT>();                 \
+                OutputCppType hi_projected = hi_field.get<TYPE_##OUTPUT_PT>();                 \
+                ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision,        \
+                                                       out_scale);                             \
+                if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {              \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));    \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));      \
+                } else {                                                                       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));    \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));      \
+                }                                                                              \
+                projected[i].boundary_cvr = std::move(cvr);                                    \
+            }                                                                                  \
+        }                                                                                      \
+        break;                                                                                 \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+    _projection_cache[filter_id] = std::move(projected);
+    return &_projection_cache[filter_id];
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    for (const auto& pb : boundaries) {
+        if (_pruned_partition_ids.contains(pb.partition_id) ||
+            newly_pruned.contains(pb.partition_id)) {
+            continue;
+        }
+
+        // Skip partitions with NULL projection conservatively
+        if (pb.null_in_projection) {
+            continue;
+        }
+
+        // NULL handling:
+        //   A partition row whose key is NULL matches the RF iff `rf_contains_null`.
+        //   - only_null partition (rows are exclusively NULL): prunable iff !rf_contains_null.
+        //   - mixed (NULL + concrete values): if rf_contains_null, NULL rows alone
+        //     prevent pruning. Otherwise NULL rows can never match, so we ignore
+        //     the NULL portion and let the regular non-NULL intersection decide.
+        if (pb.only_null) {
+            if (!rf_contains_null) {
+                newly_pruned.insert(pb.partition_id);
+            }
+            continue;
+        }
+        if (pb.contains_null && rf_contains_null) {
+            continue;
+        }
+
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();

Review Comment:
   When an `IN_OR_BLOOM_FILTER` switches to its real `BLOOM_FILTER` shape (`RuntimeFilterWrapper::get_real_type()`), the consumer builds a `VBloomPredicate` (`RuntimeFilterConsumer::_get_push_exprs`, `BLOOM_FILTER` case). In this function we only use `BLOOM_PRED` to derive `rf_contains_null`, but the actual pruning logic below only handles `impl->get_set_func()` and binary min/max predicates. So any real bloom RF never intersects with the partition boundary at all and internal-table partition pruning silently becomes a no-op for larger build sides or remote-merged RFs. Please either add a concrete bloom-vs-boundary pruning path here or explicitly gate this feature to the IN/min-max shapes; otherwise queries that fall over `runtime_filter_max_in_num` will lose the pruning promised by this PR.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3116279964


##########
be/src/exec/operator/olap_scan_operator.cpp:
##########
@@ -499,6 +508,36 @@ Status OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
         _cond_ranges.emplace_back(new doris::OlapScanRange());
     }
 
+    // Filter out tablets whose partitions have been pruned by runtime filters.
+    if (_rf_partition_pruner.pruned_partition_count() > 0) {

Review Comment:
   By the time this compaction runs, `OlapScanLocalState::prepare()` has already called `get_tablet()` / `capture_read_source()` for every scan range, and in cloud mode `init()` / `prepare()` has already kicked off and waited for `sync_rowsets()` on every tablet. Since `ScanLocalState::open()` only acquires RFs afterwards, ready-at-start RFs still pay the full metadata/read-source setup cost for partitions that are immediately dropped here. On wide-partition tables that startup work can dominate, so the new feature currently saves scanner construction / scan IO, but not the expensive setup work that partition pruning was supposed to avoid.



##########
be/src/exec/operator/scan_operator.cpp:
##########
@@ -82,6 +82,7 @@ Status ScanLocalStateBase::update_late_arrival_runtime_filter(RuntimeState* stat
             return a->execute_cost() < b->execute_cost();
         });
     };
+    _on_runtime_filter_update();

Review Comment:
   `update_late_arrival_runtime_filter()` does not know whether `try_append_late_arrival_runtime_filter()` actually appended a new RF. The only "new arrival" check happens later in `Scanner::try_append_late_arrival_runtime_filter()` by comparing `arrived_rf_num` with `_applied_rf_num`, so this new hook now reruns `_do_partition_pruning_by_rf()` on every scanner reschedule while there are still unapplied RFs. In the current scanner code `_applied_rf_num` is never advanced, which means that after the first late RF arrives we will rescan all partition boundaries on every scheduler pass forever. Can we gate this callback on "new exprs were appended" before re-entering the pruner?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4287670951

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3116865659


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,736 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vruntimefilter_wrapper.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+void ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        if (!tb.__isset.partition_id || !tb.__isset.slot_id) {
+            continue;
+        }
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        if (slot_it == slot_descs.end()) {
+            continue;
+        }
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        if (!is_list && !is_range) break;                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        if (parsed_ok) {
+            _partition_column_slot_ids.insert(slot_id);
+            _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+        }
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_partition_column_slot_ids.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (expr->is_slot_ref()) {
+        return assert_cast<const VSlotRef*>(expr);
+    }
+    const VSlotRef* found = nullptr;
+    for (const auto& child : expr->children()) {
+        const VSlotRef* c = find_unique_slot_ref(child.get());
+        if (c) {
+            if (found) {
+                return nullptr; // multiple slot refs, can't handle
+            }
+            found = c;
+        }
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+const std::vector<ParsedBoundary>* ParsedPartitionBoundaries::get_or_compute_projection(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        TTargetExprMonotonicity::type direction, VExprContext* ctx) const {
+    {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        auto it = _projection_cache.find(filter_id);
+        if (it != _projection_cache.end()) {
+            return &it->second;
+        }
+    }
+
+    // Build projection
+    std::vector<ParsedBoundary> projected;
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    if (slot_type_it == _slot_data_types.end()) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    // LIST partitions: TODO(rf-partition-prune) -- projecting through a
+    // monotonic function preserves set membership but requires re-grouping
+    // the projected values back per-partition. Skip for now (caller treats
+    // empty cache as "no projection available").
+    {
+        bool any_list = false;
+        std::visit([&](const auto& cvr) { any_list = cvr.is_fixed_value_range(); },
+                   orig_boundaries[0].boundary_cvr);
+        if (any_list) {
+            std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+            _projection_cache[filter_id] = std::move(projected);
+            return &_projection_cache[filter_id];
+        }
+    }
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = orig_boundaries.size();
+
+    // Track open bounds (low_value == TYPE_MIN or high_value == TYPE_MAX)
+    // externally so we can propagate null_in_projection regardless of whether
+    // the input column is nullable.
+    std::vector<bool> ext_null_lo(N, false);
+    std::vector<bool> ext_null_hi(N, false);
+
+    // Build input blocks for lo and hi
+    Block lo_block;
+    Block hi_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+        auto add_dummy = [&](Block& blk) {
+            auto col = ColumnUInt8::create(N, 0);
+            blk.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                        fmt::format("dummy_{}", col_idx)});
+        };
+        add_dummy(lo_block);
+        add_dummy(hi_block);
+    }
+
+    // Macro-dispatch on input PrimitiveType to build the typed value column
+    // for each side. Inserts NULL (when input column is nullable) or default
+    // (when not) for boundaries that are only_null / open / type-mismatched;
+    // ext_null_lo/hi tracks these so we can mark them null_in_projection
+    // after executing the projection.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[i];                                           \
+            bool null_lo = boundary.only_null;                                                   \
+            bool null_hi = boundary.only_null;                                                   \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            if (cvr == nullptr) {                                                                \
+                null_lo = null_hi = true;                                                        \
+            } else {                                                                             \
+                if (cvr->is_low_value_minimum()) null_lo = true;                                 \
+                if (cvr->is_high_value_maximum()) null_hi = true;                                \
+            }                                                                                    \
+            ext_null_lo[i] = null_lo;                                                            \
+            ext_null_hi[i] = null_hi;                                                            \
+            if (null_lo) {                                                                       \
+                lo_inner->insert_default();                                                      \
+                lo_nulls->get_data().push_back(1);                                               \
+            } else {                                                                             \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (null_hi) {                                                                       \
+                hi_inner->insert_default();                                                      \
+                hi_nulls->get_data().push_back(1);                                               \
+            } else {                                                                             \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    if (!input_built) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    auto status_lo = target_expr->execute(ctx, &lo_block, &lo_result_id);
+    auto status_hi = target_expr->execute(ctx, &hi_block, &hi_result_id);
+    if (!status_lo.ok() || !status_hi.ok() || lo_result_id < 0 || hi_result_id < 0) {
+        std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+        _projection_cache[filter_id] = std::move(projected);
+        return &_projection_cache[filter_id];
+    }
+
+    const auto& lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    const auto& hi_result_col = hi_block.get_by_position(hi_result_id).column;
+
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.resize(N);
+
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                         \
+    case TYPE_##OUTPUT_PT: {                                                                   \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;         \
+        for (size_t i = 0; i < N; ++i) {                                                       \
+            projected[i].partition_id = orig_boundaries[i].partition_id;                       \
+            projected[i].slot_id = leaf_slot_id;                                               \
+            projected[i].is_nullable = out_nullable;                                           \
+            projected[i].only_null = orig_boundaries[i].only_null;                             \
+            projected[i].contains_null = orig_boundaries[i].contains_null;                     \
+            bool lo_is_null = ext_null_lo[i] || lo_result_col->is_null_at(i);                  \
+            bool hi_is_null = ext_null_hi[i] || hi_result_col->is_null_at(i);                  \
+            if (lo_is_null || hi_is_null) {                                                    \
+                projected[i].null_in_projection = true;                                        \
+                ColumnValueRange<TYPE_##OUTPUT_PT> full_range("", out_nullable, out_precision, \
+                                                              out_scale);                      \
+                projected[i].boundary_cvr = std::move(full_range);                             \
+            } else {                                                                           \
+                Field lo_field = (*lo_result_col)[i];                                          \
+                Field hi_field = (*hi_result_col)[i];                                          \
+                OutputCppType lo_projected = lo_field.get<TYPE_##OUTPUT_PT>();                 \
+                OutputCppType hi_projected = hi_field.get<TYPE_##OUTPUT_PT>();                 \
+                ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision,        \
+                                                       out_scale);                             \
+                if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {              \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));    \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));      \
+                } else {                                                                       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));    \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));      \
+                }                                                                              \
+                projected[i].boundary_cvr = std::move(cvr);                                    \
+            }                                                                                  \
+        }                                                                                      \
+        break;                                                                                 \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    std::lock_guard<std::mutex> lock(_projection_cache_mutex);
+    _projection_cache[filter_id] = std::move(projected);
+    return &_projection_cache[filter_id];
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    for (const auto& pb : boundaries) {
+        if (_pruned_partition_ids.contains(pb.partition_id) ||
+            newly_pruned.contains(pb.partition_id)) {
+            continue;
+        }
+
+        // Skip partitions with NULL projection conservatively
+        if (pb.null_in_projection) {
+            continue;
+        }
+
+        // NULL handling:
+        //   A partition row whose key is NULL matches the RF iff `rf_contains_null`.
+        //   - only_null partition (rows are exclusively NULL): prunable iff !rf_contains_null.
+        //   - mixed (NULL + concrete values): if rf_contains_null, NULL rows alone
+        //     prevent pruning. Otherwise NULL rows can never match, so we ignore
+        //     the NULL portion and let the regular non-NULL intersection decide.
+        if (pb.only_null) {
+            if (!rf_contains_null) {
+                newly_pruned.insert(pb.partition_id);
+            }
+            continue;
+        }
+        if (pb.contains_null && rf_contains_null) {
+            continue;
+        }
+
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {
+                        // IN filter: build a fixed-value CVR from the HybridSet
+                        auto rf_cvr = CvrType::create_empty_column_value_range(
+                                pb.is_nullable, boundary_cvr.precision(), boundary_cvr.scale());
+                        auto* iter = hybrid_set->begin();
+                        while (iter->has_next()) {
+                            const void* value = iter->get_value();
+                            if (value) {
+                                if constexpr (std::is_same_v<CppType, StringRef>) {
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else {
+                                    static_cast<void>(rf_cvr.add_fixed_value(
+                                            *reinterpret_cast<const CppType*>(value)));
+                                }
+                            }
+                            iter->next();
+                        }
+                        auto boundary_copy = boundary_cvr;
+                        boundary_copy.intersection(rf_cvr);
+                        if (boundary_copy.is_empty_value_range()) {
+                            newly_pruned.insert(pb.partition_id);
+                        }
+                    } else if ((impl->node_type() == TExprNodeType::BINARY_PRED ||
+                                impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) &&
+                               impl->children().size() == 2 && impl->children()[1]->is_literal()) {
+                        // MinMax filter: binary pred with literal bound
+                        auto* literal = assert_cast<VLiteral*>(impl->children()[1].get());
+                        auto col_ptr = literal->get_column_ptr();
+                        auto data = col_ptr->get_data_at(0);
+                        CppType val {};
+                        if constexpr (std::is_same_v<CppType, StringRef>) {
+                            val = CppType(data.data, data.size);
+                        } else {
+                            val = *reinterpret_cast<const CppType*>(data.data);

Review Comment:
    is , not  (). In this MIN/MAX branch the literal comes from , which builds a  (, , ), so  here returns a  pointing at the character bytes. Reinterpreting  as  on the  branch is therefore invalid for CHAR/VARCHAR/STRING and can read garbage or crash.
   
   This path is reachable for string join keys because  builds  /  literals for  filters on those types. Please construct string-like values from the returned  (for example ) instead of dereferencing  as , and add a string  partition-pruning regression case so this stays covered.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4266847960

   
   Thank you for your contribution to Apache Doris.
   Don't know what should be done next? See [How to process your PR](https://cwiki.apache.org/confluence/display/DORIS/How+to+process+your+PR).
   
   Please clearly describe your PR:
   1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
   2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
   3. What features were added. Why was this function added?
   4. Which code was refactored and why was this part of the code refactored?
   5. Which functions were optimized and what is the difference before and after the optimization?
       


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4441755835

   # BE Regression && UT Coverage Report
   Increment line coverage `48.72% (342/702)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_ca30c183fbab4ffab1f11b3d91b0561736c7c199_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_ca30c183fbab4ffab1f11b3d91b0561736c7c199_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.71% (27799/37715) |
   | Line Coverage     | 57.62% (301509/523286) |
   | Region Coverage   | 54.83% (252433/460402) |
   | Branch Coverage   | 56.01% (109112/194803) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4430190107

   # FE Regression Coverage Report
   Increment line coverage `  59.03% (170/288)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4423649533

   # FE UT Coverage Report
   Increment line coverage `  25.93% (70/270)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_b29520eb8c2fd176695ee6c263c89bab10446d4a/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_b29520eb8c2fd176695ee6c263c89bab10446d4a/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] Dev runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4428347988

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4430137536

   # BE Regression && UT Coverage Report
   Increment line coverage `46.72% (328/702)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_2391ea7f407dcc42f8f4ddc3bcd8e0838059d15c_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.69% (27820/37751) |
   | Line Coverage     | 57.59% (301458/523496) |
   | Region Coverage   | 54.66% (251697/460447) |
   | Branch Coverage   | 55.86% (108810/194796) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4474166410

   # FE UT Coverage Report
   Increment line coverage `  31.19% (92/295)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_ef2529ca3c35ebe1f89cb9fa4212f239fce43a97/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_ef2529ca3c35ebe1f89cb9fa4212f239fce43a97/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4493934116

   run buildall


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4494201625

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.06% (1854/2375) |
   | Line Coverage     | 64.53% (33339/51662) |
   | Region Coverage   | 65.21% (16524/25341) |
   | Branch Coverage   | 55.76% (8839/15852) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4505933053

   # BE Regression && UT Coverage Report
   Increment line coverage `75.28% (603/801)` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_6be29ffaf962c85e6c243c68a1c26cf413cdb0b5_merge/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 73.64% (27896/37883) |
   | Line Coverage     | 57.55% (303189/526849) |
   | Region Coverage   | 54.45% (253995/466433) |
   | Branch Coverage   | 55.95% (110089/196776) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4570705201

   # Cloud UT Coverage Report
   Increment line coverage ` ` :tada:
   
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008_fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008_cloud/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008_fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008_cloud/report/index.html)
   | Category          | Coverage           |
   |-------------------|--------------------|
   | Function Coverage | 78.06% (1854/2375) |
   | Line Coverage     | 64.52% (33334/51663) |
   | Region Coverage   | 65.22% (16528/25343) |
   | Branch Coverage   | 55.72% (8834/15854) |


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4571781585

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 170116 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 32e14d6220221b6aa1e1cab5adf12ffaae3e9f5d, data reload: false
   
   query5	4317	621	491	491
   query6	438	204	181	181
   query7	4833	563	300	300
   query8	378	232	211	211
   query9	8793	3989	4070	3989
   query10	460	325	268	268
   query11	5929	2537	2234	2234
   query12	163	113	105	105
   query13	1329	602	453	453
   query14	6412	5429	5159	5159
   query14_1	4476	4485	4476	4476
   query15	212	207	187	187
   query16	1058	473	480	473
   query17	1142	719	616	616
   query18	2734	497	361	361
   query19	224	191	149	149
   query20	113	111	104	104
   query21	223	143	119	119
   query22	13681	13745	13459	13459
   query23	17339	16490	16288	16288
   query23_1	16339	16326	16378	16326
   query24	7655	1785	1359	1359
   query24_1	1325	1316	1363	1316
   query25	576	466	421	421
   query26	1327	337	168	168
   query27	2596	571	347	347
   query28	4407	2031	2014	2014
   query29	1104	638	502	502
   query30	309	242	205	205
   query31	1187	1081	961	961
   query32	113	64	62	62
   query33	540	337	271	271
   query34	1256	1113	654	654
   query35	768	804	711	711
   query36	1399	1381	1229	1229
   query37	160	104	100	100
   query38	3227	3171	3084	3084
   query39	944	910	887	887
   query39_1	892	907	875	875
   query40	218	123	101	101
   query41	66	65	63	63
   query42	95	97	95	95
   query43	331	321	283	283
   query44	
   query45	194	185	181	181
   query46	1126	1227	742	742
   query47	2370	2419	2206	2206
   query48	413	427	266	266
   query49	651	469	351	351
   query50	1007	347	262	262
   query51	4294	4418	4268	4268
   query52	90	89	77	77
   query53	248	267	200	200
   query54	276	225	205	205
   query55	77	83	73	73
   query56	249	239	225	225
   query57	1432	1406	1315	1315
   query58	246	217	219	217
   query59	1603	1665	1475	1475
   query60	281	263	235	235
   query61	164	166	163	163
   query62	695	650	587	587
   query63	224	181	190	181
   query64	2541	828	628	628
   query65	
   query66	1750	490	367	367
   query67	29828	29087	29595	29087
   query68	
   query69	441	306	275	275
   query70	985	982	952	952
   query71	301	229	216	216
   query72	3035	2742	2481	2481
   query73	878	745	437	437
   query74	5138	5004	4797	4797
   query75	2675	2587	2243	2243
   query76	2329	1154	827	827
   query77	347	388	290	290
   query78	12563	12493	11901	11901
   query79	1420	1111	799	799
   query80	1303	469	392	392
   query81	520	287	244	244
   query82	639	161	128	128
   query83	375	275	257	257
   query84	261	143	116	116
   query85	931	583	436	436
   query86	420	307	303	303
   query87	3432	3372	3244	3244
   query88	3694	2780	2745	2745
   query89	443	375	331	331
   query90	1925	184	183	183
   query91	187	175	142	142
   query92	67	63	57	57
   query93	1698	1466	860	860
   query94	720	358	313	313
   query95	692	390	426	390
   query96	1037	777	354	354
   query97	2691	2707	2552	2552
   query98	211	206	203	203
   query99	1190	1175	1038	1038
   Total cold run time: 253763 ms
   Total hot run time: 170116 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4572824526

   # FE Regression Coverage Report
   Increment line coverage `  72.96% (224/307)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_fb1122e0a26c0dc4fc1defe6bfb9b6b5da020008_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4543853212

   # FE Regression Coverage Report
   Increment line coverage `  48.25% (220/456)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_3d38dc5ab813ed88625762426d63d5324bfff4fe_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_3d38dc5ab813ed88625762426d63d5324bfff4fe_merge_fe/report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4539721621

   # FE UT Coverage Report
   Increment line coverage `  32.00% (96/300)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_f779760a4db0a4e1f4605c63a8a9bc21f51af126/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_f779760a4db0a4e1f4605c63a8a9bc21f51af126/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4449905242

   /review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "BiteTheDDDDt (via GitHub)" <gi...@apache.org>.
BiteTheDDDDt commented on code in PR #62589:
URL: https://github.com/apache/doris/pull/62589#discussion_r3331440040


##########
be/src/exec/runtime_filter/runtime_filter_partition_pruner.cpp:
##########
@@ -0,0 +1,866 @@
+// 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.
+
+#include "exec/runtime_filter/runtime_filter_partition_pruner.h"
+
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <optional>
+#include <unordered_set>
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column.h"
+#include "core/column/column_nullable.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/field.h"
+#include "exprs/bloom_filter_func.h"
+#include "exprs/hybrid_set.h"
+#include "exprs/runtime_filter_expr.h"
+#include "exprs/vexpr.h"
+#include "exprs/vexpr_context.h"
+#include "exprs/vliteral.h"
+#include "exprs/vslot_ref.h"
+#include "runtime/descriptors.h"
+
+namespace doris {
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+// Complexity is inflated by macro expansion for each PrimitiveType case.
+Status ParsedPartitionBoundaries::parse(
+        const std::vector<TPartitionBoundary>& boundaries,
+        const phmap::flat_hash_map<int, SlotDescriptor*>& slot_descs) {
+    for (const auto& tb : boundaries) {
+        DORIS_CHECK(tb.__isset.partition_id);
+        DORIS_CHECK(tb.__isset.slot_id);
+        SlotId slot_id = tb.slot_id;
+
+        auto slot_it = slot_descs.find(slot_id);
+        DORIS_CHECK(slot_it != slot_descs.end());
+        SlotDescriptor* slot = slot_it->second;
+        // Reuse the slot's pre-built DataType: walking through VLiteral here
+        // would cost a `DataTypeFactory::create_data_type(node)` heap allocation
+        // and a one-row `ColumnConst` allocation per boundary endpoint. With
+        // thousands of partitions that dominates BuildTasksTime.
+        const DataTypePtr& slot_type = slot->type();
+        PrimitiveType ptype = slot_type->get_primitive_type();
+        int precision = cast_set<int>(slot_type->get_precision());
+        int scale = cast_set<int>(slot_type->get_scale());
+        bool is_nullable = slot->is_nullable();
+
+        // Store slot data type for potential projection use
+        _slot_data_types[slot_id] = slot_type;
+
+        ParsedBoundary boundary;
+        boundary.partition_id = tb.partition_id;
+        boundary.slot_id = slot_id;
+        boundary.is_nullable = is_nullable;
+
+        bool parsed_ok = false;
+
+#define BUILD_BOUNDARY_CVR(NAME)                                                               \
+    case TYPE_##NAME: {                                                                        \
+        using CppType = typename PrimitiveTypeTraits<TYPE_##NAME>::CppType;                    \
+        bool is_list = tb.__isset.list_values && !tb.list_values.empty();                      \
+        bool is_range = tb.__isset.range_start || tb.__isset.range_end;                        \
+        DORIS_CHECK(is_list || is_range);                                                      \
+        ColumnValueRange<TYPE_##NAME> cvr(slot->col_name(), is_nullable, precision, scale);    \
+        /* Returns nullopt if `node` is a NULL literal; the caller then sets contain_null  */  \
+        /* on the CVR instead of trying to extract a typed value (which would dereference  */  \
+        /* a null data pointer for the non-string branch).                                 */  \
+        auto parse_texpr_node = [&](const TExprNode& node) -> std::optional<CppType> {         \
+            if (node.node_type == TExprNodeType::NULL_LITERAL) {                               \
+                return std::nullopt;                                                           \
+            }                                                                                  \
+            /* `Field` value is copied into the CVR by `add_fixed_value` /             */      \
+            /* `add_range` (both take CppType by const-ref / by value), so the         */      \
+            /* temporary `Field`'s lifetime ending at this expression's full-statement */      \
+            /* boundary is safe -- including for `String` payloads.                    */      \
+            Field field = slot_type->get_field(node);                                          \
+            return std::make_optional<CppType>(field.get<TYPE_##NAME>());                      \
+        };                                                                                     \
+        if (is_list) {                                                                         \
+            auto empty_cvr = ColumnValueRange<TYPE_##NAME>::create_empty_column_value_range(   \
+                    is_nullable, precision, scale);                                            \
+            bool list_has_null = false;                                                        \
+            bool list_has_value = false;                                                       \
+            for (const auto& node : tb.list_values) {                                          \
+                auto parsed = parse_texpr_node(node);                                          \
+                if (!parsed) {                                                                 \
+                    list_has_null = true;                                                      \
+                    continue;                                                                  \
+                }                                                                              \
+                static_cast<void>(empty_cvr.add_fixed_value(*parsed));                         \
+                list_has_value = true;                                                         \
+            }                                                                                  \
+            if (list_has_value) {                                                              \
+                cvr.intersection(empty_cvr);                                                   \
+            }                                                                                  \
+            if (list_has_null && is_nullable) {                                                \
+                /* Track NULL membership on ParsedBoundary; calling          */                \
+                /* cvr.set_contain_null(true) here would invoke              */                \
+                /* set_empty_value_range() and discard the concrete fixed    */                \
+                /* values we just inserted, turning {NULL, v} into a         */                \
+                /* NULL-only boundary.                                       */                \
+                boundary.contains_null = true;                                                 \
+                if (!list_has_value) {                                                         \
+                    boundary.only_null = true;                                                 \
+                }                                                                              \
+            }                                                                                  \
+        } else {                                                                               \
+            bool range_has_null = false;                                                       \
+            if (is_nullable && !tb.__isset.range_start) {                                      \
+                range_has_null = true;                                                         \
+            }                                                                                  \
+            if (tb.__isset.range_start) {                                                      \
+                auto parsed = parse_texpr_node(tb.range_start);                                \
+                if (parsed) {                                                                  \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, *parsed));         \
+                } else if (is_nullable) {                                                      \
+                    range_has_null = true;                                                     \
+                }                                                                              \
+            }                                                                                  \
+            if (tb.__isset.range_end) {                                                        \
+                auto parsed = parse_texpr_node(tb.range_end);                                  \
+                if (parsed) {                                                                  \
+                    /* Multi-column RANGE projection emits a CLOSED upper bound (see       */  \
+                    /* TPartitionBoundary.range_end_inclusive comment); single-column RANGE */ \
+                    /* keeps the natural OPEN upper bound matching Doris semantics.         */ \
+                    SQLFilterOp upper_op =                                                     \
+                            (tb.__isset.range_end_inclusive && tb.range_end_inclusive)         \
+                                    ? FILTER_LESS_OR_EQUAL                                     \
+                                    : FILTER_LESS;                                             \
+                    static_cast<void>(cvr.add_range(upper_op, *parsed));                       \
+                }                                                                              \
+            }                                                                                  \
+            if (range_has_null) {                                                              \
+                boundary.contains_null = true;                                                 \
+            }                                                                                  \
+        }                                                                                      \
+        boundary.boundary_cvr = std::move(cvr);                                                \
+        parsed_ok = true;                                                                      \
+        break;                                                                                 \
+    }
+
+        switch (ptype) {
+            BUILD_BOUNDARY_CVR(TINYINT)
+            BUILD_BOUNDARY_CVR(SMALLINT)
+            BUILD_BOUNDARY_CVR(INT)
+            BUILD_BOUNDARY_CVR(BIGINT)
+            BUILD_BOUNDARY_CVR(LARGEINT)
+            BUILD_BOUNDARY_CVR(FLOAT)
+            BUILD_BOUNDARY_CVR(DOUBLE)
+            BUILD_BOUNDARY_CVR(CHAR)
+            BUILD_BOUNDARY_CVR(DATE)
+            BUILD_BOUNDARY_CVR(DATETIME)
+            BUILD_BOUNDARY_CVR(DATEV2)
+            BUILD_BOUNDARY_CVR(DATETIMEV2)
+            BUILD_BOUNDARY_CVR(TIMESTAMPTZ)
+            BUILD_BOUNDARY_CVR(VARCHAR)
+            BUILD_BOUNDARY_CVR(STRING)
+            BUILD_BOUNDARY_CVR(DECIMAL32)
+            BUILD_BOUNDARY_CVR(DECIMAL64)
+            BUILD_BOUNDARY_CVR(DECIMAL128I)
+            BUILD_BOUNDARY_CVR(DECIMAL256)
+            BUILD_BOUNDARY_CVR(DECIMALV2)
+            BUILD_BOUNDARY_CVR(BOOLEAN)
+            BUILD_BOUNDARY_CVR(IPV4)
+            BUILD_BOUNDARY_CVR(IPV6)
+        default:
+            break;
+        }
+#undef BUILD_BOUNDARY_CVR
+
+        DORIS_CHECK(parsed_ok);
+        _slot_to_boundaries[slot_id].push_back(std::move(boundary));
+    }
+
+    // Count distinct partition IDs across all boundaries.
+    if (!_slot_to_boundaries.empty()) {
+        phmap::flat_hash_set<int64_t> all_partition_ids;
+        for (const auto& [_, slot_boundaries] : _slot_to_boundaries) {
+            for (const auto& pb : slot_boundaries) {
+                all_partition_ids.insert(pb.partition_id);
+            }
+        }
+        _total_partition_count = static_cast<int64_t>(all_partition_ids.size());
+    }
+    return Status::OK();
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static bool find_unique_slot_ref_impl(const VExpr* expr, const VSlotRef** found) {
+    if (!expr) {
+        return true;
+    }
+    if (expr->is_slot_ref()) {
+        const auto* slot = assert_cast<const VSlotRef*>(expr);
+        if (*found == nullptr) {
+            *found = slot;
+            return true;
+        }
+        if ((*found)->slot_id() != slot->slot_id()) {
+            return false;
+        }
+        DORIS_CHECK((*found)->column_id() == slot->column_id());
+        return true;
+    }
+    for (const auto& child : expr->children()) {
+        if (!find_unique_slot_ref_impl(child.get(), found)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static const VSlotRef* find_unique_slot_ref(const VExpr* expr) {
+    const VSlotRef* found = nullptr;
+    if (!find_unique_slot_ref_impl(expr, &found)) {
+        return nullptr;
+    }
+    return found;
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+Status ParsedPartitionBoundaries::get_or_compute_projected_boundaries(
+        int filter_id, const VExprSPtr& target_expr, SlotId leaf_slot_id, int leaf_column_id,
+        const std::unordered_map<int64_t, TTargetExprMonotonicity::type>& partition_directions,
+        VExprContext* ctx, std::shared_ptr<const std::vector<ParsedBoundary>>* output) const {
+    {
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto it = _projected_boundaries_by_filter_id.find(filter_id);
+        if (it != _projected_boundaries_by_filter_id.end()) {
+            *output = it->second;
+            return Status::OK();
+        }
+    }
+
+    std::vector<ParsedBoundary> projected;
+    auto store_projected_boundaries = [&](std::vector<ParsedBoundary>&& boundaries) {
+        auto cached = std::make_shared<const std::vector<ParsedBoundary>>(std::move(boundaries));
+        std::lock_guard<std::mutex> lock(_projected_boundaries_mutex);
+        auto [it, inserted] = _projected_boundaries_by_filter_id.emplace(filter_id, cached);
+        *output = inserted ? cached : it->second;
+        return Status::OK();
+    };
+
+    auto slot_boundaries_it = _slot_to_boundaries.find(leaf_slot_id);
+    if (slot_boundaries_it == _slot_to_boundaries.end()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    const auto& orig_boundaries = slot_boundaries_it->second;
+    if (orig_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::unordered_set<int64_t> boundary_partition_ids;
+    boundary_partition_ids.reserve(orig_boundaries.size());
+    for (const auto& boundary : orig_boundaries) {
+        boundary_partition_ids.insert(boundary.partition_id);
+    }
+    for (const auto& [partition_id, direction] : partition_directions) {
+        DORIS_CHECK(direction != TTargetExprMonotonicity::NON_MONOTONIC);
+        DORIS_CHECK(boundary_partition_ids.contains(partition_id));
+    }
+
+    if (target_expr->is_slot_ref()) {
+        auto* slot_ref = assert_cast<VSlotRef*>(target_expr.get());
+        DORIS_CHECK(slot_ref->slot_id() == leaf_slot_id);
+        std::vector<ParsedBoundary> slot_boundaries;
+        slot_boundaries.reserve(orig_boundaries.size());
+        for (const auto& boundary : orig_boundaries) {
+            auto direction_it = partition_directions.find(boundary.partition_id);
+            if (direction_it == partition_directions.end()) {
+                continue;
+            }
+            DORIS_CHECK(direction_it->second == TTargetExprMonotonicity::MONOTONIC_INCREASING);
+            slot_boundaries.emplace_back(boundary);
+        }
+        return store_projected_boundaries(std::move(slot_boundaries));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> selected_boundaries;
+    selected_boundaries.reserve(orig_boundaries.size());
+    for (size_t i = 0; i < orig_boundaries.size(); ++i) {
+        auto it = partition_directions.find(orig_boundaries[i].partition_id);
+        if (it != partition_directions.end() &&
+            it->second != TTargetExprMonotonicity::NON_MONOTONIC) {
+            selected_boundaries.emplace_back(i, it->second);
+        }
+    }
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    std::vector<std::pair<size_t, TTargetExprMonotonicity::type>> projectable_boundaries;
+    projectable_boundaries.reserve(selected_boundaries.size());
+    for (const auto& selected_boundary : selected_boundaries) {
+        const auto& boundary = orig_boundaries[selected_boundary.first];
+        if (!boundary.only_null && !boundary.contains_null) {
+            projectable_boundaries.emplace_back(selected_boundary);
+        }
+    }
+    selected_boundaries.swap(projectable_boundaries);
+    if (selected_boundaries.empty()) {
+        return store_projected_boundaries(std::move(projected));
+    }
+
+    auto slot_type_it = _slot_data_types.find(leaf_slot_id);
+    DORIS_CHECK(slot_type_it != _slot_data_types.end());
+
+    const DataTypePtr& input_type = slot_type_it->second;
+    bool input_is_nullable = input_type->is_nullable();
+    DataTypePtr inner_type = input_is_nullable ? remove_nullable(input_type) : input_type;
+    PrimitiveType input_ptype = input_type->get_primitive_type();
+
+    size_t N = selected_boundaries.size();
+    std::vector<bool> lo_open(N, false);
+    std::vector<bool> hi_open(N, false);
+    std::vector<bool> boundary_is_list(N, false);
+    std::vector<size_t> lo_result_row(N, 0);
+    std::vector<size_t> hi_result_row(N, 0);
+    std::vector<size_t> list_result_begin(N, 0);
+    std::vector<size_t> list_result_end(N, 0);
+    size_t lo_row_count = 0;
+    size_t hi_row_count = 0;
+    size_t list_row_count = 0;
+
+    // Build input blocks for RANGE endpoints and LIST values.
+    Block lo_block;
+    Block hi_block;
+    Block list_block;
+
+    // Pad columns 0..leaf_column_id-1 with placeholder columns so the leaf
+    // VSlotRef (whose column_id is leaf_column_id) reads our typed column.
+    auto add_dummy_columns = [&](Block& block, size_t row_count) {
+        for (int col_idx = 0; col_idx < leaf_column_id; ++col_idx) {
+            auto col = ColumnUInt8::create(row_count, 0);
+            block.insert({std::move(col), std::make_shared<DataTypeUInt8>(),
+                          fmt::format("dummy_{}", col_idx)});
+        }
+    };
+
+    // Macro-dispatch on input PrimitiveType to build finite RANGE endpoint
+    // columns and LIST value columns. Unbounded RANGE endpoints are not executed
+    // through target_expr; they stay unbounded after projection and swap sides
+    // for monotonic decreasing targets.
+    bool input_built = false;
+#define BUILD_INPUT_COLUMNS(INPUT_PT)                                                            \
+    case TYPE_##INPUT_PT: {                                                                      \
+        using InCol = typename PrimitiveTypeTraits<TYPE_##INPUT_PT>::ColumnType;                 \
+        MutableColumnPtr lo_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr hi_inner_base = inner_type->create_column();                            \
+        MutableColumnPtr list_inner_base = inner_type->create_column();                          \
+        auto* lo_inner = assert_cast<InCol*>(lo_inner_base.get());                               \
+        auto* hi_inner = assert_cast<InCol*>(hi_inner_base.get());                               \
+        auto* list_inner = assert_cast<InCol*>(list_inner_base.get());                           \
+        lo_inner->reserve(N);                                                                    \
+        hi_inner->reserve(N);                                                                    \
+        list_inner->reserve(N);                                                                  \
+        auto lo_nulls = ColumnUInt8::create();                                                   \
+        auto hi_nulls = ColumnUInt8::create();                                                   \
+        auto list_nulls = ColumnUInt8::create();                                                 \
+        lo_nulls->reserve(N);                                                                    \
+        hi_nulls->reserve(N);                                                                    \
+        list_nulls->reserve(N);                                                                  \
+        for (size_t i = 0; i < N; ++i) {                                                         \
+            const auto& boundary = orig_boundaries[selected_boundaries[i].first];                \
+            const auto* cvr =                                                                    \
+                    std::get_if<ColumnValueRange<TYPE_##INPUT_PT>>(&boundary.boundary_cvr);      \
+            DCHECK(cvr != nullptr);                                                              \
+            DCHECK(!boundary.only_null);                                                         \
+            DCHECK(!boundary.contains_null);                                                     \
+            boundary_is_list[i] = cvr->is_fixed_value_range();                                   \
+            if (boundary_is_list[i]) {                                                           \
+                list_result_begin[i] = list_row_count;                                           \
+                for (const auto& value : cvr->get_fixed_value_set()) {                           \
+                    list_inner->insert_value(value);                                             \
+                    list_nulls->get_data().push_back(0);                                         \
+                    ++list_row_count;                                                            \
+                }                                                                                \
+                list_result_end[i] = list_row_count;                                             \
+                continue;                                                                        \
+            }                                                                                    \
+            lo_open[i] = cvr->is_low_value_minimum();                                            \
+            hi_open[i] = cvr->is_high_value_maximum();                                           \
+            if (!lo_open[i]) {                                                                   \
+                lo_result_row[i] = lo_row_count++;                                               \
+                lo_inner->insert_value(cvr->get_range_min_value());                              \
+                lo_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+            if (!hi_open[i]) {                                                                   \
+                hi_result_row[i] = hi_row_count++;                                               \
+                hi_inner->insert_value(cvr->get_range_max_value());                              \
+                hi_nulls->get_data().push_back(0);                                               \
+            }                                                                                    \
+        }                                                                                        \
+        add_dummy_columns(lo_block, lo_row_count);                                               \
+        add_dummy_columns(hi_block, hi_row_count);                                               \
+        add_dummy_columns(list_block, list_row_count);                                           \
+        if (input_is_nullable) {                                                                 \
+            auto lo_col = ColumnNullable::create(std::move(lo_inner_base), std::move(lo_nulls)); \
+            auto hi_col = ColumnNullable::create(std::move(hi_inner_base), std::move(hi_nulls)); \
+            auto list_col =                                                                      \
+                    ColumnNullable::create(std::move(list_inner_base), std::move(list_nulls));   \
+            lo_block.insert({std::move(lo_col), input_type, "leaf_slot"});                       \
+            hi_block.insert({std::move(hi_col), input_type, "leaf_slot"});                       \
+            list_block.insert({std::move(list_col), input_type, "leaf_slot"});                   \
+        } else {                                                                                 \
+            lo_block.insert({std::move(lo_inner_base), input_type, "leaf_slot"});                \
+            hi_block.insert({std::move(hi_inner_base), input_type, "leaf_slot"});                \
+            list_block.insert({std::move(list_inner_base), input_type, "leaf_slot"});            \
+        }                                                                                        \
+        input_built = true;                                                                      \
+        break;                                                                                   \
+    }
+
+    switch (input_ptype) {
+        BUILD_INPUT_COLUMNS(TINYINT)
+        BUILD_INPUT_COLUMNS(SMALLINT)
+        BUILD_INPUT_COLUMNS(INT)
+        BUILD_INPUT_COLUMNS(BIGINT)
+        BUILD_INPUT_COLUMNS(LARGEINT)
+        BUILD_INPUT_COLUMNS(FLOAT)
+        BUILD_INPUT_COLUMNS(DOUBLE)
+        BUILD_INPUT_COLUMNS(CHAR)
+        BUILD_INPUT_COLUMNS(DATE)
+        BUILD_INPUT_COLUMNS(DATETIME)
+        BUILD_INPUT_COLUMNS(DATEV2)
+        BUILD_INPUT_COLUMNS(DATETIMEV2)
+        BUILD_INPUT_COLUMNS(TIMESTAMPTZ)
+        BUILD_INPUT_COLUMNS(VARCHAR)
+        BUILD_INPUT_COLUMNS(STRING)
+        BUILD_INPUT_COLUMNS(DECIMAL32)
+        BUILD_INPUT_COLUMNS(DECIMAL64)
+        BUILD_INPUT_COLUMNS(DECIMAL128I)
+        BUILD_INPUT_COLUMNS(DECIMAL256)
+        BUILD_INPUT_COLUMNS(DECIMALV2)
+        BUILD_INPUT_COLUMNS(BOOLEAN)
+        BUILD_INPUT_COLUMNS(IPV4)
+        BUILD_INPUT_COLUMNS(IPV6)
+    default:
+        break;
+    }
+#undef BUILD_INPUT_COLUMNS
+
+    DORIS_CHECK(input_built);
+
+    int lo_result_id = -1;
+    int hi_result_id = -1;
+    int list_result_id = -1;
+    ColumnPtr lo_result_col;
+    ColumnPtr hi_result_col;
+    ColumnPtr list_result_col;
+    if (lo_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &lo_block, &lo_result_id));
+        DORIS_CHECK(lo_result_id >= 0);
+        lo_result_col = lo_block.get_by_position(lo_result_id).column;
+    }
+    if (hi_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &hi_block, &hi_result_id));
+        DORIS_CHECK(hi_result_id >= 0);
+        hi_result_col = hi_block.get_by_position(hi_result_id).column;
+    }
+    if (list_row_count > 0) {
+        RETURN_IF_ERROR(target_expr->execute(ctx, &list_block, &list_result_id));
+        DORIS_CHECK(list_result_id >= 0);
+        list_result_col = list_block.get_by_position(list_result_id).column;
+    }
+    int out_precision = cast_set<int>(target_expr->data_type()->get_precision());
+    int out_scale = cast_set<int>(target_expr->data_type()->get_scale());
+    bool out_nullable = target_expr->data_type()->is_nullable();
+    PrimitiveType output_ptype = target_expr->data_type()->get_primitive_type();
+
+    projected.reserve(N);
+
+    bool output_built = false;
+#define BUILD_PROJECTED_CVR(OUTPUT_PT)                                                          \
+    case TYPE_##OUTPUT_PT: {                                                                    \
+        using OutputCppType = typename PrimitiveTypeTraits<TYPE_##OUTPUT_PT>::CppType;          \
+        auto projected_value = [](const ColumnPtr& column, size_t row) -> OutputCppType {       \
+            Field field = (*column)[row];                                                       \
+            return field.get<TYPE_##OUTPUT_PT>();                                               \
+        };                                                                                      \
+        for (size_t i = 0; i < N; ++i) {                                                        \
+            const auto& orig_boundary = orig_boundaries[selected_boundaries[i].first];          \
+            TTargetExprMonotonicity::type direction = selected_boundaries[i].second;            \
+            if (boundary_is_list[i]) {                                                          \
+                auto cvr = ColumnValueRange<TYPE_##OUTPUT_PT>::create_empty_column_value_range( \
+                        out_nullable, out_precision, out_scale);                                \
+                bool list_has_value = false;                                                    \
+                bool list_has_null = false;                                                     \
+                for (size_t row = list_result_begin[i]; row < list_result_end[i]; ++row) {      \
+                    if (list_result_col->is_null_at(row)) {                                     \
+                        list_has_null = true;                                                   \
+                        continue;                                                               \
+                    }                                                                           \
+                    auto projected_list_value = projected_value(list_result_col, row);          \
+                    static_cast<void>(cvr.add_fixed_value(projected_list_value));               \
+                    list_has_value = true;                                                      \
+                }                                                                               \
+                if (!list_has_value && !list_has_null) {                                        \
+                    continue;                                                                   \
+                }                                                                               \
+                ParsedBoundary projected_boundary;                                              \
+                projected_boundary.partition_id = orig_boundary.partition_id;                   \
+                projected_boundary.slot_id = leaf_slot_id;                                      \
+                projected_boundary.is_nullable = out_nullable;                                  \
+                projected_boundary.contains_null = list_has_null;                               \
+                projected_boundary.only_null = list_has_null && !list_has_value;                \
+                projected_boundary.boundary_cvr = std::move(cvr);                               \
+                projected.emplace_back(std::move(projected_boundary));                          \
+                continue;                                                                       \
+            }                                                                                   \
+            if ((!lo_open[i] && lo_result_col->is_null_at(lo_result_row[i])) ||                 \
+                (!hi_open[i] && hi_result_col->is_null_at(hi_result_row[i]))) {                 \
+                continue;                                                                       \
+            }                                                                                   \
+            ColumnValueRange<TYPE_##OUTPUT_PT> cvr("", out_nullable, out_precision, out_scale); \
+            /* Use closed projected finite bounds as a conservative envelope for */             \
+            /* non-strict monotonic expressions such as date ceil/floor/cast.    */             \
+            if (direction == TTargetExprMonotonicity::MONOTONIC_DECREASING) {                   \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, hi_projected));     \
+                }                                                                               \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, lo_projected));       \
+                }                                                                               \
+            } else {                                                                            \
+                if (!lo_open[i]) {                                                              \
+                    auto lo_projected = projected_value(lo_result_col, lo_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LARGER_OR_EQUAL, lo_projected));     \
+                }                                                                               \
+                if (!hi_open[i]) {                                                              \
+                    auto hi_projected = projected_value(hi_result_col, hi_result_row[i]);       \
+                    static_cast<void>(cvr.add_range(FILTER_LESS_OR_EQUAL, hi_projected));       \
+                }                                                                               \
+            }                                                                                   \
+            ParsedBoundary projected_boundary;                                                  \
+            projected_boundary.partition_id = orig_boundary.partition_id;                       \
+            projected_boundary.slot_id = leaf_slot_id;                                          \
+            projected_boundary.is_nullable = out_nullable;                                      \
+            projected_boundary.only_null = orig_boundary.only_null;                             \
+            projected_boundary.contains_null = orig_boundary.contains_null;                     \
+            projected_boundary.boundary_cvr = std::move(cvr);                                   \
+            projected.emplace_back(std::move(projected_boundary));                              \
+        }                                                                                       \
+        output_built = true;                                                                    \
+        break;                                                                                  \
+    }
+
+    switch (output_ptype) {
+        BUILD_PROJECTED_CVR(TINYINT)
+        BUILD_PROJECTED_CVR(SMALLINT)
+        BUILD_PROJECTED_CVR(INT)
+        BUILD_PROJECTED_CVR(BIGINT)
+        BUILD_PROJECTED_CVR(LARGEINT)
+        BUILD_PROJECTED_CVR(FLOAT)
+        BUILD_PROJECTED_CVR(DOUBLE)
+        BUILD_PROJECTED_CVR(CHAR)
+        BUILD_PROJECTED_CVR(DATE)
+        BUILD_PROJECTED_CVR(DATETIME)
+        BUILD_PROJECTED_CVR(DATEV2)
+        BUILD_PROJECTED_CVR(DATETIMEV2)
+        BUILD_PROJECTED_CVR(TIMESTAMPTZ)
+        BUILD_PROJECTED_CVR(VARCHAR)
+        BUILD_PROJECTED_CVR(STRING)
+        BUILD_PROJECTED_CVR(DECIMAL32)
+        BUILD_PROJECTED_CVR(DECIMAL64)
+        BUILD_PROJECTED_CVR(DECIMAL128I)
+        BUILD_PROJECTED_CVR(DECIMAL256)
+        BUILD_PROJECTED_CVR(DECIMALV2)
+        BUILD_PROJECTED_CVR(BOOLEAN)
+        BUILD_PROJECTED_CVR(IPV4)
+        BUILD_PROJECTED_CVR(IPV6)
+    default:
+        break;
+    }
+
+#undef BUILD_PROJECTED_CVR
+
+    DORIS_CHECK(output_built);
+
+    return store_projected_boundaries(std::move(projected));
+}
+// NOLINTEND(readability-function-cognitive-complexity,readability-function-size)
+
+static SQLFilterOp convert_opcode_to_filter_op(TExprOpcode::type op) {
+    switch (op) {
+    case TExprOpcode::LE:
+        return FILTER_LESS_OR_EQUAL;
+    case TExprOpcode::LT:
+        return FILTER_LESS;
+    case TExprOpcode::GE:
+        return FILTER_LARGER_OR_EQUAL;
+    case TExprOpcode::GT:
+        return FILTER_LARGER;
+    default:
+        return FILTER_IN; // sentinel: caller should skip
+    }
+}
+
+// NOLINTBEGIN(readability-function-cognitive-complexity,readability-function-size)
+void RuntimeFilterPartitionPruner::_try_prune_by_single_rf(
+        const std::vector<ParsedBoundary>& boundaries, const VExprSPtr& impl,
+        phmap::flat_hash_set<int64_t>& newly_pruned) {
+    // Pre-compute whether the RF "matches NULL" -- i.e. whether the RF would
+    // accept a row whose probe value is NULL. The signal differs by RF impl:
+    //   * IN filter      → HybridSet::contain_null()
+    //   * Bloom filter   → BloomFilterFuncBase::contain_null()
+    //   * MinMax filter  → encoded via the BinaryPredicate node_type:
+    //                      NULL_AWARE_BINARY_PRED means the build side had a
+    //                      NULL key and a null-safe equal join asked NULL to
+    //                      match NULL; plain BINARY_PRED never matches NULL.
+    // FilterBase::contain_null() already folds in `_null_aware`, so we only
+    // get a true result when the build side is actually null-aware AND
+    // produced a NULL value.
+    bool rf_contains_null = false;
+    if (auto hybrid_set = impl->get_set_func()) {
+        rf_contains_null = hybrid_set->contain_null();
+    } else if (impl->node_type() == TExprNodeType::BLOOM_PRED) {
+        auto bloom = impl->get_bloom_filter_func();
+        rf_contains_null = bloom && bloom->contain_null();
+    } else if (impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) {
+        // Min/Max RF built on a null-safe equal join. The literal child holds
+        // the min or max bound; the NULL semantic is conveyed by the node
+        // type itself (see create_vbin_predicate in runtime_filter/utils.cpp).
+        rf_contains_null = true;
+    }
+
+    std::optional<ColumnValueRangeType> rf_cvr;
+    if (!boundaries.empty()) {
+        std::visit(
+                [&](const auto& boundary_cvr) {
+                    using CvrType = std::decay_t<decltype(boundary_cvr)>;
+                    using CppType = typename CvrType::CppType;
+
+                    auto hybrid_set = impl->get_set_func();
+                    if (hybrid_set) {
+                        auto typed_rf_cvr = CvrType::create_empty_column_value_range(
+                                boundaries.front().is_nullable, boundary_cvr.precision(),
+                                boundary_cvr.scale());
+                        auto* iter = hybrid_set->begin();
+                        while (iter->has_next()) {
+                            const void* value = iter->get_value();
+                            if (value) {
+                                if constexpr (std::is_same_v<CppType, String>) {
+                                    // String HybridSets store StringRef*, but
+                                    // ColumnValueRange<String>::CppType is
+                                    // std::string. Construct from the bytes.
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                                    const auto* str_val = reinterpret_cast<const StringRef*>(value);
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            CppType(str_val->data, str_val->size)));
+                                } else {
+                                    static_cast<void>(typed_rf_cvr.add_fixed_value(
+                                            *reinterpret_cast<const CppType*>(value)));
+                                }
+                            }
+                            iter->next();
+                        }
+                        rf_cvr.emplace(std::move(typed_rf_cvr));
+                    } else if ((impl->node_type() == TExprNodeType::BINARY_PRED ||
+                                impl->node_type() == TExprNodeType::NULL_AWARE_BINARY_PRED) &&
+                               impl->children().size() == 2 && impl->children()[1]->is_literal()) {
+                        // MinMax filter: binary pred with literal bound
+                        auto* literal = assert_cast<VLiteral*>(impl->children()[1].get());
+                        auto col_ptr = literal->get_column_ptr();
+                        auto data = col_ptr->get_data_at(0);
+                        CppType val {};
+                        if constexpr (std::is_same_v<CppType, String>) {
+                            // get_data_at returns a StringRef pointing at the
+                            // raw character bytes; ColumnValueRange<String>'s
+                            // CppType is std::string, so construct one from
+                            // the bytes rather than dereferencing as String.
+                            val = CppType(data.data, data.size);
+                        } else if constexpr (std::is_same_v<CppType, StringRef>) {
+                            val = CppType(data.data, data.size);
+                        } else {
+                            val = *reinterpret_cast<const CppType*>(data.data);
+                        }
+
+                        SQLFilterOp op = convert_opcode_to_filter_op(impl->op());

Review Comment:
   改成用optional表示非法opcode



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4450398757

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 29635 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit c090ab6440346db9f2c052585ec7b6dc593d0df3, data reload: false
   
   ------ Round 1 ----------------------------------
   orders	Doris	NULL	NULL	0	0	0	NULL	0	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	17707	3885	3958	3885
   q2	q3	10706	878	610	610
   q4	4671	456	348	348
   q5	7439	1325	1141	1141
   q6	185	167	136	136
   q7	912	962	745	745
   q8	9505	1384	1283	1283
   q9	6148	5442	5372	5372
   q10	6327	2074	1823	1823
   q11	476	271	259	259
   q12	683	411	291	291
   q13	18209	3343	2744	2744
   q14	303	284	265	265
   q15	q16	908	871	788	788
   q17	1203	1061	767	767
   q18	6817	5685	5611	5611
   q19	1661	1160	1114	1114
   q20	540	409	267	267
   q21	4499	2294	1885	1885
   q22	424	355	301	301
   Total cold run time: 99323 ms
   Total hot run time: 29635 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   orders	Doris	NULL	NULL	150000000	42	6422171781	NULL	22778155	NULL	NULL	2023-12-26 18:27:23	2023-12-26 18:42:55	NULL	utf-8	NULL	NULL	
   ============================================
   q1	4277	4283	4210	4210
   q2	q3	4640	4769	4207	4207
   q4	2095	2182	1402	1402
   q5	4976	4902	5126	4902
   q6	210	163	131	131
   q7	2051	2207	1653	1653
   q8	3449	3143	3125	3125
   q9	8599	8460	8485	8460
   q10	4538	4495	4233	4233
   q11	652	439	411	411
   q12	684	742	531	531
   q13	3234	3514	3002	3002
   q14	309	312	287	287
   q15	q16	782	785	709	709
   q17	1334	1311	1314	1311
   q18	8281	7100	7157	7100
   q19	1166	1170	1168	1168
   q20	2291	2234	1945	1945
   q21	6304	5364	4926	4926
   q22	556	509	420	420
   Total cold run time: 60428 ms
   Total hot run time: 54133 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4450460751

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 170469 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit c090ab6440346db9f2c052585ec7b6dc593d0df3, data reload: false
   
   query5	4316	638	478	478
   query6	433	192	181	181
   query7	4801	556	305	305
   query8	366	213	202	202
   query9	8795	4065	4075	4065
   query10	450	317	257	257
   query11	6130	2366	2222	2222
   query12	163	111	99	99
   query13	1281	633	435	435
   query14	7272	5325	4967	4967
   query14_1	4306	4277	4275	4275
   query15	198	196	172	172
   query16	1025	455	437	437
   query17	1228	713	594	594
   query18	2719	477	347	347
   query19	201	181	139	139
   query20	110	109	103	103
   query21	214	136	117	117
   query22	13582	13977	14451	13977
   query23	17298	16428	16249	16249
   query23_1	16346	16282	16368	16282
   query24	8256	1811	1367	1367
   query24_1	1392	1378	1340	1340
   query25	598	494	431	431
   query26	1287	321	163	163
   query27	2674	618	328	328
   query28	4367	1990	1959	1959
   query29	1087	622	511	511
   query30	299	243	195	195
   query31	1113	1072	937	937
   query32	107	60	57	57
   query33	535	357	265	265
   query34	1134	1124	607	607
   query35	736	807	658	658
   query36	1348	1343	1158	1158
   query37	151	104	91	91
   query38	3190	3106	3102	3102
   query39	944	933	884	884
   query39_1	873	888	894	888
   query40	232	135	113	113
   query41	70	67	67	67
   query42	92	94	95	94
   query43	317	315	274	274
   query44	
   query45	205	178	185	178
   query46	1070	1187	736	736
   query47	2354	2279	2228	2228
   query48	402	395	301	301
   query49	659	485	384	384
   query50	698	282	227	227
   query51	4425	4292	4195	4195
   query52	86	86	76	76
   query53	249	273	187	187
   query54	260	234	198	198
   query55	77	74	69	69
   query56	229	215	214	214
   query57	1391	1377	1284	1284
   query58	243	215	202	202
   query59	1593	1613	1468	1468
   query60	291	258	246	246
   query61	160	156	169	156
   query62	660	613	555	555
   query63	229	184	189	184
   query64	2511	824	669	669
   query65	
   query66	1779	487	360	360
   query67	30151	29934	29830	29830
   query68	
   query69	430	307	275	275
   query70	966	941	936	936
   query71	298	221	214	214
   query72	2850	2790	2429	2429
   query73	833	755	428	428
   query74	5032	4912	4734	4734
   query75	2753	2615	2279	2279
   query76	2323	1126	757	757
   query77	353	389	288	288
   query78	12950	12907	12379	12379
   query79	1425	1017	716	716
   query80	1318	497	407	407
   query81	521	282	234	234
   query82	625	157	126	126
   query83	313	273	244	244
   query84	261	139	110	110
   query85	904	508	451	451
   query86	434	305	287	287
   query87	3395	3335	3150	3150
   query88	3585	2718	2682	2682
   query89	428	368	323	323
   query90	2023	187	179	179
   query91	184	174	137	137
   query92	66	60	56	56
   query93	1111	986	558	558
   query94	718	345	298	298
   query95	665	479	340	340
   query96	1021	829	335	335
   query97	2665	2667	2528	2528
   query98	207	207	208	207
   query99	1120	1104	980	980
   Total cold run time: 253632 ms
   Total hot run time: 170469 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4450448196

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 169794 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit c090ab6440346db9f2c052585ec7b6dc593d0df3, data reload: false
   
   query5	4338	619	472	472
   query6	453	208	188	188
   query7	4847	565	298	298
   query8	379	223	211	211
   query9	8817	4165	4130	4130
   query10	444	315	277	277
   query11	6060	2349	2196	2196
   query12	167	103	100	100
   query13	1272	597	446	446
   query14	7375	5312	5005	5005
   query14_1	4324	4319	4310	4310
   query15	203	191	175	175
   query16	1046	427	458	427
   query17	1302	738	623	623
   query18	2745	500	366	366
   query19	330	187	150	150
   query20	119	109	106	106
   query21	239	154	137	137
   query22	13776	13883	14441	13883
   query23	17461	16625	16170	16170
   query23_1	16309	16255	16170	16170
   query24	7710	1751	1416	1416
   query24_1	1315	1343	1372	1343
   query25	603	486	395	395
   query26	1355	302	157	157
   query27	2563	613	333	333
   query28	4274	1966	1952	1952
   query29	1103	607	505	505
   query30	291	228	193	193
   query31	1103	1064	937	937
   query32	111	62	57	57
   query33	508	307	247	247
   query34	1166	1167	643	643
   query35	747	753	675	675
   query36	1292	1325	1208	1208
   query37	154	98	92	92
   query38	3196	3140	3036	3036
   query39	936	913	892	892
   query39_1	867	874	898	874
   query40	237	123	112	112
   query41	62	63	59	59
   query42	93	90	90	90
   query43	312	312	270	270
   query44	
   query45	190	186	177	177
   query46	1075	1210	729	729
   query47	2318	2312	2199	2199
   query48	408	419	286	286
   query49	629	482	390	390
   query50	702	296	225	225
   query51	4294	4391	4155	4155
   query52	89	88	75	75
   query53	245	264	186	186
   query54	277	223	196	196
   query55	77	74	71	71
   query56	238	236	218	218
   query57	1421	1388	1298	1298
   query58	257	207	209	207
   query59	1567	1636	1459	1459
   query60	293	257	246	246
   query61	158	157	149	149
   query62	660	607	559	559
   query63	229	183	185	183
   query64	2504	773	659	659
   query65	
   query66	1761	492	354	354
   query67	30095	29922	29819	29819
   query68	
   query69	427	297	271	271
   query70	944	945	947	945
   query71	309	221	208	208
   query72	2934	2789	2486	2486
   query73	803	765	422	422
   query74	5125	4916	4730	4730
   query75	2747	2652	2298	2298
   query76	2324	1149	786	786
   query77	360	388	298	298
   query78	12906	12918	12318	12318
   query79	1497	954	725	725
   query80	1364	511	410	410
   query81	535	274	241	241
   query82	679	160	127	127
   query83	341	273	246	246
   query84	280	139	112	112
   query85	920	496	426	426
   query86	438	307	283	283
   query87	3393	3331	3213	3213
   query88	3532	2704	2694	2694
   query89	418	370	326	326
   query90	1927	192	183	183
   query91	180	175	143	143
   query92	63	63	56	56
   query93	1124	960	558	558
   query94	728	363	290	290
   query95	661	376	439	376
   query96	1073	748	363	363
   query97	2693	2636	2531	2531
   query98	220	207	209	207
   query99	1104	1140	986	986
   Total cold run time: 253751 ms
   Total hot run time: 169794 ms
   ```
   </details>
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4560498825

   # FE UT Coverage Report
   Increment line coverage `  31.37% (96/306)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_9af6867af36b4da0dec52406116ba2d158e2e2b9/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_9af6867af36b4da0dec52406116ba2d158e2e2b9/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [Feature](scan) support runtime partition prune [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #62589:
URL: https://github.com/apache/doris/pull/62589#issuecomment-4563882796

   # FE UT Coverage Report
   Increment line coverage `  31.37% (96/306)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/62589_2ede6c8cafb5ff16b7121d7edac1018fc2e592e0/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/62589_2ede6c8cafb5ff16b7121d7edac1018fc2e592e0/fe_report/index.html)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org