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 62b8b4b3b88 [fix](be) Preserve rows for untrusted Parquet null counts 
(#67573)
62b8b4b3b88 is described below

commit 62b8b4b3b8816fda32faa335f433ebabbff2f779
Author: Gabriel <[email protected]>
AuthorDate: Tue Sep 8 11:04:20 2026 +0800

    [fix](be) Preserve rows for untrusted Parquet null counts (#67573)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: None
    
    Legacy parquet-mr writers and Arrow versions through 5 can under-report
    Parquet null counts. Format v2 treated a reported zero as proof that a
    row group or page had no NULL values, so `IS NULL` pruning could drop
    matching rows.
    
    ### What is changed?
    
    - Derive null-count trust from Parquet `created_by` in format v2.
    - Treat the legacy `parquet-cpp` identity and `parquet-cpp-arrow`
    versions before 6.0.0 as untrusted for null-count pruning.
    - Conservatively retain NULL candidates at both row-group and page-index
    pruning layers while preserving min/max pruning.
    - Add an affected PyArrow 3.0.0 dictionary-encoded fixture and Arrow
    3/4/5/6 boundary coverage.
    
    ### Release note
    
    Avoid incorrect `IS NULL` pruning for Parquet files produced by affected
    legacy writers.
    
    ### Check List (For Author)
    
    - Test: Unit Test
    - `NativeParquetStatisticsTest.*:ParquetScanTest.*Null*` (24 tests
    passed with ASAN)
    - Behavior changed: Yes. Format v2 no longer trusts null counts from
    affected legacy Parquet and Arrow writers for metadata pruning.
    - Does this need documentation: No
---
 be/src/format_v2/parquet/parquet_statistics.cpp    |  67 ++++++++-----
 be/src/format_v2/parquet/parquet_statistics.h      |   3 +-
 .../parquet/reader/native/column_chunk_reader.cpp  |  10 +-
 .../parquet/reader/native/column_chunk_reader.h    |   2 +
 be/test/format_v2/parquet/parquet_scan_test.cpp    |  48 +++++++++
 .../format_v2/parquet/parquet_statistics_test.cpp  | 107 +++++++++++++++++++++
 .../pyarrow-3.0.0-dictionary-null-count.parquet    | Bin 0 -> 944 bytes
 7 files changed, 211 insertions(+), 26 deletions(-)

diff --git a/be/src/format_v2/parquet/parquet_statistics.cpp 
b/be/src/format_v2/parquet/parquet_statistics.cpp
index 79cd23c5bb0..36acdcf8c23 100644
--- a/be/src/format_v2/parquet/parquet_statistics.cpp
+++ b/be/src/format_v2/parquet/parquet_statistics.cpp
@@ -45,6 +45,7 @@
 #include "format_v2/parquet/parquet_column_schema.h"
 #include "format_v2/parquet/parquet_file_context.h"
 #include "format_v2/parquet/reader/native/block_split_bloom_filter.h"
+#include "format_v2/parquet/reader/native/column_chunk_reader.h"
 #include "format_v2/parquet/reader/native_column_reader.h"
 #include "format_v2/parquet/selection_vector.h" // count_range_rows
 #include "format_v2/timestamp_statistics.h"
@@ -135,7 +136,7 @@ namespace {
 bool build_native_page_statistics(const tparquet::ColumnIndex& column_index,
                                   const ParquetColumnSchema& column_schema, 
size_t page_idx,
                                   int64_t page_rows, ParquetColumnStatistics* 
page_statistics,
-                                  const cctz::time_zone* timezone);
+                                  const cctz::time_zone* timezone, bool 
null_count_trusted);
 
 enum class ParquetRowGroupPruneReason {
     NONE,         // cannot prune; must read
@@ -976,13 +977,14 @@ std::shared_ptr<segment_v2::ZoneMap> 
ParquetStatisticsUtils::MakeZoneMap(
 
 ParquetColumnStatistics ParquetStatisticsUtils::TransformColumnStatistics(
         const ParquetColumnSchema& column_schema, const tparquet::Statistics* 
statistics,
-        int64_t column_value_count, const cctz::time_zone* timezone) {
+        int64_t column_value_count, const cctz::time_zone* timezone, bool 
null_count_trusted) {
     ParquetColumnStatistics result;
     if (statistics == nullptr || column_value_count < 0) {
         return result;
     }
 
-    if (statistics->__isset.null_count && statistics->null_count > 
column_value_count) {
+    if (null_count_trusted && statistics->__isset.null_count &&
+        statistics->null_count > column_value_count) {
         // An impossible null count makes all derived min/max and all-null 
flags untrustworthy;
         // disable pruning instead of turning corrupt footer metadata into 
false negatives.
         return result;
@@ -990,7 +992,8 @@ ParquetColumnStatistics 
ParquetStatisticsUtils::TransformColumnStatistics(
 
     const bool has_null_count = statistics->__isset.null_count && 
statistics->null_count >= 0;
     const int64_t null_count = has_null_count ? statistics->null_count : 0;
-    const bool has_not_null = has_null_count ? column_value_count > null_count 
: true;
+    const bool has_not_null =
+            has_null_count && null_count_trusted ? column_value_count > 
null_count : true;
     const std::string* min_value = statistics->__isset.min_value
                                            ? &statistics->min_value
                                            : (statistics->__isset.min ? 
&statistics->min : nullptr);
@@ -1008,10 +1011,10 @@ ParquetColumnStatistics 
ParquetStatisticsUtils::TransformColumnStatistics(
     // Footer statistics and page indexes share the same little-endian 
physical encoding. Reusing
     // one decoder keeps native row-group and page pruning identical for 
logical types and NaNs.
     if (!build_native_page_statistics(index, column_schema, 0, 
column_value_count, &result,
-                                      timezone)) {
+                                      timezone, null_count_trusted)) {
         return {};
     }
-    if (!has_null_count) {
+    if (!has_null_count || !null_count_trusted) {
         result.has_null_count = false;
         result.has_null = true;
     }
@@ -1081,7 +1084,8 @@ bool check_native_statistics(const 
tparquet::FileMetaData& metadata,
                              const tparquet::RowGroup& row_group,
                              const 
std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
                              const format::FileScanRequest& request,
-                             ParquetPruningStats* pruning_stats, const 
cctz::time_zone* timezone) {
+                             ParquetPruningStats* pruning_stats, const 
cctz::time_zone* timezone,
+                             bool null_count_trusted) {
     const auto conjuncts = metadata_pruning_conjuncts(request);
     const auto add_column_zonemap = [&](ZoneMapEvalContext* ctx, int 
slot_index,
                                         const ParquetColumnSchema* 
column_schema) {
@@ -1106,7 +1110,7 @@ bool check_native_statistics(const 
tparquet::FileMetaData& metadata,
                     ParquetStatisticsUtils::TransformColumnStatistics(
                             *column_schema,
                             safe_statistics.has_value() ? &*safe_statistics : 
nullptr,
-                            column_metadata.num_values, timezone));
+                            column_metadata.num_values, timezone, 
null_count_trusted));
         }
         add_slot_zonemap(ctx, slot_index, column_schema->type, 
std::move(zone_map));
     };
@@ -1163,7 +1167,8 @@ bool check_native_statistics(const 
tparquet::FileMetaData& metadata,
 bool check_shredded_variant_statistics(
         const tparquet::FileMetaData& metadata, const tparquet::RowGroup& 
row_group,
         const std::vector<std::unique_ptr<ParquetColumnSchema>>& file_schema,
-        const format::FileScanRequest& request, const cctz::time_zone* 
timezone) {
+        const format::FileScanRequest& request, const cctz::time_zone* 
timezone,
+        bool null_count_trusted) {
     for (const auto& conjunct : metadata_pruning_conjuncts(request)) {
         const auto predicate = extract_variant_shredded_predicate(conjunct);
         if (!predicate.has_value()) {
@@ -1193,7 +1198,7 @@ bool check_shredded_variant_statistics(
         }
         const auto statistics = 
ParquetStatisticsUtils::TransformColumnStatistics(
                 *shredding->typed_value, safe_statistics.has_value() ? 
&*safe_statistics : nullptr,
-                column_metadata.num_values, timezone);
+                column_metadata.num_values, timezone, null_count_trusted);
         const auto normalized =
                 normalize_variant_statistics(*predicate, 
*shredding->typed_value, statistics);
         if (normalized.has_value() && variant_statistics_exclude(*predicate, 
*normalized)) {
@@ -1501,6 +1506,9 @@ Status select_row_groups_by_metadata(
                                           DORIS_CHECK(column != nullptr);
                                           return column->contains_variant;
                                       });
+    const bool null_count_trusted =
+            native::parquet_reader_compat(metadata.__isset.created_by ? 
metadata.created_by : "")
+                    .null_count_trusted;
     selected_row_groups->reserve(candidate_size);
     for (size_t candidate_idx = 0; candidate_idx < candidate_size; 
++candidate_idx) {
         const int row_group_idx = candidate_row_groups == nullptr
@@ -1526,9 +1534,10 @@ Status select_row_groups_by_metadata(
         if (probe_mode != ParquetMetadataProbeMode::EXPENSIVE_ONLY &&
             has_expr_zonemap_filter(request, runtime_state) &&
             (check_native_statistics(metadata, row_group, file_schema, 
request, pruning_stats,
-                                     timezone) ||
-             (contains_variant && check_shredded_variant_statistics(
-                                          metadata, row_group, file_schema, 
request, timezone)))) {
+                                     timezone, null_count_trusted) ||
+             (contains_variant &&
+              check_shredded_variant_statistics(metadata, row_group, 
file_schema, request, timezone,
+                                                null_count_trusted)))) {
             prune_reason = ParquetRowGroupPruneReason::STATISTICS;
         }
         if (probe_mode != ParquetMetadataProbeMode::FOOTER_ONLY &&
@@ -1783,7 +1792,7 @@ bool set_native_page_boolean_min_max(const 
tparquet::ColumnIndex& column_index,
 bool build_native_page_statistics(const tparquet::ColumnIndex& column_index,
                                   const ParquetColumnSchema& column_schema, 
size_t page_idx,
                                   int64_t page_rows, ParquetColumnStatistics* 
page_statistics,
-                                  const cctz::time_zone* timezone) {
+                                  const cctz::time_zone* timezone, bool 
null_count_trusted) {
     DORIS_CHECK(page_statistics != nullptr);
     *page_statistics = {};
     if (!column_index.__isset.null_counts || page_idx >= 
column_index.null_pages.size() ||
@@ -1792,14 +1801,14 @@ bool build_native_page_statistics(const 
tparquet::ColumnIndex& column_index,
     }
     const int64_t null_count = column_index.null_counts[page_idx];
     const bool all_null = column_index.null_pages[page_idx];
-    if (page_rows < 0 || null_count < 0 || null_count > page_rows ||
-        all_null != (null_count == page_rows)) {
+    if (page_rows < 0 || (null_count_trusted && (null_count < 0 || null_count 
> page_rows ||
+                                                 all_null != (null_count == 
page_rows)))) {
         // The caller supplies the exact flat page or row-group span. 
Contradictory optional null
         // metadata must disable pruning instead of turning a partial span 
into an all-null proof.
         return false;
     }
-    page_statistics->has_null_count = true;
-    page_statistics->has_null = null_count > 0;
+    page_statistics->has_null_count = null_count_trusted;
+    page_statistics->has_null = !null_count_trusted || null_count > 0;
     page_statistics->has_not_null = !all_null;
     if (!page_statistics->has_not_null) {
         return true;
@@ -1875,14 +1884,16 @@ public:
             const std::unordered_map<int, NativeParquetPageIndex>& 
page_indexes,
             const std::vector<std::unique_ptr<ParquetColumnSchema>>& 
file_schema,
             const format::FileScanRequest& request, int64_t row_group_rows,
-            ParquetPruningStats* pruning_stats, const cctz::time_zone* 
timezone)
+            ParquetPruningStats* pruning_stats, const cctz::time_zone* 
timezone,
+            bool null_count_trusted)
             : _metadata(metadata),
               _page_indexes(page_indexes),
               _file_schema(file_schema),
               _request(request),
               _row_group_rows(row_group_rows),
               _pruning_stats(pruning_stats),
-              _timezone(timezone) {}
+              _timezone(timezone),
+              _null_count_trusted(null_count_trusted) {}
 
     std::optional<std::vector<RowRange>> evaluate(const VExprSPtr& expr) const 
{
         if (expr == nullptr || !expr->can_evaluate_zonemap_filter()) {
@@ -1995,7 +2006,8 @@ private:
                     native_page_row_range(indexes.offset_index, page_idx, 
_row_group_rows);
             ParquetColumnStatistics statistics;
             if (!build_native_page_statistics(indexes.column_index, 
*column_schema, page_idx,
-                                              page_range.length, &statistics, 
_timezone)) {
+                                              page_range.length, &statistics, 
_timezone,
+                                              _null_count_trusted)) {
                 _slot_page_zone_maps.emplace(slot_index, std::nullopt);
                 return nullptr;
             }
@@ -2013,6 +2025,7 @@ private:
     int64_t _row_group_rows;
     ParquetPruningStats* _pruning_stats;
     const cctz::time_zone* _timezone;
+    bool _null_count_trusted;
     mutable std::unordered_map<int, std::optional<SlotPageZoneMaps>> 
_slot_page_zone_maps;
 };
 
@@ -2042,6 +2055,9 @@ Status select_row_group_ranges_by_native_page_index(
     if (pruning_stats != nullptr) {
         ++pruning_stats->page_index_read_calls;
     }
+    const bool null_count_trusted =
+            native::parquet_reader_compat(metadata.__isset.created_by ? 
metadata.created_by : "")
+                    .null_count_trusted;
 
     std::map<int, VExprContextSPtrs> conjuncts_by_slot;
     VExprContextSPtrs multi_slot_conjuncts;
@@ -2079,7 +2095,8 @@ Status select_row_group_ranges_by_native_page_index(
                     native_page_row_range(indexes.offset_index, page_idx, 
row_group_rows);
             ParquetColumnStatistics statistics;
             if (!build_native_page_statistics(indexes.column_index, 
*column_schema, page_idx,
-                                              page_range.length, &statistics, 
timezone)) {
+                                              page_range.length, &statistics, 
timezone,
+                                              null_count_trusted)) {
                 usable = false;
                 break;
             }
@@ -2106,7 +2123,8 @@ Status select_row_group_ranges_by_native_page_index(
     }
 
     NativePageIndexPredicateEvaluator evaluator(metadata, page_indexes, 
file_schema, request,
-                                                row_group_rows, pruning_stats, 
timezone);
+                                                row_group_rows, pruning_stats, 
timezone,
+                                                null_count_trusted);
     for (const auto& conjunct : multi_slot_conjuncts) {
         auto conjunct_ranges = evaluator.evaluate(conjunct->root());
         if (!conjunct_ranges.has_value()) {
@@ -2148,7 +2166,8 @@ Status select_row_group_ranges_by_native_page_index(
                     native_page_row_range(indexes.offset_index, page_idx, 
row_group_rows);
             ParquetColumnStatistics statistics;
             if (!build_native_page_statistics(indexes.column_index, 
*shredding->typed_value,
-                                              page_idx, page_range.length, 
&statistics, timezone)) {
+                                              page_idx, page_range.length, 
&statistics, timezone,
+                                              null_count_trusted)) {
                 usable = false;
                 break;
             }
diff --git a/be/src/format_v2/parquet/parquet_statistics.h 
b/be/src/format_v2/parquet/parquet_statistics.h
index cd758ebb909..20cae0b4060 100644
--- a/be/src/format_v2/parquet/parquet_statistics.h
+++ b/be/src/format_v2/parquet/parquet_statistics.h
@@ -131,7 +131,8 @@ struct ParquetStatisticsUtils {
 
     static ParquetColumnStatistics TransformColumnStatistics(
             const ParquetColumnSchema& column_schema, const 
tparquet::Statistics* statistics,
-            int64_t column_value_count, const cctz::time_zone* timezone = 
nullptr);
+            int64_t column_value_count, const cctz::time_zone* timezone = 
nullptr,
+            bool null_count_trusted = true);
 
     static bool NativeBloomFilterExcludes(const ParquetColumnSchema& 
column_schema, int slot_index,
                                           const VExprContextSPtrs& conjuncts,
diff --git a/be/src/format_v2/parquet/reader/native/column_chunk_reader.cpp 
b/be/src/format_v2/parquet/reader/native/column_chunk_reader.cpp
index d1677c66676..89df1d75f86 100644
--- a/be/src/format_v2/parquet/reader/native/column_chunk_reader.cpp
+++ b/be/src/format_v2/parquet/reader/native/column_chunk_reader.cpp
@@ -189,10 +189,18 @@ ParquetReaderCompat parquet_reader_compat(const 
std::string& created_by) {
         return {};
     }
     const ::parquet::ApplicationVersion version(created_by);
+    const ::parquet::ApplicationVersion 
arrow_fixed_null_count_version("parquet-cpp-arrow", 6, 0,
+                                                                       0);
     return {.parquet_816_padding =
                     
version.VersionLt(::parquet::ApplicationVersion::PARQUET_816_FIXED_VERSION()),
             .data_page_v2_always_compressed = version.VersionLt(
-                    
::parquet::ApplicationVersion::PARQUET_CPP_10353_FIXED_VERSION())};
+                    
::parquet::ApplicationVersion::PARQUET_CPP_10353_FIXED_VERSION()),
+            // Arrow <= 3 used parquet-cpp, then Arrow 4/5 used 
parquet-cpp-arrow before the fix.
+            .null_count_trusted =
+                    version.application_ != "parquet-cpp" &&
+                    !version.VersionLt(arrow_fixed_null_count_version) &&
+                    !version.VersionLt(
+                            
::parquet::ApplicationVersion::PARQUET_MR_FIXED_STATS_VERSION())};
 }
 
 Status compute_column_chunk_range(const tparquet::ColumnMetaData& metadata, 
size_t file_size,
diff --git a/be/src/format_v2/parquet/reader/native/column_chunk_reader.h 
b/be/src/format_v2/parquet/reader/native/column_chunk_reader.h
index e866e8b1c49..a7701b7ec34 100644
--- a/be/src/format_v2/parquet/reader/native/column_chunk_reader.h
+++ b/be/src/format_v2/parquet/reader/native/column_chunk_reader.h
@@ -63,6 +63,8 @@ struct ColumnChunkRange {
 struct ParquetReaderCompat {
     bool parquet_816_padding = false;
     bool data_page_v2_always_compressed = false;
+    // Legacy parquet-mr and Arrow writers can under-report null_count.
+    bool null_count_trusted = true;
 };
 
 ParquetReaderCompat parquet_reader_compat(const std::string& created_by);
diff --git a/be/test/format_v2/parquet/parquet_scan_test.cpp 
b/be/test/format_v2/parquet/parquet_scan_test.cpp
index d12f78c9b10..17d80897b7a 100644
--- a/be/test/format_v2/parquet/parquet_scan_test.cpp
+++ b/be/test/format_v2/parquet/parquet_scan_test.cpp
@@ -26,6 +26,7 @@
 #include <parquet/encoding.h>
 
 #include <bit>
+#include <cstdlib>
 #include <cstring>
 #include <filesystem>
 #include <fstream>
@@ -3405,6 +3406,53 @@ TEST_F(ParquetScanTest, 
PredicateOnlyPlainStringNullPredicatesUseDefinitionLevel
     }
 }
 
+TEST_F(ParquetScanTest, PyArrow3DictionaryNullCountFixturePreservesIsNullRows) 
{
+    const char* source_root = std::getenv("ROOT");
+    ASSERT_NE(source_root, nullptr);
+    const auto fixture_path =
+            std::filesystem::path(source_root) /
+            
"be/test/format_v2/parquet/test_data/pyarrow-3.0.0-dictionary-null-count.parquet";
+    ASSERT_TRUE(std::filesystem::copy_file(fixture_path, _file_path));
+
+    const auto fixture_reader = 
::parquet::ParquetFileReader::OpenFile(_file_path, false);
+    const auto fixture_metadata = fixture_reader->metadata();
+    ASSERT_EQ(fixture_metadata->created_by(), "parquet-cpp version 
1.5.1-SNAPSHOT");
+    const auto fixture_statistics = 
fixture_metadata->RowGroup(0)->ColumnChunk(0)->statistics();
+    ASSERT_NE(fixture_statistics, nullptr);
+    ASSERT_EQ(fixture_statistics->null_count(), 0);
+
+    RuntimeProfile profile("profile");
+    auto reader = create_reader(0, -1, &profile);
+    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(), 2);
+    auto request = std::make_shared<format::FileScanRequest>();
+    format::FileScanRequestBuilder request_builder(request.get());
+    
ASSERT_TRUE(request_builder.add_predicate_column(format::LocalColumnId(0)).ok());
+    
ASSERT_TRUE(request_builder.add_non_predicate_column(format::LocalColumnId(1)).ok());
+    request->predicate_only_columns.push_back(format::LocalColumnId(0));
+    auto conjunct = create_string_null_conjunct(0, true);
+    ASSERT_TRUE(conjunct->prepare(&state, RowDescriptor()).ok());
+    ASSERT_TRUE(conjunct->open(&state).ok());
+    request->conjuncts.push_back(conjunct);
+    ASSERT_TRUE(reader->open(request).ok());
+
+    ColumnInt32::Container actual;
+    bool eof = false;
+    while (!eof) {
+        Block block = build_file_block(schema);
+        size_t rows = 0;
+        ASSERT_TRUE(reader->get_block(&block, &rows, &eof).ok());
+        const auto& ids = 
int32_data_column(*block.get_by_position(1).column).get_data();
+        actual.insert(actual.end(), ids.begin(), ids.end());
+    }
+    EXPECT_EQ(actual, (ColumnInt32::Container {0, 3, 6, 9, 12}));
+    conjunct->close();
+}
+
 TEST_F(ParquetScanTest, 
PredicateOnlyDictionaryIntNullPredicatesUseDefinitionLevels) {
     for (const bool is_null : {true, false}) {
         write_dictionary_int_pair_parquet_file(_file_path);
diff --git a/be/test/format_v2/parquet/parquet_statistics_test.cpp 
b/be/test/format_v2/parquet/parquet_statistics_test.cpp
index 4aa996ab00c..9f750faddda 100644
--- a/be/test/format_v2/parquet/parquet_statistics_test.cpp
+++ b/be/test/format_v2/parquet/parquet_statistics_test.cpp
@@ -408,6 +408,29 @@ private:
     const std::string _expr_name = "MetadataInt32GreaterThanExpr";
 };
 
+class MetadataIsNullExpr final : public VExpr {
+public:
+    MetadataIsNullExpr() : VExpr(std::make_shared<DataTypeUInt8>(), false) {}
+
+    const std::string& expr_name() const override { return _expr_name; }
+    Status execute_column_impl(VExprContext*, const Block*, const Selector*, 
size_t,
+                               ColumnPtr&) const override {
+        return Status::InternalError("MetadataIsNullExpr is metadata-only");
+    }
+    bool can_evaluate_zonemap_filter() const override { return true; }
+    void collect_slot_column_ids(std::set<int>& column_ids) const override { 
column_ids.insert(0); }
+    ZoneMapFilterResult evaluate_zonemap_filter(const ZoneMapEvalContext& ctx) 
const override {
+        const auto zone_map = ctx.zone_map(0);
+        if (zone_map == nullptr) {
+            return unsupported_zonemap_filter(ctx);
+        }
+        return zone_map->has_null ? ZoneMapFilterResult::kMayMatch : 
ZoneMapFilterResult::kNoMatch;
+    }
+
+private:
+    const std::string _expr_name = "MetadataIsNullExpr";
+};
+
 class MetadataSlotInt32GreaterThanExpr final : public VExpr {
 public:
     MetadataSlotInt32GreaterThanExpr(int slot_index, int32_t value)
@@ -1859,6 +1882,90 @@ TEST(NativeParquetStatisticsTest, 
TypeDefinedBoundsRequireSupportedColumnOrder)
     EXPECT_TRUE(selected_ranges.empty());
 }
 
+TEST(NativeParquetStatisticsTest, WriterVersionControlsNullCountPruningTrust) {
+    const 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 = make_nullable(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(3));
+    statistics.__set_null_count(0);
+    tparquet::ColumnMetaData column_metadata;
+    column_metadata.__set_type(tparquet::Type::INT32);
+    column_metadata.__set_num_values(3);
+    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(3);
+    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<MetadataIsNullExpr>())};
+
+    format::parquet::NativeParquetPageIndex page_index;
+    page_index.column_index.__set_min_values({encode_int32(1)});
+    page_index.column_index.__set_max_values({encode_int32(3)});
+    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));
+
+    const auto check_writer = [&](const std::string& created_by, bool 
expect_selected) {
+        SCOPED_TRACE(created_by);
+        metadata.__set_created_by(created_by);
+        std::vector<int> selected_row_groups;
+        ASSERT_TRUE(format::parquet::select_row_groups_by_metadata(metadata, 
schema, request,
+                                                                   nullptr, 
&selected_row_groups,
+                                                                   false, 
nullptr)
+                            .ok());
+        EXPECT_EQ(!selected_row_groups.empty(), expect_selected);
+
+        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, metadata.row_groups[0], page_indexes, 
schema, request, 3,
+                            &selected_ranges, &skip_plans, nullptr)
+                            .ok());
+        EXPECT_EQ(!selected_ranges.empty(), expect_selected);
+    };
+
+    check_writer("parquet-mr version 1.9.0-cdh6.3.2 (build test)", true);
+    check_writer("parquet-mr version 1.13.1 (build test)", false);
+    check_writer("parquet-cpp version 1.2.8 (build test)", true);
+    check_writer("parquet-cpp version 1.5.1-SNAPSHOT", true);
+    check_writer("parquet-cpp-arrow version 4.0.1", true);
+    check_writer("parquet-cpp-arrow version 5.0.0", true);
+    check_writer("parquet-cpp-arrow version 6.0.0", false);
+    check_writer("custom-writer version 1.0.0 (build test)", false);
+    check_writer("", false);
+}
+
 TEST(NativeParquetStatisticsTest, 
RuntimeFilterWrapperKeepsScalarPageIndexPruning) {
     auto encode_int32 = [](int32_t value) {
         std::string bytes(sizeof(value), '\0');
diff --git 
a/be/test/format_v2/parquet/test_data/pyarrow-3.0.0-dictionary-null-count.parquet
 
b/be/test/format_v2/parquet/test_data/pyarrow-3.0.0-dictionary-null-count.parquet
new file mode 100644
index 00000000000..a5173a34309
Binary files /dev/null and 
b/be/test/format_v2/parquet/test_data/pyarrow-3.0.0-dictionary-null-count.parquet
 differ


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

Reply via email to