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

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 61720ba4927 [branch-4.1][pick] Backport Paimon and Parquet scan 
improvements (#66359)
61720ba4927 is described below

commit 61720ba49278b6a7b954c250e581f26f6629edd6
Author: Gabriel <[email protected]>
AuthorDate: Mon Aug 3 13:02:10 2026 +0800

    [branch-4.1][pick] Backport Paimon and Parquet scan improvements (#66359)
    
    ### What problem does this PR solve?
    
    Original PRs (master merge order):
    
    - [#66244 — Reduce Paimon JNI read object
    allocations](https://github.com/apache/doris/pull/66244)
    - [#66263 — Fuse Parquet nested sparse selection
    planning](https://github.com/apache/doris/pull/66263)
    - [#66357 — Keep File Scanner V2 predicate pruning
    enabled](https://github.com/apache/doris/pull/66357)
    
    Backport the three changes to `branch-4.1` in their master integration
    order. This branch contains the complete final three-commit chain merged
    by #66357.
    
    The backport reduces Paimon JNI allocation overhead, fuses Parquet V2
    nested sparse-selection planning, and keeps safe FileScannerV2 predicate
    pruning independent of legacy session gates. It also adapts the
    FileScannerV2 test fixture to the `branch-4.1` row-descriptor
    nullability invariant.
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test
        - [x] Unit Test
        - [ ] Regression test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason
    
    - Behavior changed:
        - [ ] No.
    - [x] Yes. Safe FileScannerV2 partition and expression ZoneMap pruning
    is no longer disabled by legacy session gates.
    
    - Does this need documentation?
        - [x] No.
        - [ ] Yes.
    
    Verification:
    
    - FE Checkstyle: 28 modules passed with zero violations.
    - Paimon unit tests: 42 tests passed with zero failures or errors.
    - Focused BE ASAN unit tests: 6 tests passed.
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label
---
 be/benchmark/parquet/AGENTS.md                     |  19 +-
 be/benchmark/parquet/README.md                     |  34 ++-
 be/benchmark/parquet/benchmark_parquet_kernels.hpp | 268 ++++++++++++++++++++-
 be/benchmark/parquet/parquet_benchmark_scenarios.h |  26 +-
 be/src/exec/scan/file_scanner_v2.cpp               |   5 +-
 be/src/format_v2/parquet/parquet_statistics.cpp    |  10 +-
 .../parquet/reader/native/column_reader.cpp        |  39 +--
 be/src/format_v2/parquet/reader/native/common.cpp  |  84 +++++++
 be/src/format_v2/parquet/reader/native/common.h    |   8 +
 be/test/exec/scan/file_scanner_v2_test.cpp         |  62 +++++
 .../parquet/parquet_benchmark_scenarios_test.cpp   |  20 +-
 .../parquet/parquet_reader_control_test.cpp        |  75 ++++++
 be/test/format_v2/parquet/parquet_reader_test.cpp  | 252 +++++++++++++++++++
 .../format_v2/parquet/parquet_statistics_test.cpp  |  74 ++++++
 .../org/apache/doris/paimon/PaimonColumnValue.java | 131 ++++++++--
 .../apache/doris/paimon/PaimonColumnValueTest.java | 236 +++++++++++++++++-
 .../apache/doris/paimon/PaimonJniScannerTest.java  |  10 +
 .../java/org/apache/doris/qe/SessionVariable.java  |  16 +-
 gensrc/thrift/PaloInternalService.thrift           |   6 +-
 19 files changed, 1306 insertions(+), 69 deletions(-)

diff --git a/be/benchmark/parquet/AGENTS.md b/be/benchmark/parquet/AGENTS.md
index ba52658a966..6c2fb8c85df 100644
--- a/be/benchmark/parquet/AGENTS.md
+++ b/be/benchmark/parquet/AGENTS.md
@@ -43,10 +43,10 @@ be/output/lib/benchmark_test --benchmark_list_tests \
   | grep -c '^ParquetDecoder/'  # currently 228
 
 be/output/lib/benchmark_test --benchmark_list_tests \
-  | grep -c '^ParquetKernel/'   # currently 80
+  | grep -c '^ParquetKernel/'   # currently 92
 
 be/output/lib/benchmark_test --benchmark_list_tests \
-  | grep -c '^ParquetReader/'   # currently 152
+  | grep -c '^ParquetReader/'   # currently 167
 ```
 
 When running the binary directly from `be/build_RELEASE/bin`, make sure the 
JVM and third-party
@@ -120,13 +120,16 @@ cache to manufacture a cold run.
 | DELTA_LENGTH_BYTE_ARRAY | BYTE_ARRAY |
 | DELTA_BYTE_ARRAY | BYTE_ARRAY |
 
-`ParquetKernel` contains 80 cases across five SIMD-sensitive stages: 
BYTE_STREAM_SPLIT,
-DELTA_PREFIX_SUM, DICTIONARY_GATHER, NULLABLE_EXPAND, and RAW_PREDICATE. It 
covers the applicable
-four- and eight-byte types, three dictionary working-set sizes, 0% through 90% 
null rates with both
-placement patterns, and 0% through 100% raw-predicate selectivities.
+`ParquetKernel` contains 92 cases across six decode and selection stages: 
BYTE_STREAM_SPLIT,
+DELTA_PREFIX_SUM, DICTIONARY_GATHER, NULLABLE_EXPAND, RAW_PREDICATE, and 
NESTED_SELECTION. It covers
+the applicable four- and eight-byte types, three dictionary working-set sizes, 
0% through 90% null
+rates with both placement patterns, 0% through 100% raw-predicate 
selectivities, and 1%, 10%, and
+50% nested parent-row selectivities with both placement patterns. Nested 
selection registers the
+legacy and fused implementations in the same binary and validates both against 
an independent
+source-level oracle before timing.
 
 `ParquetReader` deliberately uses a single-variable matrix rather than a 
Cartesian product. After
-deduplication it contains 152 cases covering:
+deduplication it contains 167 cases covering:
 
 - operations: open-to-first-block, full scan, predicate scan, complex residual 
scan, limit 1, and
   limit 1000;
@@ -299,7 +302,7 @@ be simulated by silently changing the local reader 
benchmark.
 
 ## Current validation record
 
-The current expected registration counts are 228 decoder, 80 kernel, and 152 
reader cases. A smoke
+The current expected registration counts are 228 decoder, 92 kernel, and 167 
reader cases. A smoke
 run is an execution record only, not a reviewed performance baseline, because 
repetitions, host
 isolation, warmups, cache control, `perf` data, variance, and before/after 
comparison are not
 collected.
diff --git a/be/benchmark/parquet/README.md b/be/benchmark/parquet/README.md
index e6eab6362ce..fcbed4a548b 100644
--- a/be/benchmark/parquet/README.md
+++ b/be/benchmark/parquet/README.md
@@ -39,13 +39,16 @@ be/output/lib/benchmark_test \
 
 ## SIMD kernel cases
 
-`ParquetKernel` isolates the five SIMD-sensitive stages from reader setup and 
virtual consumer
+`ParquetKernel` isolates six decode and selection stages from reader setup and 
virtual consumer
 overhead: byte-stream-split transpose, delta prefix sum, numeric dictionary 
gather, nullable
-expansion, and raw predicate evaluation. It covers the applicable 4-byte and 
8-byte integer and
-floating-point physical types, raw-predicate selectivities from 0% through 
100%, and nullable
-rates from 0% through 90% with clustered and alternating placement. Dictionary 
gather uses 32-,
-4,096-, and 262,144-entry working sets to separate cache-resident and 
cache-miss-dominated
-behavior.
+expansion, raw predicate evaluation, and repeated-level sparse selection. It 
covers the applicable
+4-byte and 8-byte integer and floating-point physical types, raw-predicate 
selectivities from 0%
+through 100%, and nullable rates from 0% through 90% with clustered and 
alternating placement.
+Nested selection covers 1%, 10%, and 50% surviving parent rows with both 
placement patterns.
+Each nested-selection scenario registers both `impl_legacy` and `impl_fused`; 
both paths use the
+same source levels and are checked against an independent oracle before timing.
+Dictionary gather uses 32-, 4,096-, and 262,144-entry working sets to separate 
cache-resident and
+cache-miss-dominated behavior.
 
 ```shell
 be/output/lib/benchmark_test \
@@ -53,6 +56,25 @@ be/output/lib/benchmark_test \
   --benchmark_min_time=0.1s
 ```
 
+For a reproducible nested-selection comparison, build once and run the two 
implementations from
+that same binary in ABBA order. Pin every command to the same otherwise-idle 
CPU:
+
+```shell
+taskset -c 8 be/output/lib/benchmark_test \
+  --benchmark_filter='^ParquetKernel/nested_selection/.*/impl_legacy$' \
+  --benchmark_min_time=1s --benchmark_repetitions=10 \
+  --benchmark_report_aggregates_only=true \
+  --benchmark_out=nested-legacy-a1.json --benchmark_out_format=json
+
+taskset -c 8 be/output/lib/benchmark_test \
+  --benchmark_filter='^ParquetKernel/nested_selection/.*/impl_fused$' \
+  --benchmark_min_time=1s --benchmark_repetitions=10 \
+  --benchmark_report_aggregates_only=true \
+  --benchmark_out=nested-fused-b1.json --benchmark_out_format=json
+
+# Repeat fused as B2, then legacy as A2, changing only --benchmark_out.
+```
+
 ## Local reader cases
 
 `ParquetReader` measures local open-to-first-block, full scan, predicate scan, 
complex residual
diff --git a/be/benchmark/parquet/benchmark_parquet_kernels.hpp 
b/be/benchmark/parquet/benchmark_parquet_kernels.hpp
index e064e69f64f..619d58fd8cf 100644
--- a/be/benchmark/parquet/benchmark_parquet_kernels.hpp
+++ b/be/benchmark/parquet/benchmark_parquet_kernels.hpp
@@ -20,12 +20,15 @@
 #include <benchmark/benchmark.h>
 
 #include <algorithm>
+#include <climits>
 #include <cstddef>
 #include <cstdint>
 #include <string>
 #include <type_traits>
+#include <unordered_set>
 #include <vector>
 
+#include "format_v2/parquet/reader/native/common.h"
 #include "parquet_benchmark_scenarios.h"
 #include "util/byte_stream_split.h"
 #include "util/simd/parquet_kernels.h"
@@ -34,6 +37,247 @@ namespace doris::parquet_benchmark {
 namespace detail {
 
 constexpr size_t KERNEL_ROWS = 1UL << 16;
+constexpr size_t NESTED_VALUES_PER_ROW = 8;
+
+using NestedReadType = 
format::parquet::native::ColumnSelectVector::DataReadType;
+using NestedLevel = format::parquet::native::level_t;
+
+struct NestedSelectionOracle {
+    std::vector<NestedLevel> repetition_levels;
+    std::vector<NestedLevel> definition_levels;
+    NullMap selected_nulls;
+    std::vector<NestedReadType> reads;
+    size_t ancestor_null_count = 0;
+    size_t filtered_count = 0;
+};
+
+struct NestedSelectionScratch {
+    std::vector<NestedLevel> repetition_levels;
+    std::vector<NestedLevel> definition_levels;
+    std::vector<uint8_t> nested_filter_data;
+    std::vector<uint16_t> null_runs;
+    std::unordered_set<size_t> ancestor_null_indices;
+    format::parquet::native::FilterMap nested_filter;
+    format::parquet::native::ColumnSelectVector selection;
+    NullMap selected_nulls;
+    size_t ancestor_null_count = 0;
+};
+
+inline NestedSelectionOracle build_nested_selection_oracle(
+        const std::vector<NestedLevel>& repetition_levels,
+        const std::vector<NestedLevel>& definition_levels,
+        const std::vector<uint8_t>& parent_filter_data) {
+    // Derive expectations from source levels so validation cannot inherit a 
mistake from either
+    // measured implementation.
+    NestedSelectionOracle oracle;
+    size_t parent = 0;
+    for (size_t level = 0; level < repetition_levels.size(); ++level) {
+        if (level != 0 && repetition_levels[level] == 0) {
+            ++parent;
+        }
+        const bool selected = parent_filter_data[parent] != 0;
+        if (selected) {
+            oracle.repetition_levels.push_back(repetition_levels[level]);
+            oracle.definition_levels.push_back(definition_levels[level]);
+        }
+        if (definition_levels[level] < 2) {
+            ++oracle.ancestor_null_count;
+            continue;
+        }
+        const bool is_null = definition_levels[level] < 3;
+        if (selected) {
+            oracle.selected_nulls.push_back(static_cast<UInt8>(is_null));
+            oracle.reads.push_back(is_null ? NestedReadType::NULL_DATA : 
NestedReadType::CONTENT);
+        } else {
+            ++oracle.filtered_count;
+            oracle.reads.push_back(is_null ? NestedReadType::FILTERED_NULL
+                                           : NestedReadType::FILTERED_CONTENT);
+        }
+    }
+    return oracle;
+}
+
+inline void append_nested_null_run(std::vector<uint16_t>* null_runs, bool 
is_null,
+                                   size_t run_length, bool* previous_is_null) {
+    if (*previous_is_null == is_null && USHRT_MAX - null_runs->back() >= 
run_length) {
+        null_runs->back() += static_cast<uint16_t>(run_length);
+        return;
+    }
+    if (!(*previous_is_null ^ is_null)) {
+        null_runs->push_back(0);
+    }
+    while (run_length > USHRT_MAX) {
+        null_runs->push_back(USHRT_MAX);
+        null_runs->push_back(0);
+        run_length -= USHRT_MAX;
+    }
+    null_runs->push_back(static_cast<uint16_t>(run_length));
+    *previous_is_null = is_null;
+}
+
+inline Status run_legacy_nested_selection(NestedSelectionScratch* scratch,
+                                          format::parquet::native::FilterMap* 
parent_filter) {
+    // Keep the pre-fusion passes selectable in the same binary so comparisons 
share compiler,
+    // fixtures, and process state.
+    scratch->nested_filter_data.resize(scratch->repetition_levels.size());
+    size_t parent = 0;
+    for (size_t level = 0; level < scratch->repetition_levels.size(); ++level) 
{
+        if (level != 0 && scratch->repetition_levels[level] == 0) {
+            ++parent;
+        }
+        scratch->nested_filter_data[level] = 
parent_filter->filter_map_data()[parent];
+    }
+    
RETURN_IF_ERROR(scratch->nested_filter.init(scratch->nested_filter_data.data(),
+                                                
scratch->nested_filter_data.size(), false));
+
+    scratch->null_runs.clear();
+    scratch->null_runs.push_back(0);
+    scratch->ancestor_null_indices.clear();
+    bool previous_is_null = false;
+    size_t level = 0;
+    while (level < scratch->definition_levels.size()) {
+        const NestedLevel definition_level = scratch->definition_levels[level];
+        const size_t run_start = level++;
+        while (level < scratch->definition_levels.size() &&
+               scratch->definition_levels[level] == definition_level) {
+            ++level;
+        }
+        const size_t run_length = level - run_start;
+        if (definition_level < 2) {
+            for (size_t index = run_start; index < level; ++index) {
+                scratch->ancestor_null_indices.insert(index);
+            }
+            continue;
+        }
+        append_nested_null_run(&scratch->null_runs, definition_level < 3, 
run_length,
+                               &previous_is_null);
+    }
+    scratch->ancestor_null_count = scratch->ancestor_null_indices.size();
+    RETURN_IF_ERROR(scratch->selection.init(
+            scratch->null_runs, scratch->repetition_levels.size() - 
scratch->ancestor_null_count,
+            &scratch->selected_nulls, &scratch->nested_filter, 0, 
&scratch->ancestor_null_indices));
+
+    size_t output_level = 0;
+    for (size_t input_level = 0; input_level < 
scratch->repetition_levels.size(); ++input_level) {
+        if (scratch->nested_filter_data[input_level] != 0) {
+            scratch->repetition_levels[output_level] = 
scratch->repetition_levels[input_level];
+            scratch->definition_levels[output_level] = 
scratch->definition_levels[input_level];
+            ++output_level;
+        }
+    }
+    scratch->repetition_levels.resize(output_level);
+    scratch->definition_levels.resize(output_level);
+    return Status::OK();
+}
+
+inline Status run_nested_selection_once(NestedSelectionScratch* scratch,
+                                        format::parquet::native::FilterMap* 
parent_filter,
+                                        NestedSelectionImplementation 
implementation) {
+    if (implementation == NestedSelectionImplementation::LEGACY) {
+        return run_legacy_nested_selection(scratch, parent_filter);
+    }
+    return scratch->selection.init_nested(
+            &scratch->repetition_levels, &scratch->definition_levels, 0,
+            /*repeated_parent_def_level=*/2, /*definition_level=*/3, 
&scratch->selected_nulls,
+            parent_filter, 0, &scratch->ancestor_null_count);
+}
+
+inline Status validate_nested_selection(NestedSelectionScratch& scratch,
+                                        const NestedSelectionOracle& oracle) {
+    if (scratch.repetition_levels != oracle.repetition_levels ||
+        scratch.definition_levels != oracle.definition_levels ||
+        scratch.selected_nulls != oracle.selected_nulls ||
+        scratch.ancestor_null_count != oracle.ancestor_null_count ||
+        scratch.selection.num_filtered() != oracle.filtered_count) {
+        return Status::InternalError("nested selection differs from 
independent oracle");
+    }
+    std::vector<NestedReadType> actual_reads;
+    NestedReadType type;
+    size_t run_length = 0;
+    while ((run_length = scratch.selection.get_next_run<true>(&type)) != 0) {
+        actual_reads.insert(actual_reads.end(), run_length, type);
+    }
+    if (actual_reads != oracle.reads) {
+        return Status::InternalError("nested selection read sequence differs 
from oracle");
+    }
+    return Status::OK();
+}
+
+inline void run_nested_selection_kernel(benchmark::State& state, const 
KernelScenario& scenario) {
+    using format::parquet::native::ColumnSelectVector;
+    using format::parquet::native::FilterMap;
+    using format::parquet::native::level_t;
+
+    std::vector<level_t> source_repetition_levels;
+    std::vector<level_t> source_definition_levels;
+    source_repetition_levels.reserve(KERNEL_ROWS * NESTED_VALUES_PER_ROW);
+    source_definition_levels.reserve(KERNEL_ROWS * NESTED_VALUES_PER_ROW);
+    for (size_t row = 0; row < KERNEL_ROWS; ++row) {
+        if (row % 10 == 0) {
+            source_repetition_levels.push_back(0);
+            source_definition_levels.push_back(0);
+            continue;
+        }
+        for (size_t value = 0; value < NESTED_VALUES_PER_ROW; ++value) {
+            source_repetition_levels.push_back(value == 0 ? 0 : 1);
+            source_definition_levels.push_back((row + value) % 10 == 0 ? 2 : 
3);
+        }
+    }
+
+    const auto parent_selection =
+            make_selection_plan(KERNEL_ROWS, scenario.selectivity_percent, 
scenario.pattern);
+    std::vector<uint8_t> parent_filter_data(KERNEL_ROWS, 0);
+    visit_selected_rows(parent_selection,
+                        [&](size_t row) { parent_filter_data[row] = uint8_t 
{1}; });
+    FilterMap parent_filter;
+    auto status = parent_filter.init(parent_filter_data.data(), 
parent_filter_data.size(), false);
+    if (!status.ok()) {
+        state.SkipWithError(status.to_string().c_str());
+        return;
+    }
+
+    const auto oracle = build_nested_selection_oracle(source_repetition_levels,
+                                                      
source_definition_levels, parent_filter_data);
+    NestedSelectionScratch scratch;
+    scratch.repetition_levels = source_repetition_levels;
+    scratch.definition_levels = source_definition_levels;
+    status = run_nested_selection_once(&scratch, &parent_filter, 
scenario.nested_implementation);
+    if (status.ok()) {
+        status = validate_nested_selection(scratch, oracle);
+    }
+    if (!status.ok()) {
+        state.SkipWithError(status.to_string().c_str());
+        return;
+    }
+
+    for (auto _ : state) {
+        state.PauseTiming();
+        scratch.repetition_levels = source_repetition_levels;
+        scratch.definition_levels = source_definition_levels;
+        scratch.selected_nulls.clear();
+        state.ResumeTiming();
+        status =
+                run_nested_selection_once(&scratch, &parent_filter, 
scenario.nested_implementation);
+        if (!status.ok()) {
+            state.SkipWithError(status.to_string().c_str());
+            return;
+        }
+        auto* compacted_levels = scratch.repetition_levels.data();
+        size_t filtered_values = scratch.selection.num_filtered();
+        benchmark::DoNotOptimize(compacted_levels);
+        benchmark::DoNotOptimize(filtered_values);
+        benchmark::ClobberMemory();
+    }
+
+    state.SetItemsProcessed(static_cast<int64_t>(state.iterations()) *
+                            
static_cast<int64_t>(source_repetition_levels.size()));
+    state.SetBytesProcessed(
+            static_cast<int64_t>(state.iterations()) *
+            static_cast<int64_t>(source_repetition_levels.size() * 2 * 
sizeof(level_t)));
+    state.counters["parent_rows"] = static_cast<double>(KERNEL_ROWS);
+    state.counters["selected_parent_rows"] = 
static_cast<double>(parent_selection.selected_rows);
+    state.counters["level_entries"] = 
static_cast<double>(source_repetition_levels.size());
+}
 
 inline void decode_byte_stream_split(const uint8_t* src, size_t width, size_t 
offset,
                                      size_t num_values, size_t stride, 
uint8_t* dest) {
@@ -155,6 +399,9 @@ void run_kernel(benchmark::State& state, const 
KernelScenario& scenario) {
             }
         }
         break;
+    case Kernel::NESTED_SELECTION:
+        state.SkipWithError("nested selection uses its dedicated level 
kernel");
+        return;
     }
 
     for (auto _ : state) {
@@ -193,6 +440,8 @@ void run_kernel(benchmark::State& state, const 
KernelScenario& scenario) {
             simd::raw_compare(reinterpret_cast<const uint8_t*>(input.data()), 
input.size(), literal,
                               simd::RawComparisonOp::LT, matches.data());
             break;
+        case Kernel::NESTED_SELECTION:
+            break;
         }
         benchmark::ClobberMemory();
     }
@@ -207,13 +456,20 @@ void run_kernel(benchmark::State& state, const 
KernelScenario& scenario) {
 
 inline bool register_kernel_benchmarks() {
     for (const auto& scenario : kernel_scenarios()) {
-        const std::string name = "ParquetKernel/" + to_string(scenario.kernel) 
+ "/" +
-                                 to_string(scenario.value_type) + "/sel_" +
-                                 std::to_string(scenario.selectivity_percent) 
+ "/null_" +
-                                 std::to_string(scenario.null_percent) + "/" +
-                                 to_string(scenario.pattern) + "/dict_" +
-                                 std::to_string(scenario.dictionary_entries);
+        std::string name = "ParquetKernel/" + to_string(scenario.kernel) + "/" 
+
+                           to_string(scenario.value_type) + "/sel_" +
+                           std::to_string(scenario.selectivity_percent) + 
"/null_" +
+                           std::to_string(scenario.null_percent) + "/" +
+                           to_string(scenario.pattern) + "/dict_" +
+                           std::to_string(scenario.dictionary_entries);
+        if (scenario.kernel == Kernel::NESTED_SELECTION) {
+            name += "/impl_" + to_string(scenario.nested_implementation);
+        }
         benchmark::RegisterBenchmark(name.c_str(), [=](benchmark::State& 
state) {
+            if (scenario.kernel == Kernel::NESTED_SELECTION) {
+                run_nested_selection_kernel(state, scenario);
+                return;
+            }
             switch (scenario.value_type) {
             case ValueType::INT32:
                 run_kernel<int32_t>(state, scenario);
diff --git a/be/benchmark/parquet/parquet_benchmark_scenarios.h 
b/be/benchmark/parquet/parquet_benchmark_scenarios.h
index 900cb29a583..d2655b3380e 100644
--- a/be/benchmark/parquet/parquet_benchmark_scenarios.h
+++ b/be/benchmark/parquet/parquet_benchmark_scenarios.h
@@ -50,8 +50,10 @@ enum class Kernel {
     DELTA_PREFIX_SUM,
     DICTIONARY_GATHER,
     NULLABLE_EXPAND,
-    RAW_PREDICATE
+    RAW_PREDICATE,
+    NESTED_SELECTION
 };
+enum class NestedSelectionImplementation { LEGACY, FUSED };
 
 struct DecoderScenario {
     Encoding encoding;
@@ -77,6 +79,7 @@ struct KernelScenario {
     int null_percent;
     Pattern pattern;
     size_t dictionary_entries;
+    NestedSelectionImplementation nested_implementation = 
NestedSelectionImplementation::FUSED;
 };
 
 struct SelectionRange {
@@ -141,6 +144,15 @@ inline std::vector<KernelScenario> kernel_scenarios() {
                     {Kernel::RAW_PREDICATE, value_type, selectivity, 0, 
Pattern::ALTERNATING, 256});
         }
     }
+    for (const int selectivity : {1, 10, 50}) {
+        for (const auto pattern : {Pattern::CLUSTERED, Pattern::ALTERNATING}) {
+            for (const auto implementation :
+                 {NestedSelectionImplementation::LEGACY, 
NestedSelectionImplementation::FUSED}) {
+                scenarios.push_back({Kernel::NESTED_SELECTION, 
ValueType::INT32, selectivity, 10,
+                                     pattern, 256, implementation});
+            }
+        }
+    }
     return scenarios;
 }
 
@@ -374,6 +386,18 @@ inline std::string to_string(Kernel value) {
         return "nullable_expand";
     case Kernel::RAW_PREDICATE:
         return "raw_predicate";
+    case Kernel::NESTED_SELECTION:
+        return "nested_selection";
+    }
+    return "unknown";
+}
+
+inline std::string to_string(NestedSelectionImplementation value) {
+    switch (value) {
+    case NestedSelectionImplementation::LEGACY:
+        return "legacy";
+    case NestedSelectionImplementation::FUSED:
+        return "fused";
     }
     return "unknown";
 }
diff --git a/be/src/exec/scan/file_scanner_v2.cpp 
b/be/src/exec/scan/file_scanner_v2.cpp
index c568832d67c..6a7bf74571e 100644
--- a/be/src/exec/scan/file_scanner_v2.cpp
+++ b/be/src/exec/scan/file_scanner_v2.cpp
@@ -642,7 +642,10 @@ Status FileScannerV2::_prepare_table_reader_split(const 
TFileRangeDesc& range,
     VExprContextSPtrs conjuncts;
     RETURN_IF_ERROR(_build_table_conjuncts(&conjuncts));
     VExprContextSPtrs partition_prune_conjuncts;
-    if (_state->query_options().enable_runtime_filter_partition_prune) {
+    if (!partition_values.empty()) {
+        // A split without partition constants cannot be pruned here, so avoid 
cloning every
+        // conjunct solely for a consumer that must return immediately. 
FileScannerV2 otherwise
+        // keeps safe partition pruning enabled independently of the legacy 
session gate.
         RETURN_IF_ERROR(_build_table_conjuncts(&partition_prune_conjuncts));
     }
     RETURN_IF_ERROR(_table_reader->prepare_split({
diff --git a/be/src/format_v2/parquet/parquet_statistics.cpp 
b/be/src/format_v2/parquet/parquet_statistics.cpp
index 7d1afb39240..7f35b087910 100644
--- a/be/src/format_v2/parquet/parquet_statistics.cpp
+++ b/be/src/format_v2/parquet/parquet_statistics.cpp
@@ -452,11 +452,11 @@ std::optional<format::LocalColumnId> 
file_column_id_by_block_position(
     return std::nullopt;
 }
 
-bool has_expr_zonemap_filter(const format::FileScanRequest& request,
-                             const RuntimeState* runtime_state) {
-    if (!expr_zonemap::is_expr_zonemap_filter_enabled(runtime_state)) {
-        return false;
-    }
+bool has_expr_zonemap_filter(const format::FileScanRequest& request, const 
RuntimeState*) {
+    // FileScannerV2 metadata pruning is a fixed part of its scan pipeline and 
must not inherit
+    // the legacy scanner's expression ZoneMap session gate.
+    // TODO: Fence metadata pruning at the first unsafe/error-preserving 
conjunct so a later
+    // ZoneMap predicate cannot bypass its row-level evaluation.
     for (const auto& conjunct : request.conjuncts) {
         if (conjunct != nullptr && conjunct->root() != nullptr &&
             conjunct->root()->can_evaluate_zonemap_filter()) {
diff --git a/be/src/format_v2/parquet/reader/native/column_reader.cpp 
b/be/src/format_v2/parquet/reader/native/column_reader.cpp
index e66495dfd7c..f762cb0f9d3 100644
--- a/be/src/format_v2/parquet/reader/native/column_reader.cpp
+++ b/be/src/format_v2/parquet/reader/native/column_reader.cpp
@@ -1066,7 +1066,15 @@ Status ScalarColumnReader<IN_COLLECTION, 
OFFSET_INDEX>::_read_nested_column(
 
     auto read_and_fill_data = [&](size_t before_rep_level_sz, size_t 
filter_map_index) {
         RETURN_IF_ERROR(_chunk_reader->fill_def(_def_levels));
-        if (filter_map.has_filter()) {
+        const bool fuse_nested_selection = filter_map.has_filter() && 
!filter_map.filter_all();
+        size_t ancestor_null_count = 0;
+        if (fuse_nested_selection) {
+            SCOPED_RAW_TIMER(&_decode_null_map_time);
+            RETURN_IF_ERROR(_select_vector.init_nested(
+                    &_rep_levels, &_def_levels, before_rep_level_sz,
+                    _field_schema->repeated_parent_def_level, 
_field_schema->definition_level,
+                    map_data_column, &filter_map, filter_map_index, 
&ancestor_null_count));
+        } else if (filter_map.has_filter()) {
             RETURN_IF_ERROR(gen_filter_map(filter_map, filter_map_index, 
before_rep_level_sz,
                                            _rep_levels.size(), 
_nested_filter_map_data,
                                            &_nested_filter_map));
@@ -1075,17 +1083,20 @@ Status ScalarColumnReader<IN_COLLECTION, 
OFFSET_INDEX>::_read_nested_column(
                     nullptr, _rep_levels.size() - before_rep_level_sz, false));
         }
 
-        _null_run_lengths.clear();
-        _ancestor_null_indices.clear();
-        RETURN_IF_ERROR(gen_nested_null_map(before_rep_level_sz, 
_rep_levels.size(),
-                                            _null_run_lengths, 
_ancestor_null_indices));
+        if (!fuse_nested_selection) {
+            _null_run_lengths.clear();
+            _ancestor_null_indices.clear();
+            RETURN_IF_ERROR(gen_nested_null_map(before_rep_level_sz, 
_rep_levels.size(),
+                                                _null_run_lengths, 
_ancestor_null_indices));
+            ancestor_null_count = _ancestor_null_indices.size();
 
-        {
-            SCOPED_RAW_TIMER(&_decode_null_map_time);
-            RETURN_IF_ERROR(_select_vector.init(
-                    _null_run_lengths,
-                    _rep_levels.size() - before_rep_level_sz - 
_ancestor_null_indices.size(),
-                    map_data_column, &_nested_filter_map, 0, 
&_ancestor_null_indices));
+            {
+                SCOPED_RAW_TIMER(&_decode_null_map_time);
+                RETURN_IF_ERROR(_select_vector.init(
+                        _null_run_lengths,
+                        _rep_levels.size() - before_rep_level_sz - 
ancestor_null_count,
+                        map_data_column, &_nested_filter_map, 0, 
&_ancestor_null_indices));
+            }
         }
 
         DORIS_CHECK(_serde != nullptr);
@@ -1104,10 +1115,10 @@ Status ScalarColumnReader<IN_COLLECTION, 
OFFSET_INDEX>::_read_nested_column(
                                           map_data_column, 
materialization_start_row);
         }
         RETURN_IF_ERROR(status);
-        if (!_ancestor_null_indices.empty()) {
-            
RETURN_IF_ERROR(_chunk_reader->skip_values(_ancestor_null_indices.size(), 
false));
+        if (ancestor_null_count != 0) {
+            RETURN_IF_ERROR(_chunk_reader->skip_values(ancestor_null_count, 
false));
         }
-        if (filter_map.has_filter()) {
+        if (filter_map.has_filter() && !fuse_nested_selection) {
             auto new_rep_sz = before_rep_level_sz;
             for (size_t idx = before_rep_level_sz; idx < _rep_levels.size(); 
idx++) {
                 if (_nested_filter_map_data[idx - before_rep_level_sz]) {
diff --git a/be/src/format_v2/parquet/reader/native/common.cpp 
b/be/src/format_v2/parquet/reader/native/common.cpp
index f048d2b878e..c50488575e4 100644
--- a/be/src/format_v2/parquet/reader/native/common.cpp
+++ b/be/src/format_v2/parquet/reader/native/common.cpp
@@ -193,4 +193,88 @@ Status ColumnSelectVector::init(const 
std::vector<uint16_t>& run_length_null_map
     return Status::OK();
 }
 
+void ColumnSelectVector::reset(bool has_filter) {
+    _data_map.clear();
+    _run_length_null_map = nullptr;
+    _has_filter = has_filter;
+    _num_values = 0;
+    _num_nulls = 0;
+    _num_filtered = 0;
+    _read_index = 0;
+}
+
+Status ColumnSelectVector::init_nested(std::vector<level_t>* repetition_levels,
+                                       std::vector<level_t>* definition_levels,
+                                       size_t level_start_index, level_t 
repeated_parent_def_level,
+                                       level_t definition_level, NullMap* 
null_map,
+                                       FilterMap* parent_filter, size_t 
filter_map_index,
+                                       size_t* ancestor_null_count) {
+    if (repetition_levels == nullptr || definition_levels == nullptr || 
parent_filter == nullptr ||
+        ancestor_null_count == nullptr) {
+        return Status::InvalidArgument("Nested selection requires non-null 
input state");
+    }
+    if (repetition_levels->size() != definition_levels->size() ||
+        level_start_index > repetition_levels->size()) {
+        return Status::InvalidArgument(
+                "Nested selection has invalid level bounds: repetition={}, 
definition={}, start={}",
+                repetition_levels->size(), definition_levels->size(), 
level_start_index);
+    }
+    if (!parent_filter->has_filter() || parent_filter->filter_all()) {
+        return Status::InvalidArgument("Nested selection requires a partial 
parent filter");
+    }
+    if (level_start_index == repetition_levels->size()) {
+        reset(true);
+        *ancestor_null_count = 0;
+        return Status::OK();
+    }
+    if (filter_map_index >= parent_filter->filter_map_size()) {
+        return Status::InvalidArgument("Nested filter row {} exceeds filter 
map size {}",
+                                       filter_map_index, 
parent_filter->filter_map_size());
+    }
+    reset(true);
+    _data_map.reserve(repetition_levels->size() - level_start_index);
+    *ancestor_null_count = 0;
+    size_t current_parent = filter_map_index;
+    size_t output_level = level_start_index;
+    for (size_t input_level = level_start_index; input_level < 
repetition_levels->size();
+         ++input_level) {
+        if (input_level != level_start_index && 
(*repetition_levels)[input_level] == 0) {
+            ++current_parent;
+            if (current_parent >= parent_filter->filter_map_size()) {
+                return Status::InvalidArgument("Nested filter row {} exceeds 
filter map size {}",
+                                               current_parent, 
parent_filter->filter_map_size());
+            }
+        }
+        const bool selected = parent_filter->filter_map_data()[current_parent] 
!= 0;
+        if (selected) {
+            (*repetition_levels)[output_level] = 
(*repetition_levels)[input_level];
+            (*definition_levels)[output_level] = 
(*definition_levels)[input_level];
+            ++output_level;
+        }
+        // An ancestor-null placeholder belongs to the surviving parent's 
level shape, but it must
+        // never consume a leaf selection entry because no physical leaf value 
exists for it.
+        const level_t def_level = (*definition_levels)[input_level];
+        if (def_level < repeated_parent_def_level) {
+            ++*ancestor_null_count;
+            continue;
+        }
+        const bool is_null = def_level < definition_level;
+        ++_num_values;
+        _num_nulls += is_null;
+        if (!selected) {
+            ++_num_filtered;
+        } else if (null_map != nullptr) {
+            null_map->push_back(static_cast<UInt8>(is_null));
+        }
+        DataReadType read_type = is_null ? FILTERED_NULL : FILTERED_CONTENT;
+        if (selected) {
+            read_type = is_null ? NULL_DATA : CONTENT;
+        }
+        _data_map.push_back(read_type);
+    }
+    repetition_levels->resize(output_level);
+    definition_levels->resize(output_level);
+    return Status::OK();
+}
+
 } // namespace doris::format::parquet::native
diff --git a/be/src/format_v2/parquet/reader/native/common.h 
b/be/src/format_v2/parquet/reader/native/common.h
index 4ad204da2ff..eb6848ee299 100644
--- a/be/src/format_v2/parquet/reader/native/common.h
+++ b/be/src/format_v2/parquet/reader/native/common.h
@@ -65,6 +65,12 @@ public:
                 NullMap* null_map, FilterMap* filter_map, size_t 
filter_map_index,
                 const std::unordered_set<size_t>* skipped_indices = nullptr);
 
+    Status init_nested(std::vector<level_t>* repetition_levels,
+                       std::vector<level_t>* definition_levels, size_t 
level_start_index,
+                       level_t repeated_parent_def_level, level_t 
definition_level,
+                       NullMap* null_map, FilterMap* parent_filter, size_t 
filter_map_index,
+                       size_t* ancestor_null_count);
+
     size_t num_values() const { return _num_values; }
     size_t num_nulls() const { return _num_nulls; }
     size_t num_filtered() const { return _num_filtered; }
@@ -99,6 +105,8 @@ public:
     }
 
 private:
+    void reset(bool has_filter);
+
     std::vector<DataReadType> _data_map;
     const std::vector<uint16_t>* _run_length_null_map = nullptr;
     bool _has_filter = false;
diff --git a/be/test/exec/scan/file_scanner_v2_test.cpp 
b/be/test/exec/scan/file_scanner_v2_test.cpp
index 2950fa4911d..c73ed981491 100644
--- a/be/test/exec/scan/file_scanner_v2_test.cpp
+++ b/be/test/exec/scan/file_scanner_v2_test.cpp
@@ -141,6 +141,18 @@ private:
     std::shared_ptr<RetryableCloseState> _state;
 };
 
+class CapturingSplitTableReader final : public format::TableReader {
+public:
+    Status prepare_split(const format::SplitReadOptions& options) override {
+        conjunct_count = options.conjuncts.has_value() ? 
options.conjuncts->size() : 0;
+        partition_prune_conjunct_count = 
options.partition_prune_conjuncts.size();
+        return Status::OK();
+    }
+
+    size_t conjunct_count = 0;
+    size_t partition_prune_conjunct_count = 0;
+};
+
 VExprSPtr slot_ref(int slot_id, int column_id, DataTypePtr type, const 
std::string& name) {
     return VSlotRef::create_shared(slot_id, column_id, -1, std::move(type), 
name);
 }
@@ -445,6 +457,56 @@ TEST(FileScannerV2Test, 
FailedTableReaderCloseCanBeRetriedThroughScanner) {
     EXPECT_EQ(close_state->close_calls, 2);
 }
 
+TEST(FileScannerV2Test, 
PartitionPruningRemainsEnabledWhenSessionSwitchIsFalse) {
+    TQueryOptions query_options;
+    query_options.__set_enable_runtime_filter_partition_prune(false);
+    RuntimeState state {query_options, TQueryGlobals()};
+    ObjectPool pool;
+    TDescriptorTable thrift_descriptors;
+    TTupleDescriptor tuple_descriptor;
+    tuple_descriptor.id = 0;
+    tuple_descriptor.byteSize = 0;
+    tuple_descriptor.numNullBytes = 0;
+    thrift_descriptors.tupleDescriptors.push_back(tuple_descriptor);
+    DescriptorTbl* descriptors = nullptr;
+    ASSERT_TRUE(DescriptorTbl::create(&pool, thrift_descriptors, 
&descriptors).ok());
+    TPlanNode plan_node;
+    plan_node.node_id = 0;
+    plan_node.node_type = TPlanNodeType::FILE_SCAN_NODE;
+    plan_node.num_children = 0;
+    plan_node.limit = -1;
+    plan_node.row_tuples.push_back(0);
+    // RowDescriptor requires one nullability entry for every row tuple on 
branch-4.1.
+    plan_node.nullable_tuples.push_back(false);
+    plan_node.file_scan_node.tuple_id = 0;
+    plan_node.__isset.file_scan_node = true;
+    FileScanOperatorX parent(&pool, plan_node, 0, *descriptors, 1);
+    FileScanLocalState local_state(&state, &parent);
+    RuntimeProfile profile("file_scanner_v2_partition_prune");
+    auto table_reader = std::make_unique<CapturingSplitTableReader>();
+    auto* captured = table_reader.get();
+    FileScannerV2 scanner(&state, &profile, std::move(table_reader));
+    scanner._local_state = &local_state;
+
+    TFileScanRangeParams params;
+    params.__set_format_type(TFileFormatType::FORMAT_PARQUET);
+    scanner._params = &params;
+    scanner._slot_id_to_global_index.emplace(7, format::GlobalIndex(0));
+    scanner._conjuncts = {VExprContext::create_shared(
+            slot_ref(7, 7, std::make_shared<DataTypeInt32>(), 
"partition_col"))};
+
+    const auto range = range_with_format("hive", 
TFileFormatType::FORMAT_PARQUET);
+    ASSERT_TRUE(scanner._prepare_table_reader_split(range, {}).ok());
+    EXPECT_EQ(captured->conjunct_count, 1);
+    EXPECT_EQ(captured->partition_prune_conjunct_count, 0);
+
+    ASSERT_TRUE(scanner._prepare_table_reader_split(
+                               range, {{"partition_col", 
Field::create_field<TYPE_INT>(1)}})
+                        .ok());
+    EXPECT_EQ(captured->conjunct_count, 1);
+    EXPECT_EQ(captured->partition_prune_conjunct_count, 1);
+}
+
 // Scenario: Once FileScannerV2 is selected, an unsupported range must fail 
instead of falling back
 // to FileScanner.
 TEST(FileScannerV2Test, ValidateScanRangeRejectsUnsupportedRange) {
diff --git a/be/test/format_v2/parquet/parquet_benchmark_scenarios_test.cpp 
b/be/test/format_v2/parquet/parquet_benchmark_scenarios_test.cpp
index 89e1f56ac3e..72ccbf44844 100644
--- a/be/test/format_v2/parquet/parquet_benchmark_scenarios_test.cpp
+++ b/be/test/format_v2/parquet/parquet_benchmark_scenarios_test.cpp
@@ -66,7 +66,7 @@ TEST(ParquetBenchmarkScenariosTest, 
DecoderMatrixCoversNativeEncodingAndTypeFami
 
 TEST(ParquetBenchmarkScenariosTest, 
KernelMatrixCoversEverySimdStageAndBoundaryShape) {
     const auto scenarios = kernel_scenarios();
-    EXPECT_EQ(scenarios.size(), size_t {80});
+    EXPECT_EQ(scenarios.size(), size_t {92});
     const std::map<Kernel, std::vector<ValueType>> expected_types {
             {Kernel::BYTE_STREAM_SPLIT, {ValueType::FLOAT, ValueType::DOUBLE}},
             {Kernel::DELTA_PREFIX_SUM, {ValueType::INT32, ValueType::INT64}},
@@ -76,6 +76,7 @@ TEST(ParquetBenchmarkScenariosTest, 
KernelMatrixCoversEverySimdStageAndBoundaryS
              {ValueType::INT32, ValueType::INT64, ValueType::FLOAT, 
ValueType::DOUBLE}},
             {Kernel::RAW_PREDICATE,
              {ValueType::INT32, ValueType::INT64, ValueType::FLOAT, 
ValueType::DOUBLE}},
+            {Kernel::NESTED_SELECTION, {ValueType::INT32}},
     };
     for (const auto& [kernel, value_types] : expected_types) {
         for (const auto value_type : value_types) {
@@ -109,6 +110,23 @@ TEST(ParquetBenchmarkScenariosTest, 
KernelMatrixCoversEverySimdStageAndBoundaryS
     }
 }
 
+TEST(ParquetBenchmarkScenariosTest, 
NestedSelectionCoversSparseParentSurvivors) {
+    const auto scenarios = kernel_scenarios();
+    for (const int selectivity : {1, 10, 50}) {
+        for (const auto pattern : {Pattern::CLUSTERED, Pattern::ALTERNATING}) {
+            for (const auto implementation :
+                 {NestedSelectionImplementation::LEGACY, 
NestedSelectionImplementation::FUSED}) {
+                EXPECT_TRUE(std::ranges::any_of(scenarios, [&](const 
KernelScenario& scenario) {
+                    return scenario.kernel == Kernel::NESTED_SELECTION &&
+                           scenario.selectivity_percent == selectivity &&
+                           scenario.null_percent == 10 && scenario.pattern == 
pattern &&
+                           scenario.nested_implementation == implementation;
+                })) << "missing nested sparse-selection implementation";
+            }
+        }
+    }
+}
+
 TEST(ParquetBenchmarkScenariosTest, 
ReaderMatrixCoversNullableSparseAndProjectionAxes) {
     const auto scenarios = reader_scenarios();
     // Keep the exact count aligned with the upstream complex-residual 
scenario retained by rebase.
diff --git a/be/test/format_v2/parquet/parquet_reader_control_test.cpp 
b/be/test/format_v2/parquet/parquet_reader_control_test.cpp
index 9de951ec5e4..0c70ddaf01e 100644
--- a/be/test/format_v2/parquet/parquet_reader_control_test.cpp
+++ b/be/test/format_v2/parquet/parquet_reader_control_test.cpp
@@ -31,6 +31,7 @@
 #include "format_v2/parquet/parquet_scan.h"
 #include "format_v2/parquet/reader/column_reader.h"
 #include "format_v2/parquet/reader/global_rowid_column_reader.h"
+#include "format_v2/parquet/reader/native/common.h"
 #include "format_v2/parquet/reader/row_position_column_reader.h"
 #include "format_v2/parquet/selection_vector.h"
 #include "storage/utils.h"
@@ -184,6 +185,80 @@ TEST(SelectionVectorTest, 
IdentitySelectionDoesNotMaterializeFilter) {
     EXPECT_EQ(filter, nullptr);
 }
 
+TEST(NativeNestedSelectionTest, 
BuildsSelectionAndCompactsSurvivingParentLevels) {
+    using native::ColumnSelectVector;
+    using native::FilterMap;
+    using native::level_t;
+
+    std::vector<level_t> repetition_levels {0, 1, 1, 0, 0, 1};
+    std::vector<level_t> definition_levels {3, 2, 1, 3, 0, 3};
+    std::vector<uint8_t> parent_filter_data {1, 0, 1};
+    FilterMap parent_filter;
+    ASSERT_TRUE(
+            parent_filter.init(parent_filter_data.data(), 
parent_filter_data.size(), false).ok());
+
+    ColumnSelectVector selection;
+    NullMap selected_nulls;
+    size_t ancestor_null_count = 0;
+    ASSERT_TRUE(selection
+                        .init_nested(&repetition_levels, &definition_levels, 0,
+                                     /*repeated_parent_def_level=*/2,
+                                     /*definition_level=*/3, &selected_nulls, 
&parent_filter, 0,
+                                     &ancestor_null_count)
+                        .ok());
+
+    EXPECT_EQ(ancestor_null_count, 2);
+    EXPECT_EQ(selection.num_values(), 4);
+    EXPECT_EQ(selection.num_nulls(), 1);
+    EXPECT_EQ(selection.num_filtered(), 1);
+    EXPECT_EQ(selected_nulls, NullMap({0, 1, 0}));
+    EXPECT_EQ(repetition_levels, (std::vector<level_t> {0, 1, 1, 0, 1}));
+    EXPECT_EQ(definition_levels, (std::vector<level_t> {3, 2, 1, 0, 3}));
+
+    ColumnSelectVector::DataReadType type;
+    EXPECT_EQ(selection.get_next_run<true>(&type), 1);
+    EXPECT_EQ(type, ColumnSelectVector::CONTENT);
+    EXPECT_EQ(selection.get_next_run<true>(&type), 1);
+    EXPECT_EQ(type, ColumnSelectVector::NULL_DATA);
+    EXPECT_EQ(selection.get_next_run<true>(&type), 1);
+    EXPECT_EQ(type, ColumnSelectVector::FILTERED_CONTENT);
+    EXPECT_EQ(selection.get_next_run<true>(&type), 1);
+    EXPECT_EQ(type, ColumnSelectVector::CONTENT);
+    EXPECT_EQ(selection.get_next_run<true>(&type), 0);
+}
+
+TEST(NativeNestedSelectionTest, PreservesPriorLevelsAcrossPageContinuation) {
+    using native::ColumnSelectVector;
+    using native::FilterMap;
+    using native::level_t;
+
+    std::vector<level_t> repetition_levels {0, 1, 1, 0, 1};
+    std::vector<level_t> definition_levels {3, 3, 2, 3, 1};
+    std::vector<uint8_t> parent_filter_data {1, 0};
+    FilterMap parent_filter;
+    ASSERT_TRUE(
+            parent_filter.init(parent_filter_data.data(), 
parent_filter_data.size(), false).ok());
+
+    ColumnSelectVector selection;
+    NullMap selected_nulls;
+    size_t ancestor_null_count = 0;
+    ASSERT_TRUE(selection
+                        .init_nested(&repetition_levels, &definition_levels,
+                                     /*level_start_index=*/2,
+                                     /*repeated_parent_def_level=*/2,
+                                     /*definition_level=*/3, &selected_nulls, 
&parent_filter, 0,
+                                     &ancestor_null_count)
+                        .ok());
+
+    EXPECT_EQ(ancestor_null_count, 1);
+    EXPECT_EQ(selection.num_values(), 2);
+    EXPECT_EQ(selection.num_nulls(), 1);
+    EXPECT_EQ(selection.num_filtered(), 1);
+    EXPECT_EQ(selected_nulls, NullMap({1}));
+    EXPECT_EQ(repetition_levels, (std::vector<level_t> {0, 1, 1}));
+    EXPECT_EQ(definition_levels, (std::vector<level_t> {3, 3, 2}));
+}
+
 TEST(ParquetColumnReaderControlTest, BaseSelectUsesSkipReadRanges) {
     CursorColumnReader reader;
     SelectionVector selection(3);
diff --git a/be/test/format_v2/parquet/parquet_reader_test.cpp 
b/be/test/format_v2/parquet/parquet_reader_test.cpp
index af0897d6ef2..bc73055ba88 100644
--- a/be/test/format_v2/parquet/parquet_reader_test.cpp
+++ b/be/test/format_v2/parquet/parquet_reader_test.cpp
@@ -21,7 +21,9 @@
 #include <arrow/io/api.h>
 #include <gtest/gtest.h>
 #include <parquet/api/reader.h>
+#include <parquet/api/writer.h>
 #include <parquet/arrow/writer.h>
+#include <parquet/column_page.h>
 #include <parquet/page_index.h>
 
 #include <array>
@@ -835,6 +837,123 @@ std::shared_ptr<arrow::Array> 
build_nullable_struct_with_list_array(bool list_fi
     return finish_array(&builder);
 }
 
+constexpr size_t SPANNING_NESTED_VALUES = 128;
+
+void write_sparse_filter_nested_parquet_file(const std::string& file_path) {
+    auto file_result = arrow::io::FileOutputStream::Open(file_path);
+    ASSERT_TRUE(file_result.ok()) << file_result.status();
+    std::shared_ptr<arrow::io::FileOutputStream> out = *file_result;
+
+    const auto id = ::parquet::schema::PrimitiveNode::Make("id", 
::parquet::Repetition::REQUIRED,
+                                                           
::parquet::LogicalType::None(),
+                                                           
::parquet::Type::INT32);
+    const auto map_key = ::parquet::schema::PrimitiveNode::Make(
+            "key", ::parquet::Repetition::REQUIRED, 
::parquet::LogicalType::None(),
+            ::parquet::Type::INT32);
+    const auto map_value = ::parquet::schema::PrimitiveNode::Make(
+            "value", ::parquet::Repetition::OPTIONAL, 
::parquet::LogicalType::String(),
+            ::parquet::Type::BYTE_ARRAY);
+    const auto key_value = ::parquet::schema::GroupNode::Make(
+            "key_value", ::parquet::Repetition::REPEATED, {map_key, 
map_value});
+    const auto map = ::parquet::schema::GroupNode::Make("m", 
::parquet::Repetition::OPTIONAL,
+                                                        {key_value}, 
::parquet::LogicalType::Map());
+    const auto element = ::parquet::schema::PrimitiveNode::Make(
+            "element", ::parquet::Repetition::OPTIONAL, 
::parquet::LogicalType::None(),
+            ::parquet::Type::INT32);
+    const auto list =
+            ::parquet::schema::GroupNode::Make("list", 
::parquet::Repetition::REPEATED, {element});
+    const auto items = ::parquet::schema::GroupNode::Make("items", 
::parquet::Repetition::OPTIONAL,
+                                                          {list}, 
::parquet::LogicalType::List());
+    const auto marker = ::parquet::schema::PrimitiveNode::Make(
+            "marker", ::parquet::Repetition::REQUIRED, 
::parquet::LogicalType::None(),
+            ::parquet::Type::INT32);
+    const auto nested_struct = ::parquet::schema::GroupNode::Make(
+            "s", ::parquet::Repetition::OPTIONAL, {items, marker});
+    const auto schema_node = ::parquet::schema::GroupNode::Make(
+            "schema", ::parquet::Repetition::REQUIRED, {id, map, 
nested_struct});
+    const auto schema = 
std::static_pointer_cast<::parquet::schema::GroupNode>(schema_node);
+
+    ::parquet::WriterProperties::Builder builder;
+    builder.version(::parquet::ParquetVersion::PARQUET_2_6);
+    // V2 and page-index writers preserve record boundaries, so use V1 here to 
produce the
+    // continuation pages that the reader must still handle correctly.
+    builder.data_page_version(::parquet::ParquetDataPageVersion::V1);
+    builder.compression(::parquet::Compression::UNCOMPRESSED);
+    builder.disable_dictionary();
+    builder.write_batch_size(8);
+    builder.data_pagesize(64);
+    auto writer = ::parquet::ParquetFileWriter::Open(out, schema, 
builder.build());
+    auto* row_group = writer->AppendRowGroup();
+
+    auto* id_writer = 
static_cast<::parquet::Int32Writer*>(row_group->NextColumn());
+    const int32_t ids[] = {1, 2, 3, 4, 5, 6};
+    EXPECT_EQ(id_writer->WriteBatch(6, nullptr, nullptr, ids), 6);
+    id_writer->Close();
+
+    std::vector<int16_t> map_repetition_levels {0, 0, 0, 0};
+    std::vector<int16_t> map_key_definition_levels {2, 0, 2, 1};
+    std::vector<int16_t> map_value_definition_levels {3, 0, 2, 1};
+    std::vector<int32_t> map_keys {10, 30};
+    std::vector<::parquet::ByteArray> map_values;
+    const std::string rejected_value = "rejected";
+    const std::string selected_value = "selected-wide-value";
+    map_values.emplace_back(static_cast<uint32_t>(rejected_value.size()),
+                            reinterpret_cast<const 
uint8_t*>(rejected_value.data()));
+    for (size_t value = 0; value < SPANNING_NESTED_VALUES; ++value) {
+        map_repetition_levels.push_back(value == 0 ? 0 : 1);
+        map_key_definition_levels.push_back(2);
+        map_value_definition_levels.push_back(3);
+        map_keys.push_back(static_cast<int32_t>(5000 + value));
+        map_values.emplace_back(static_cast<uint32_t>(selected_value.size()),
+                                reinterpret_cast<const 
uint8_t*>(selected_value.data()));
+    }
+    map_repetition_levels.push_back(0);
+    map_key_definition_levels.push_back(2);
+    map_value_definition_levels.push_back(2);
+    map_keys.push_back(6000);
+
+    auto* map_key_writer = 
static_cast<::parquet::Int32Writer*>(row_group->NextColumn());
+    
EXPECT_EQ(map_key_writer->WriteBatch(static_cast<int64_t>(map_repetition_levels.size()),
+                                         map_key_definition_levels.data(),
+                                         map_repetition_levels.data(), 
map_keys.data()),
+              static_cast<int64_t>(map_keys.size()));
+    map_key_writer->Close();
+    auto* map_value_writer = 
static_cast<::parquet::ByteArrayWriter*>(row_group->NextColumn());
+    
EXPECT_EQ(map_value_writer->WriteBatch(static_cast<int64_t>(map_repetition_levels.size()),
+                                           map_value_definition_levels.data(),
+                                           map_repetition_levels.data(), 
map_values.data()),
+              static_cast<int64_t>(map_values.size()));
+    map_value_writer->Close();
+
+    std::vector<int16_t> element_repetition_levels {0, 0, 0, 1, 0};
+    std::vector<int16_t> element_definition_levels {4, 0, 4, 3, 1};
+    std::vector<int32_t> element_values {10, 30};
+    for (size_t value = 0; value < SPANNING_NESTED_VALUES; ++value) {
+        element_repetition_levels.push_back(value == 0 ? 0 : 1);
+        element_definition_levels.push_back(4);
+        element_values.push_back(static_cast<int32_t>(5000 + value));
+    }
+    element_repetition_levels.push_back(0);
+    element_definition_levels.push_back(4);
+    element_values.push_back(6000);
+    element_repetition_levels.push_back(1);
+    element_definition_levels.push_back(3);
+
+    auto* element_writer = 
static_cast<::parquet::Int32Writer*>(row_group->NextColumn());
+    
EXPECT_EQ(element_writer->WriteBatch(static_cast<int64_t>(element_repetition_levels.size()),
+                                         element_definition_levels.data(),
+                                         element_repetition_levels.data(), 
element_values.data()),
+              static_cast<int64_t>(element_values.size()));
+    element_writer->Close();
+    auto* marker_writer = 
static_cast<::parquet::Int32Writer*>(row_group->NextColumn());
+    const int16_t marker_definition_levels[] = {1, 0, 1, 1, 1, 1};
+    const int32_t marker_values[] = {10, 30, 40, 50, 60};
+    EXPECT_EQ(marker_writer->WriteBatch(6, marker_definition_levels, nullptr, 
marker_values), 5);
+    marker_writer->Close();
+    row_group->Close();
+    writer->Close();
+}
+
 void write_nullable_map_parquet_file(const std::string& file_path) {
     auto array = build_nullable_int_string_map_array();
     auto field = arrow::field("arr", array->type(), true);
@@ -1645,6 +1764,139 @@ TEST_F(NewParquetReaderTest, 
NativeComplexColumnsMaterializeDirectlyAcrossBatchC
     EXPECT_GT(profile.get_counter("NestedBatches")->value(), 0);
 }
 
+TEST_F(NewParquetReaderTest, 
SparseFilterPreservesNestedShapeAcrossPhysicalPages) {
+    write_sparse_filter_nested_parquet_file(_file_path);
+
+    auto physical_reader = ::parquet::ParquetFileReader::OpenFile(_file_path, 
false);
+    auto row_group_metadata = physical_reader->metadata()->RowGroup(0);
+    auto row_group_reader = physical_reader->RowGroup(0);
+    for (const std::string path :
+         {"m.key_value.key", "m.key_value.value", "s.items.list.element"}) {
+        int column_ordinal = -1;
+        for (int column = 0; column < row_group_metadata->num_columns(); 
++column) {
+            if 
(row_group_metadata->ColumnChunk(column)->path_in_schema()->ToDotString() == 
path) {
+                column_ordinal = column;
+                break;
+            }
+        }
+        ASSERT_GE(column_ordinal, 0) << path;
+        auto page_reader = 
row_group_reader->GetColumnPageReader(column_ordinal);
+        bool saw_continuation_page = false;
+        std::vector<int16_t> first_repetition_levels;
+        while (auto page = page_reader->NextPage()) {
+            if (page->type() != ::parquet::PageType::DATA_PAGE) {
+                continue;
+            }
+            auto data_page = 
std::static_pointer_cast<::parquet::DataPageV1>(page);
+            ::parquet::LevelDecoder repetition_decoder;
+            repetition_decoder.SetData(data_page->repetition_level_encoding(), 
1,
+                                       data_page->num_values(), 
data_page->data(),
+                                       data_page->size());
+            int16_t first_repetition_level = 0;
+            ASSERT_EQ(repetition_decoder.Decode(1, &first_repetition_level), 
1);
+            first_repetition_levels.push_back(first_repetition_level);
+            saw_continuation_page |= first_repetition_level > 0;
+        }
+        EXPECT_TRUE(saw_continuation_page)
+                << path << " must contain a parent row split across data 
pages; page starts: "
+                << testing::PrintToString(first_repetition_levels);
+    }
+    physical_reader.reset();
+
+    RuntimeProfile profile("sparse_filter_nested_pages");
+    auto reader = create_reader(0, -1, &profile);
+    reader->set_batch_size(2);
+    RuntimeState state {TQueryOptions(), TQueryGlobals()};
+    ASSERT_TRUE(reader->init(&state).ok());
+    std::vector<format::ColumnDefinition> schema;
+    ASSERT_TRUE(reader->get_schema(&schema).ok());
+    ASSERT_EQ(schema.size(), 3);
+
+    auto request = std::make_shared<format::FileScanRequest>();
+    request->predicate_columns = {field_projection(0)};
+    request->non_predicate_columns = {field_projection(1), 
field_projection(2)};
+    request->conjuncts.push_back(create_int32_greater_than_conjunct(0, 3));
+    use_schema_order_positions(request.get(), schema);
+    ASSERT_TRUE(reader->open(request).ok());
+
+    MutableColumns output;
+    for (const auto& field : schema) {
+        output.push_back(field.type->create_column());
+    }
+    bool eof = false;
+    while (!eof) {
+        Block block = build_file_block(schema);
+        size_t rows = 0;
+        ASSERT_TRUE(reader->get_block(&block, &rows, &eof).ok());
+        for (size_t column = 0; column < output.size(); ++column) {
+            
output[column]->insert_range_from(*block.get_by_position(column).column, 0, 
rows);
+        }
+    }
+
+    const auto& ids = assert_cast<const ColumnNullable&>(*output[0]);
+    const auto& id_values = assert_cast<const 
ColumnInt32&>(ids.get_nested_column());
+    EXPECT_EQ(std::vector<int32_t>(id_values.get_data().begin(), 
id_values.get_data().end()),
+              std::vector<int32_t>({4, 5, 6}));
+
+    const auto& nullable_map = assert_cast<const ColumnNullable&>(*output[1]);
+    EXPECT_EQ(nullable_map.get_null_map_data(), NullMap({0, 0, 0}));
+    const auto& map = assert_cast<const 
ColumnMap&>(nullable_map.get_nested_column());
+    EXPECT_EQ(map.get_offsets(),
+              ColumnArray::Offsets64({0, SPANNING_NESTED_VALUES, 
SPANNING_NESTED_VALUES + 1}));
+    const auto& map_keys = assert_cast<const ColumnNullable&>(map.get_keys());
+    const auto& key_values = assert_cast<const 
ColumnInt32&>(map_keys.get_nested_column());
+    ASSERT_EQ(key_values.size(), SPANNING_NESTED_VALUES + 1);
+    EXPECT_EQ(std::count(map_keys.get_null_map_data().begin(), 
map_keys.get_null_map_data().end(),
+                         uint8_t {1}),
+              0);
+    for (size_t value = 0; value < SPANNING_NESTED_VALUES; ++value) {
+        EXPECT_EQ(key_values.get_element(value), 5000 + value);
+    }
+    EXPECT_EQ(key_values.get_element(SPANNING_NESTED_VALUES), 6000);
+    const auto& map_values = assert_cast<const 
ColumnNullable&>(map.get_values());
+    const auto& map_strings = assert_cast<const 
ColumnString&>(map_values.get_nested_column());
+    EXPECT_EQ(std::count(map_values.get_null_map_data().begin(),
+                         map_values.get_null_map_data().end(), uint8_t {1}),
+              1);
+    for (size_t value = 0; value < SPANNING_NESTED_VALUES; ++value) {
+        EXPECT_EQ(map_strings.get_data_at(value).to_string(), 
"selected-wide-value");
+    }
+    EXPECT_TRUE(map_values.is_null_at(SPANNING_NESTED_VALUES));
+
+    const auto& nullable_struct = assert_cast<const 
ColumnNullable&>(*output[2]);
+    EXPECT_EQ(nullable_struct.get_null_map_data(), NullMap({0, 0, 0}));
+    const auto& struct_column =
+            assert_cast<const 
ColumnStruct&>(nullable_struct.get_nested_column());
+    const auto& nullable_list = assert_cast<const 
ColumnNullable&>(struct_column.get_column(0));
+    EXPECT_EQ(nullable_list.get_null_map_data(), NullMap({1, 0, 0}));
+    const auto& list = assert_cast<const 
ColumnArray&>(nullable_list.get_nested_column());
+    EXPECT_EQ(list.get_offsets(),
+              ColumnArray::Offsets64({0, SPANNING_NESTED_VALUES, 
SPANNING_NESTED_VALUES + 2}));
+    const auto& nullable_elements = assert_cast<const 
ColumnNullable&>(list.get_data());
+    const auto& element_values =
+            assert_cast<const 
ColumnInt32&>(nullable_elements.get_nested_column());
+    ASSERT_EQ(element_values.size(), SPANNING_NESTED_VALUES + 2);
+    for (size_t value = 0; value < SPANNING_NESTED_VALUES; ++value) {
+        EXPECT_EQ(element_values.get_element(value), 5000 + value);
+        EXPECT_FALSE(nullable_elements.is_null_at(value));
+    }
+    EXPECT_EQ(element_values.get_element(SPANNING_NESTED_VALUES), 6000);
+    EXPECT_TRUE(nullable_elements.is_null_at(SPANNING_NESTED_VALUES + 1));
+    const auto& markers = assert_cast<const 
ColumnNullable&>(struct_column.get_column(1));
+    const auto& marker_values = assert_cast<const 
ColumnInt32&>(markers.get_nested_column());
+    EXPECT_EQ(std::count(markers.get_null_map_data().begin(), 
markers.get_null_map_data().end(),
+                         uint8_t {1}),
+              0);
+    EXPECT_EQ(
+            std::vector<int32_t>(marker_values.get_data().begin(), 
marker_values.get_data().end()),
+            std::vector<int32_t>({40, 50, 60}));
+
+    ASSERT_NE(profile.get_counter("SelectedRows"), nullptr);
+    EXPECT_EQ(profile.get_counter("SelectedRows")->value(), 3);
+    ASSERT_NE(profile.get_counter("NestedBatches"), nullptr);
+    EXPECT_GT(profile.get_counter("NestedBatches")->value(), 0);
+}
+
 TEST_F(NewParquetReaderTest, 
FullComplexChildUnderPartialParentReadsItsWholeSubtree) {
     write_nested_complex_under_struct_parquet_file(_file_path);
     for (size_t child_index = 0; child_index < 3; ++child_index) {
diff --git a/be/test/format_v2/parquet/parquet_statistics_test.cpp 
b/be/test/format_v2/parquet/parquet_statistics_test.cpp
index e69f1311e4a..740892367bb 100644
--- a/be/test/format_v2/parquet/parquet_statistics_test.cpp
+++ b/be/test/format_v2/parquet/parquet_statistics_test.cpp
@@ -47,6 +47,7 @@
 #include "format_v2/parquet/parquet_file_context.h"
 #include "format_v2/parquet/reader/native/block_split_bloom_filter.h"
 #include "io/fs/file_reader.h"
+#include "runtime/runtime_state.h"
 #include "util/thrift_util.h"
 namespace doris {
 namespace {
@@ -669,6 +670,79 @@ TEST(NativeParquetStatisticsTest, 
TypeDefinedBoundsRequireSupportedColumnOrder)
     EXPECT_TRUE(selected_ranges.empty());
 }
 
+TEST(NativeParquetStatisticsTest, ZonemapPruningIgnoresDisabledSessionSwitch) {
+    auto encode_int32 = [](int32_t value) {
+        std::string bytes(sizeof(value), '\0');
+        memcpy(bytes.data(), &value, sizeof(value));
+        return bytes;
+    };
+
+    auto column_schema = 
std::make_unique<format::parquet::ParquetColumnSchema>();
+    column_schema->kind = format::parquet::ParquetColumnSchemaKind::PRIMITIVE;
+    column_schema->local_id = 0;
+    column_schema->leaf_column_id = 0;
+    column_schema->type = std::make_shared<DataTypeInt32>();
+    column_schema->type_descriptor.doris_type = column_schema->type;
+    column_schema->type_descriptor.physical_type = tparquet::Type::INT32;
+    std::vector<std::unique_ptr<format::parquet::ParquetColumnSchema>> schema;
+    schema.push_back(std::move(column_schema));
+
+    tparquet::Statistics statistics;
+    statistics.__set_min_value(encode_int32(1));
+    statistics.__set_max_value(encode_int32(2));
+    statistics.__set_null_count(0);
+    tparquet::ColumnMetaData column_metadata;
+    column_metadata.__set_type(tparquet::Type::INT32);
+    column_metadata.__set_num_values(1);
+    column_metadata.__set_statistics(statistics);
+    tparquet::ColumnChunk chunk;
+    chunk.__set_meta_data(column_metadata);
+    tparquet::RowGroup row_group;
+    row_group.__set_columns({chunk});
+    row_group.__set_num_rows(1);
+    tparquet::ColumnOrder order;
+    order.__set_TYPE_ORDER(tparquet::TypeDefinedOrder());
+    tparquet::FileMetaData metadata;
+    metadata.__set_column_orders({order});
+    metadata.__set_row_groups({row_group});
+
+    format::FileScanRequest request;
+    request.local_positions.emplace(format::LocalColumnId(0), 
format::LocalIndex(0));
+    request.predicate_columns = 
{format::LocalColumnIndex::top_level(format::LocalColumnId(0))};
+    request.conjuncts = {
+            
VExprContext::create_shared(std::make_shared<MetadataInt32GreaterThanExpr>(100))};
+
+    TQueryOptions query_options;
+    query_options.__set_enable_expr_zonemap_filter(false);
+    RuntimeState state {query_options, TQueryGlobals()};
+    std::vector<int> selected_row_groups;
+    ASSERT_TRUE(format::parquet::select_row_groups_by_metadata(metadata, 
schema, request, nullptr,
+                                                               
&selected_row_groups, false, nullptr,
+                                                               nullptr, &state)
+                        .ok());
+    EXPECT_TRUE(selected_row_groups.empty());
+
+    format::parquet::NativeParquetPageIndex page_index;
+    page_index.column_index.__set_min_values({encode_int32(1)});
+    page_index.column_index.__set_max_values({encode_int32(2)});
+    page_index.column_index.__set_null_pages({false});
+    page_index.column_index.__set_null_counts({0});
+    tparquet::PageLocation location;
+    location.__set_offset(0);
+    location.__set_compressed_page_size(10);
+    location.__set_first_row_index(0);
+    page_index.offset_index.__set_page_locations({location});
+    std::unordered_map<int, format::parquet::NativeParquetPageIndex> 
page_indexes;
+    page_indexes.emplace(0, std::move(page_index));
+    std::vector<format::parquet::RowRange> selected_ranges;
+    std::map<int, format::parquet::ParquetPageSkipPlan> skip_plans;
+    ASSERT_TRUE(format::parquet::select_row_group_ranges_by_native_page_index(
+                        metadata, page_indexes, schema, request, 1, 
&selected_ranges, &skip_plans,
+                        nullptr, nullptr, &state)
+                        .ok());
+    EXPECT_TRUE(selected_ranges.empty());
+}
+
 TEST(NativeParquetStatisticsTest, 
ContradictoryAllNullPageCountsDisablePruning) {
     auto column_schema = 
std::make_unique<format::parquet::ParquetColumnSchema>();
     column_schema->kind = format::parquet::ParquetColumnSchemaKind::PRIMITIVE;
diff --git 
a/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonColumnValue.java
 
b/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonColumnValue.java
index f60bb996703..339feb97c9b 100644
--- 
a/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonColumnValue.java
+++ 
b/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonColumnValue.java
@@ -38,20 +38,47 @@ import java.math.BigInteger;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class PaimonColumnValue implements ColumnValue {
     private static final Logger LOG = 
LoggerFactory.getLogger(PaimonColumnValue.class);
+    private static final Map<String, String> DORIS_TIME_ZONE_ALIASES;
+
+    static {
+        Map<String, String> aliases = new HashMap<>(ZoneId.SHORT_IDS);
+        // The scanner cannot depend on FE's TimeUtils, so keep its accepted 
aliases and CST
+        // interpretation identical at this JNI boundary.
+        aliases.put("CST", "Asia/Shanghai");
+        aliases.put("PRC", "Asia/Shanghai");
+        aliases.put("UTC", "UTC");
+        aliases.put("GMT", "UTC");
+        DORIS_TIME_ZONE_ALIASES = Collections.unmodifiableMap(aliases);
+    }
+
     private int idx;
     private DataGetters record;
     private ColumnType dorisType;
     private DataType dataType;
-    private String timeZone;
+    private ZoneId timeZone;
+    // Keep these caches lazy so scalar columns do not pay for complex-type 
reuse bookkeeping.
+    private List<PaimonColumnValue> arrayValues;
+    private List<PaimonColumnValue> mapKeys;
+    private List<PaimonColumnValue> mapValues;
+    private List<PaimonColumnValue> structValues;
 
     public PaimonColumnValue() {
     }
 
     public PaimonColumnValue(DataGetters record, int idx, ColumnType 
columnType, DataType dataType, String timeZone) {
+        this(record, idx, columnType, dataType, resolveTimeZone(timeZone));
+    }
+
+    private PaimonColumnValue(
+            DataGetters record, int idx, ColumnType columnType, DataType 
dataType, ZoneId timeZone) {
         this.idx = idx;
         this.record = record;
         this.dorisType = columnType;
@@ -70,7 +97,7 @@ public class PaimonColumnValue implements ColumnValue {
     }
 
     public void setTimeZone(String timeZone) {
-        this.timeZone = timeZone;
+        this.timeZone = resolveTimeZone(timeZone);
     }
 
     @Override
@@ -142,8 +169,8 @@ public class PaimonColumnValue implements ColumnValue {
     public LocalDateTime getDateTime() {
         Timestamp ts = record.getTimestamp(idx, dorisType.getPrecision());
         if (dataType instanceof LocalZonedTimestampType) {
-            return ts.toLocalDateTime().atZone(ZoneId.of("UTC"))
-                    
.withZoneSameInstant(ZoneId.of(timeZone)).toLocalDateTime();
+            // Paimon stores TIMESTAMP_LTZ as an epoch instant, so convert it 
directly in the cached session zone.
+            return LocalDateTime.ofInstant(ts.toInstant(), timeZone);
         } else {
             return ts.toLocalDateTime();
         }
@@ -152,15 +179,18 @@ public class PaimonColumnValue implements ColumnValue {
     @Override
     public LocalDateTime getTimeStampTz() {
         Timestamp ts = record.getTimestamp(idx, dorisType.getPrecision());
-        LocalDateTime v = ts.toInstant()
-                .atZone(ZoneId.of("UTC"))
-                .toLocalDateTime();
-        return v;
+        // Timestamp's local representation is identical to converting its 
epoch instant in UTC.
+        return ts.toLocalDateTime();
     }
 
     @Override
     public boolean isNull() {
-        return record.isNullAt(idx);
+        boolean isNull = record.isNullAt(idx);
+        if (isNull) {
+            // A null complex value has no live descendants; release wrappers 
retained by its prior row.
+            clearChildCaches();
+        }
+        return isNull;
     }
 
     @Override
@@ -171,28 +201,41 @@ public class PaimonColumnValue implements ColumnValue {
     @Override
     public void unpackArray(List<ColumnValue> values) {
         InternalArray recordArray = record.getArray(idx);
+        if (arrayValues == null) {
+            arrayValues = new ArrayList<>();
+        }
+        ColumnType elementDorisType = dorisType.getChildTypes().get(0);
+        DataType elementPaimonType = ((ArrayType) dataType).getElementType();
         for (int i = 0; i < recordArray.size(); i++) {
-            PaimonColumnValue arrayColumnValue = new 
PaimonColumnValue((DataGetters) recordArray, i,
-                    dorisType.getChildTypes().get(0), ((ArrayType) 
dataType).getElementType(), timeZone);
-            values.add(arrayColumnValue);
+            values.add(reuseColumnValue(arrayValues, i, (DataGetters) 
recordArray, i,
+                    elementDorisType, elementPaimonType));
         }
+        trimCache(arrayValues, recordArray.size());
     }
 
     @Override
     public void unpackMap(List<ColumnValue> keys, List<ColumnValue> values) {
         InternalMap map = record.getMap(idx);
+        if (mapKeys == null) {
+            mapKeys = new ArrayList<>();
+            mapValues = new ArrayList<>();
+        }
         InternalArray key = map.keyArray();
+        ColumnType keyDorisType = dorisType.getChildTypes().get(0);
+        DataType keyPaimonType = ((MapType) dataType).getKeyType();
         for (int i = 0; i < key.size(); i++) {
-            PaimonColumnValue keyColumnValue = new 
PaimonColumnValue((DataGetters) key, i,
-                    dorisType.getChildTypes().get(0), ((MapType) 
dataType).getKeyType(), timeZone);
-            keys.add(keyColumnValue);
+            keys.add(reuseColumnValue(mapKeys, i, (DataGetters) key, i,
+                    keyDorisType, keyPaimonType));
         }
+        trimCache(mapKeys, key.size());
         InternalArray value = map.valueArray();
+        ColumnType valueDorisType = dorisType.getChildTypes().get(1);
+        DataType valuePaimonType = ((MapType) dataType).getValueType();
         for (int i = 0; i < value.size(); i++) {
-            PaimonColumnValue valueColumnValue = new 
PaimonColumnValue((DataGetters) value, i,
-                    dorisType.getChildTypes().get(1), ((MapType) 
dataType).getValueType(), timeZone);
-            values.add(valueColumnValue);
+            values.add(reuseColumnValue(mapValues, i, (DataGetters) value, i,
+                    valueDorisType, valuePaimonType));
         }
+        trimCache(mapValues, value.size());
     }
 
     @Override
@@ -200,9 +243,57 @@ public class PaimonColumnValue implements ColumnValue {
         RowType rowType = (RowType) dataType;
         // Projection entries are original child indexes, so the binary row 
must keep the full RowType arity.
         InternalRow row = record.getRow(idx, rowType.getFieldCount());
+        if (structValues == null) {
+            structValues = new ArrayList<>();
+        }
         for (int i : structFieldIndex) {
-            values.add(new PaimonColumnValue(row, i, 
dorisType.getChildTypes().get(i),
-                    rowType.getFields().get(i).type(), timeZone));
+            values.add(reuseColumnValue(structValues, i, row, i, 
dorisType.getChildTypes().get(i),
+                    rowType.getFields().get(i).type()));
+        }
+    }
+
+    private PaimonColumnValue reuseColumnValue(
+            List<PaimonColumnValue> cache, int cacheIndex, DataGetters 
childRecord, int childIndex,
+            ColumnType childDorisType, DataType childPaimonType) {
+        while (cache.size() <= cacheIndex) {
+            cache.add(null);
+        }
+        PaimonColumnValue value = cache.get(cacheIndex);
+        if (value == null) {
+            value = new PaimonColumnValue(
+                    childRecord, childIndex, childDorisType, childPaimonType, 
timeZone);
+            cache.set(cacheIndex, value);
+        } else {
+            // VectorColumn consumes unpacked values synchronously, before 
this parent advances to another value.
+            value.reset(childRecord, childIndex, childDorisType, 
childPaimonType, timeZone);
         }
+        return value;
+    }
+
+    private void reset(
+            DataGetters record, int idx, ColumnType dorisType, DataType 
dataType, ZoneId timeZone) {
+        this.record = record;
+        this.idx = idx;
+        this.dorisType = dorisType;
+        this.dataType = dataType;
+        this.timeZone = timeZone;
+    }
+
+    private static ZoneId resolveTimeZone(String timeZone) {
+        return ZoneId.of(timeZone, DORIS_TIME_ZONE_ALIASES);
+    }
+
+    private static void trimCache(List<PaimonColumnValue> cache, int liveSize) 
{
+        if (cache.size() > liveSize) {
+            // Retain only wrappers addressable by the current container, not 
its historical maximum.
+            cache.subList(liveSize, cache.size()).clear();
+        }
+    }
+
+    private void clearChildCaches() {
+        arrayValues = null;
+        mapKeys = null;
+        mapValues = null;
+        structValues = null;
     }
 }
diff --git 
a/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonColumnValueTest.java
 
b/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonColumnValueTest.java
index 639a2e87f30..20062ff2f6e 100644
--- 
a/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonColumnValueTest.java
+++ 
b/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonColumnValueTest.java
@@ -21,21 +21,33 @@ import org.apache.doris.common.jni.vec.ColumnType;
 import org.apache.doris.common.jni.vec.ColumnValue;
 
 import org.apache.paimon.data.BinaryString;
+import org.apache.paimon.data.GenericArray;
+import org.apache.paimon.data.GenericMap;
 import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.data.InternalRow;
+import org.apache.paimon.data.Timestamp;
 import org.apache.paimon.data.serializer.InternalRowSerializer;
+import org.apache.paimon.types.ArrayType;
 import org.apache.paimon.types.BigIntType;
 import org.apache.paimon.types.DataType;
 import org.apache.paimon.types.IntType;
+import org.apache.paimon.types.LocalZonedTimestampType;
+import org.apache.paimon.types.MapType;
 import org.apache.paimon.types.RowType;
+import org.apache.paimon.types.TimestampType;
 import org.apache.paimon.types.VarCharType;
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.lang.reflect.Field;
+import java.time.Instant;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 public class PaimonColumnValueTest {
     private final RowType paimonStructType = RowType.of(
@@ -80,10 +92,228 @@ public class PaimonColumnValueTest {
         Assert.assertEquals(100L, values.get(2).getLong());
     }
 
+    @Test
+    public void testReuseArrayElementsAcrossRows() {
+        ArrayType paimonArrayType = new ArrayType(new IntType());
+        ColumnType dorisArrayType = ColumnType.parseType("a", "array<int>");
+        PaimonColumnValue arrayValue = new PaimonColumnValue(
+                GenericRow.of(new GenericArray(new int[] {1, 2})), 0,
+                dorisArrayType, paimonArrayType, "UTC");
+
+        List<ColumnValue> firstValues = new ArrayList<>();
+        arrayValue.unpackArray(firstValues);
+        arrayValue.setOffsetRow(GenericRow.of(new GenericArray(new int[] {3, 
4, 5})));
+        List<ColumnValue> secondValues = new ArrayList<>();
+        arrayValue.unpackArray(secondValues);
+
+        Assert.assertSame(firstValues.get(0), secondValues.get(0));
+        Assert.assertSame(firstValues.get(1), secondValues.get(1));
+        Assert.assertEquals(Arrays.asList(3, 4, 5), getInts(secondValues));
+
+        arrayValue.setOffsetRow(GenericRow.of(new GenericArray(new int[] 
{6})));
+        List<ColumnValue> thirdValues = new ArrayList<>();
+        arrayValue.unpackArray(thirdValues);
+        Assert.assertSame(firstValues.get(0), thirdValues.get(0));
+        Assert.assertEquals(Collections.singletonList(6), 
getInts(thirdValues));
+    }
+
+    @Test
+    public void testReuseMapEntriesAcrossRows() {
+        MapType paimonMapType = new MapType(new IntType(), new BigIntType());
+        ColumnType dorisMapType = ColumnType.parseType("m", "map<int,bigint>");
+        PaimonColumnValue mapValue = new PaimonColumnValue(
+                GenericRow.of(new GenericMap(linkedMap(1, 10L, 2, 20L))), 0,
+                dorisMapType, paimonMapType, "UTC");
+
+        List<ColumnValue> firstKeys = new ArrayList<>();
+        List<ColumnValue> firstValues = new ArrayList<>();
+        mapValue.unpackMap(firstKeys, firstValues);
+        mapValue.setOffsetRow(GenericRow.of(new GenericMap(linkedMap(3, 30L, 
4, 40L))));
+        List<ColumnValue> secondKeys = new ArrayList<>();
+        List<ColumnValue> secondValues = new ArrayList<>();
+        mapValue.unpackMap(secondKeys, secondValues);
+
+        Assert.assertSame(firstKeys.get(0), secondKeys.get(0));
+        Assert.assertSame(firstKeys.get(1), secondKeys.get(1));
+        Assert.assertSame(firstValues.get(0), secondValues.get(0));
+        Assert.assertSame(firstValues.get(1), secondValues.get(1));
+        Assert.assertEquals(Arrays.asList(3, 4), getInts(secondKeys));
+        Assert.assertEquals(Arrays.asList(30L, 40L), getLongs(secondValues));
+    }
+
+    @Test
+    public void testReuseProjectedStructFieldsAcrossRows() {
+        PaimonColumnValue structValue = createStructValue();
+        List<Integer> projectedFields = Arrays.asList(0, 2);
+        List<ColumnValue> firstValues = new ArrayList<>();
+        structValue.unpackStruct(projectedFields, firstValues);
+
+        structValue.setOffsetRow(createStructRow(20, "y", 200L));
+        List<ColumnValue> secondValues = new ArrayList<>();
+        structValue.unpackStruct(projectedFields, secondValues);
+
+        Assert.assertSame(firstValues.get(0), secondValues.get(0));
+        Assert.assertSame(firstValues.get(1), secondValues.get(1));
+        Assert.assertEquals(20, secondValues.get(0).getInt());
+        Assert.assertEquals(200L, secondValues.get(1).getLong());
+    }
+
+    @Test
+    public void testReuseNestedArrayElementsAcrossRows() {
+        ArrayType paimonNestedArrayType = new ArrayType(new ArrayType(new 
IntType()));
+        ColumnType dorisNestedArrayType = ColumnType.parseType("a", 
"array<array<int>>");
+        PaimonColumnValue arrayValue = new PaimonColumnValue(
+                GenericRow.of(new GenericArray(new Object[] {new 
GenericArray(new int[] {1, 2})})), 0,
+                dorisNestedArrayType, paimonNestedArrayType, "UTC");
+
+        List<ColumnValue> firstOuterValues = new ArrayList<>();
+        arrayValue.unpackArray(firstOuterValues);
+        List<ColumnValue> firstInnerValues = new ArrayList<>();
+        firstOuterValues.get(0).unpackArray(firstInnerValues);
+
+        arrayValue.setOffsetRow(
+                GenericRow.of(new GenericArray(new Object[] {new 
GenericArray(new int[] {3, 4})})));
+        List<ColumnValue> secondOuterValues = new ArrayList<>();
+        arrayValue.unpackArray(secondOuterValues);
+        List<ColumnValue> secondInnerValues = new ArrayList<>();
+        secondOuterValues.get(0).unpackArray(secondInnerValues);
+
+        Assert.assertSame(firstOuterValues.get(0), secondOuterValues.get(0));
+        Assert.assertSame(firstInnerValues.get(0), secondInnerValues.get(0));
+        Assert.assertSame(firstInnerValues.get(1), secondInnerValues.get(1));
+        Assert.assertEquals(Arrays.asList(3, 4), getInts(secondInnerValues));
+    }
+
+    @Test
+    public void testNestedArrayCacheRetainsOnlyCurrentShape() throws Exception 
{
+        int outerSize = 4;
+        int innerSize = 32;
+        ArrayType paimonNestedArrayType = new ArrayType(new ArrayType(new 
IntType()));
+        ColumnType dorisNestedArrayType = ColumnType.parseType("a", 
"array<array<int>>");
+        PaimonColumnValue arrayValue = new PaimonColumnValue(
+                nestedArrayRow(outerSize, innerSize, 0, -1), 0,
+                dorisNestedArrayType, paimonNestedArrayType, "UTC");
+
+        consumeNestedArray(arrayValue);
+        Assert.assertEquals(outerSize + innerSize, 
retainedCachedValues(arrayValue));
+
+        // Moving the large child to position 1 makes position 0 a smaller, 
non-null array.
+        arrayValue.setOffsetRow(nestedArrayRow(outerSize, innerSize, 1, -1));
+        consumeNestedArray(arrayValue);
+        Assert.assertEquals(outerSize + innerSize, 
retainedCachedValues(arrayValue));
+
+        // Moving it again makes position 1 null, which must release that 
position's descendants.
+        arrayValue.setOffsetRow(nestedArrayRow(outerSize, innerSize, 2, 1));
+        consumeNestedArray(arrayValue);
+        Assert.assertEquals(outerSize + innerSize, 
retainedCachedValues(arrayValue));
+    }
+
+    @Test
+    public void testTimestampConversionsPreserveBoundaryValues() {
+        Timestamp timestamp = Timestamp.fromEpochMillis(-1, 999_999);
+        ColumnType dorisTimestampType = ColumnType.parseType("t", 
"datetimev2(9)");
+
+        PaimonColumnValue timestampValue = new PaimonColumnValue(
+                GenericRow.of(timestamp), 0, dorisTimestampType, new 
TimestampType(9), "UTC");
+        Assert.assertEquals(
+                LocalDateTime.of(1969, 12, 31, 23, 59, 59, 999_999_999),
+                timestampValue.getDateTime());
+        Assert.assertEquals(
+                LocalDateTime.of(1969, 12, 31, 23, 59, 59, 999_999_999),
+                timestampValue.getTimeStampTz());
+
+        PaimonColumnValue localZonedValue = new PaimonColumnValue(
+                
GenericRow.of(Timestamp.fromInstant(Instant.parse("2024-03-10T10:30:00.123456789Z"))),
 0,
+                dorisTimestampType, new LocalZonedTimestampType(9), 
"America/Los_Angeles");
+        Assert.assertEquals(
+                LocalDateTime.of(2024, 3, 10, 3, 30, 0, 123_456_789),
+                localZonedValue.getDateTime());
+
+        localZonedValue.setTimeZone("Asia/Shanghai");
+        Assert.assertEquals(
+                LocalDateTime.of(2024, 3, 10, 18, 30, 0, 123_456_789),
+                localZonedValue.getDateTime());
+
+        localZonedValue.setTimeZone("CST");
+        Assert.assertEquals(
+                LocalDateTime.of(2024, 3, 10, 18, 30, 0, 123_456_789),
+                localZonedValue.getDateTime());
+    }
+
+    private InternalRow nestedArrayRow(int outerSize, int innerSize, int 
populatedIndex, int nullIndex) {
+        Object[] outerValues = new Object[outerSize];
+        for (int i = 0; i < outerSize; i++) {
+            if (i == nullIndex) {
+                outerValues[i] = null;
+            } else if (i == populatedIndex) {
+                outerValues[i] = new GenericArray(new int[innerSize]);
+            } else {
+                outerValues[i] = new GenericArray(new int[0]);
+            }
+        }
+        return GenericRow.of(new GenericArray(outerValues));
+    }
+
+    private void consumeNestedArray(PaimonColumnValue arrayValue) {
+        List<ColumnValue> outerValues = new ArrayList<>();
+        arrayValue.unpackArray(outerValues);
+        for (ColumnValue outerValue : outerValues) {
+            if (!outerValue.isNull()) {
+                outerValue.unpackArray(new ArrayList<>());
+            }
+        }
+    }
+
+    private int retainedCachedValues(PaimonColumnValue value) throws Exception 
{
+        int retained = 0;
+        for (String fieldName : Arrays.asList("arrayValues", "mapKeys", 
"mapValues", "structValues")) {
+            Field field = PaimonColumnValue.class.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            List<?> children = (List<?>) field.get(value);
+            if (children == null) {
+                continue;
+            }
+            for (Object child : children) {
+                if (child != null) {
+                    retained++;
+                    retained += retainedCachedValues((PaimonColumnValue) 
child);
+                }
+            }
+        }
+        return retained;
+    }
+
     private PaimonColumnValue createStructValue() {
-        GenericRow nestedRow = GenericRow.of(10, BinaryString.fromString("x"), 
100L);
+        return new PaimonColumnValue(createStructRow(10, "x", 100L), 0,
+                dorisStructType, paimonStructType, "UTC");
+    }
+
+    private InternalRow createStructRow(int intValue, String stringValue, long 
longValue) {
+        GenericRow nestedRow = GenericRow.of(intValue, 
BinaryString.fromString(stringValue), longValue);
         RowType outerType = RowType.of(new DataType[] {paimonStructType}, new 
String[] {"s"});
-        InternalRow outerRow = new 
InternalRowSerializer(outerType).toBinaryRow(GenericRow.of(nestedRow));
-        return new PaimonColumnValue(outerRow, 0, dorisStructType, 
paimonStructType, "UTC");
+        return new 
InternalRowSerializer(outerType).toBinaryRow(GenericRow.of(nestedRow));
+    }
+
+    private List<Integer> getInts(List<ColumnValue> values) {
+        List<Integer> result = new ArrayList<>();
+        for (ColumnValue value : values) {
+            result.add(value.getInt());
+        }
+        return result;
+    }
+
+    private List<Long> getLongs(List<ColumnValue> values) {
+        List<Long> result = new ArrayList<>();
+        for (ColumnValue value : values) {
+            result.add(value.getLong());
+        }
+        return result;
+    }
+
+    private Map<Integer, Long> linkedMap(int key1, long value1, int key2, long 
value2) {
+        Map<Integer, Long> result = new LinkedHashMap<>();
+        result.put(key1, value1);
+        result.put(key2, value2);
+        return result;
     }
 }
diff --git 
a/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonJniScannerTest.java
 
b/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonJniScannerTest.java
index 563d2fbf659..9918618c07a 100644
--- 
a/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonJniScannerTest.java
+++ 
b/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonJniScannerTest.java
@@ -493,6 +493,16 @@ public class PaimonJniScannerTest {
         Assert.assertEquals(2, sampleCalls.get());
     }
 
+    @Test
+    public void 
testConstructorAcceptsDorisShortTimeZoneForNonTemporalProjection() {
+        Map<String, String> params = createBaseParams();
+        params.put("required_fields", "id");
+        params.put("columns_types", "int");
+        params.put("time_zone", "EST");
+
+        new PaimonJniScanner(128, params);
+    }
+
     @Test
     public void testIOManagerOptionHelpers() throws Exception {
         Map<String, String> params = createBaseParams();
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index 74caa6d0fad..1c59b4fa1ea 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -2129,7 +2129,15 @@ public class SessionVariable implements Serializable, 
Writable {
     @VariableMgr.VarAttr(name = ENABLE_RUNTIME_FILTER_PRUNE, needForward = 
true, fuzzy = true)
     public boolean enableRuntimeFilterPrune = true;
 
-    @VariableMgr.VarAttr(name = ENABLE_RUNTIME_FILTER_PARTITION_PRUNE, 
needForward = true, fuzzy = true)
+    @VariableMgr.VarAttr(
+            name = ENABLE_RUNTIME_FILTER_PARTITION_PRUNE,
+            description = {"控制支持该变量的 scanner 是否启用运行时过滤器分区裁剪。"
+                    + "File Scanner V2 始终启用安全的分区裁剪。默认为 true。",
+                    "Controls runtime-filter partition pruning in scanners 
that honor this variable. "
+                            + "File Scanner V2 always enables safe partition 
pruning. "
+                            + "The default value is true."},
+            needForward = true,
+            fuzzy = true)
     public boolean enableRuntimeFilterPartitionPrune = true;
 
     /**
@@ -2613,8 +2621,10 @@ public class SessionVariable implements Serializable, 
Writable {
     @VariableMgr.VarAttr(
             name = ENABLE_EXPR_ZONEMAP_FILTER,
             fuzzy = true,
-            description = {"控制 scanner 是否启用表达式 ZoneMap 过滤。默认为 false。",
-                    "Controls whether to enable expression ZoneMap filtering 
in scanners. "
+            description = {"控制支持该变量的 scanner 是否启用表达式 ZoneMap 过滤。"
+                    + "File Scanner V2 始终启用安全的表达式 ZoneMap 过滤。默认为 false。",
+                    "Controls expression ZoneMap filtering in scanners that 
honor this variable. "
+                            + "File Scanner V2 always enables safe expression 
ZoneMap filtering. "
                             + "The default value is false."},
             needForward = true)
     public boolean enableExprZonemapFilter = false;
diff --git a/gensrc/thrift/PaloInternalService.thrift 
b/gensrc/thrift/PaloInternalService.thrift
index 05deef4efa5..64a8331dd93 100644
--- a/gensrc/thrift/PaloInternalService.thrift
+++ b/gensrc/thrift/PaloInternalService.thrift
@@ -374,6 +374,8 @@ struct TQueryOptions {
 
   148: optional i32 min_scanners_concurrency = 1;
   149: optional i32 min_scan_scheduler_concurrency = 0; //deprecated
+  // Controls runtime-filter partition pruning for readers that honor this 
option.
+  // FileScannerV2 always enables safe partition pruning.
   150: optional bool enable_runtime_filter_partition_prune = true;
 
   // The minimum memory that an operator required to run.
@@ -496,7 +498,9 @@ struct TQueryOptions {
   219: optional i64 ann_index_candidate_rows_threshold = 0
   // Candidate row ratio threshold against segment rows. Existing default is 
0.3.
   220: optional double ann_index_candidate_rows_percent_threshold = 0.3
-  // To control whether BE scan readers may apply expression-based ZoneMap 
pruning.
+
+  // Controls expression-based ZoneMap pruning for readers that honor this 
option.
+  // FileScannerV2 always enables safe expression ZoneMap pruning.
   224: optional bool enable_expr_zonemap_filter = true
 
   225: optional i64 runtime_filter_tree_publish_max_send_bytes = 268435456


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

Reply via email to