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

Gabriel39 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 93941b10cca [improvement](parquet) Fuse nested sparse selection 
planning (#66263)
93941b10cca is described below

commit 93941b10cca7bf911d7ff09baee345f57ac606c6
Author: Gabriel <[email protected]>
AuthorDate: Thu Jul 30 20:45:28 2026 +0800

    [improvement](parquet) Fuse nested sparse selection planning (#66263)
    
    Parquet V2 repeated leaves currently rebuild a parent-row filter into a 
level-entry filter, scan definition levels into null runs plus an ancestor-null 
hash set, build the leaf selection, and scan the levels again to compact 
surviving parent shapes. This fixed O(level entries) work is especially visible 
when only 1% or 10% of parent rows survive.
    
    This change adds a single-pass nested selection builder. It maps parent 
filters to repeated entries, classifies ancestor and leaf nulls, builds the 
decoder selection, appends selected nulls, and compacts repetition/definition 
levels in place. Ancestor-null placeholders remain in the surviving parent 
shape but do not consume leaf selection entries. Unfiltered and fully filtered 
paths keep their existing behavior.
---
 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 +-
 .../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 +
 .../parquet/parquet_benchmark_scenarios_test.cpp   |  20 +-
 .../parquet/parquet_reader_control_test.cpp        |  75 ++++++
 be/test/format_v2/parquet/parquet_reader_test.cpp  | 252 +++++++++++++++++++
 10 files changed, 789 insertions(+), 36 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/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/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) {


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

Reply via email to