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 a1359783afc [fix](parquet) Bound legacy Bloom filter metadata reads 
(#66820)
a1359783afc is described below

commit a1359783afc5cd56955f8cdd93cf64cf89528114
Author: Gabriel <[email protected]>
AuthorDate: Thu Aug 27 15:18:28 2026 +0800

    [fix](parquet) Bound legacy Bloom filter metadata reads (#66820)
    
    ### What problem does this PR solve?
    
    The legacy Parquet reader trusts Bloom filter offsets and sizes from
    file metadata before it validates the complete on-disk layout. Malformed
    metadata can therefore cause excessive allocation, invalid range access,
    or incorrect row-group pruning when a payload is truncated or its
    declared length contradicts the Bloom header.
    
    ### Scope
    
    This PR is intentionally limited to the legacy Parquet V1 Bloom filter
    read path under `be/src/format/parquet`.
    
    In scope:
    
    - Validate the Bloom filter offset and optional declared length before
    allocation.
    - Bound the header read independently from the metadata-controlled
    length.
    - Require the declared length, when present, to exactly match the
    decoded header plus payload.
    - Validate payload bounds, the 128 MiB per-filter limit, and 32-byte
    split-block alignment.
    - Read the payload into one memory-tracked owner and reject successful
    short reads.
    - Bound the V1 per-row-group Bloom filter cache to 16 MiB; larger valid
    filters remain usable but may be read again for another predicate.
    - Fall back conservatively on malformed metadata so it cannot produce
    false-negative pruning.
    
    Out of scope:
    
    - The format V2/native Parquet reader.
    - Generic Thrift deserialization limits or generated Thrift container
    accounting.
    - Parquet page index and page header memory accounting.
    - DORIS-27978.
    
    ### Behavior and compatibility
    
    - Valid legacy Bloom filters continue to participate in pruning.
    - Older Parquet metadata without the optional `bloom_filter_length`
    remains supported.
    - Malformed, contradictory, out-of-range, oversized, misaligned, or
    truncated Bloom filters are ignored conservatively.
    - The Parquet wire format, write path, and format V2 reader are
    unchanged.
    
    ### Testing
    
    - `ParquetStatisticsTest.*` under ASAN.
    - Clang-format 16.0.6 dry-run with `--Werror` on every changed C++
    source and header.
    - `git diff --check` and a five-file scope allowlist check.
---
 .../parquet/parquet_block_split_bloom_filter.cpp   |  39 ++++-
 .../parquet/parquet_block_split_bloom_filter.h     |   6 +
 be/src/format/parquet/parquet_predicate.h          |  69 +++++---
 be/src/format/parquet/vparquet_reader.cpp          |  10 ++
 be/test/format/parquet/parquet_statistics_test.cpp | 176 +++++++++++++++++++++
 5 files changed, 273 insertions(+), 27 deletions(-)

diff --git a/be/src/format/parquet/parquet_block_split_bloom_filter.cpp 
b/be/src/format/parquet/parquet_block_split_bloom_filter.cpp
index 7f93681df03..b8cbacbfa9b 100644
--- a/be/src/format/parquet/parquet_block_split_bloom_filter.cpp
+++ b/be/src/format/parquet/parquet_block_split_bloom_filter.cpp
@@ -23,6 +23,22 @@
 
 namespace doris {
 
+ParquetBlockSplitBloomFilter::~ParquetBlockSplitBloomFilter() {
+    if (_data == nullptr) {
+        return;
+    }
+    if (_is_write) {
+        g_write_bloom_filter_total_bytes << -static_cast<int64_t>(_size);
+        g_write_bloom_filter_num << -1;
+    } else {
+        g_read_bloom_filter_total_bytes << -static_cast<int64_t>(_size);
+        g_read_bloom_filter_num << -1;
+    }
+    g_total_bloom_filter_total_bytes << -static_cast<int64_t>(_size);
+    // The derived owner uses Doris's tracked allocator, so the base must not 
delete this view.
+    _data = nullptr;
+}
+
 // for write
 Status ParquetBlockSplitBloomFilter::init(uint64_t filter_size,
                                           segment_v2::HashStrategyPB strategy) 
{
@@ -37,7 +53,8 @@ Status ParquetBlockSplitBloomFilter::init(uint64_t 
filter_size,
     }
     _num_bytes = filter_size;
     _size = _num_bytes;
-    _data = new char[_size];
+    _owned_data = make_unique_buffer<char>(_size);
+    _data = _owned_data.get();
     memset(_data, 0, _size);
     _has_null = nullptr;
     _is_write = true;
@@ -51,10 +68,20 @@ Status ParquetBlockSplitBloomFilter::init(uint64_t 
filter_size,
 // use deep copy to acquire the data
 Status ParquetBlockSplitBloomFilter::init(const char* buf, size_t size,
                                           segment_v2::HashStrategyPB strategy) 
{
-    if (size <= 1) {
+    if (buf == nullptr || size <= 1) {
         return Status::InvalidArgument("invalid size:{}", size);
     }
     DCHECK(size > 1);
+    RETURN_IF_ERROR(init_for_read(size, strategy));
+    memcpy(_data, buf, size);
+    return Status::OK();
+}
+
+Status ParquetBlockSplitBloomFilter::init_for_read(size_t size,
+                                                   segment_v2::HashStrategyPB 
strategy) {
+    if (size <= 1) {
+        return Status::InvalidArgument("invalid size:{}", size);
+    }
     if (strategy == XX_HASH_64) {
         _hash_func = [](const void* buf, const int64_t len, const uint64_t 
seed, void* out) {
             auto h =
@@ -64,12 +91,8 @@ Status ParquetBlockSplitBloomFilter::init(const char* buf, 
size_t size,
     } else {
         return Status::InvalidArgument("invalid strategy:{}", strategy);
     }
-    if (buf == nullptr) {
-        return Status::InvalidArgument("buf is nullptr");
-    }
-
-    _data = new char[size];
-    memcpy(_data, buf, size);
+    _owned_data = make_unique_buffer<char>(size);
+    _data = _owned_data.get();
     _size = size;
     _num_bytes = _size;
     _has_null = nullptr;
diff --git a/be/src/format/parquet/parquet_block_split_bloom_filter.h 
b/be/src/format/parquet/parquet_block_split_bloom_filter.h
index b27e9bab8fe..186ebb1931d 100644
--- a/be/src/format/parquet/parquet_block_split_bloom_filter.h
+++ b/be/src/format/parquet/parquet_block_split_bloom_filter.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 
+#include "core/custom_allocator.h"
 #include "storage/index/bloom_filter/bloom_filter.h"
 
 namespace doris {
@@ -36,8 +37,11 @@ namespace doris {
 // https://parquet.apache.org/docs/file-format/bloomfilter/
 class ParquetBlockSplitBloomFilter : public segment_v2::BloomFilter {
 public:
+    ~ParquetBlockSplitBloomFilter() override;
     Status init(uint64_t filter_size, segment_v2::HashStrategyPB strategy) 
override;
     Status init(const char* buf, size_t size, segment_v2::HashStrategyPB 
strategy) override;
+    Status init_for_read(size_t size, segment_v2::HashStrategyPB strategy);
+    char* mutable_data() { return _data; }
     void add_bytes(const char* buf, size_t size) override;
     bool test_bytes(const char* buf, size_t size) const override;
     void set_has_null(bool has_null) override;
@@ -62,6 +66,8 @@ private:
     };
 
 private:
+    DorisUniqueBufferPtr<char> _owned_data;
+
     void _set_masks(uint32_t key, BlockMask& block_mask) const {
         for (int i = 0; i < BITS_SET_PER_BLOCK; ++i) {
             block_mask.item[i] = key * SALT[i];
diff --git a/be/src/format/parquet/parquet_predicate.h 
b/be/src/format/parquet/parquet_predicate.h
index a0f6c75bdef..ee0f3b60d83 100644
--- a/be/src/format/parquet/parquet_predicate.h
+++ b/be/src/format/parquet/parquet_predicate.h
@@ -441,45 +441,76 @@ public:
     static Status read_bloom_filter(const tparquet::ColumnMetaData& 
column_meta_data,
                                     io::FileReaderSPtr file_reader, 
io::IOContext* io_ctx,
                                     ColumnStat* ans_stat) {
-        size_t size;
         if (!column_meta_data.__isset.bloom_filter_offset) {
             return Status::NotSupported("Can not use this parquet bloom 
filter.");
         }
+        if (column_meta_data.bloom_filter_offset < 0 ||
+            (column_meta_data.__isset.bloom_filter_length &&
+             column_meta_data.bloom_filter_length <= 0)) {
+            return Status::Corruption("Invalid Parquet bloom filter offset or 
declared length");
+        }
 
-        if (column_meta_data.__isset.bloom_filter_length &&
-            column_meta_data.bloom_filter_length > 0) {
-            size = column_meta_data.bloom_filter_length;
-        } else {
-            size = BLOOM_FILTER_MAX_HEADER_LENGTH;
+        const uint64_t bloom_offset = 
static_cast<uint64_t>(column_meta_data.bloom_filter_offset);
+        if (bloom_offset >= file_reader->size()) {
+            return Status::Corruption("Parquet bloom filter offset exceeds 
file size");
         }
+        const size_t available = file_reader->size() - bloom_offset;
+        const size_t declared_available =
+                column_meta_data.__isset.bloom_filter_length
+                        ? 
std::min<size_t>(column_meta_data.bloom_filter_length, available)
+                        : available;
+        const size_t header_read_size =
+                std::min<size_t>(declared_available, 
BLOOM_FILTER_MAX_HEADER_LENGTH);
         size_t bytes_read = 0;
-        std::vector<uint8_t> header_buffer(size);
+        std::vector<uint8_t> header_buffer(header_read_size);
         
RETURN_IF_ERROR(file_reader->read_at(column_meta_data.bloom_filter_offset,
-                                             Slice(header_buffer.data(), 
size), &bytes_read,
-                                             io_ctx));
+                                             Slice(header_buffer.data(), 
header_buffer.size()),
+                                             &bytes_read, io_ctx));
 
         tparquet::BloomFilterHeader t_bloom_filter_header;
         uint32_t t_bloom_filter_header_size = 
static_cast<uint32_t>(bytes_read);
-        RETURN_IF_ERROR(deserialize_thrift_msg(header_buffer.data(), 
&t_bloom_filter_header_size,
-                                               true, &t_bloom_filter_header));
+        if (!deserialize_thrift_msg(header_buffer.data(), 
&t_bloom_filter_header_size, true,
+                                    &t_bloom_filter_header)
+                     .ok()) {
+            return Status::Corruption("Malformed Parquet bloom filter header");
+        }
 
         // TODO the bloom filter could be encrypted, too, so need to double 
check that this is NOT the case
         if (!t_bloom_filter_header.algorithm.__isset.BLOCK ||
             !t_bloom_filter_header.compression.__isset.UNCOMPRESSED ||
-            !t_bloom_filter_header.hash.__isset.XXHASH) {
+            !t_bloom_filter_header.hash.__isset.XXHASH || 
t_bloom_filter_header.numBytes <= 0) {
             return Status::NotSupported("Can not use this parquet bloom 
filter.");
         }
 
-        ans_stat->bloom_filter = 
std::make_unique<ParquetBlockSplitBloomFilter>();
+        const int64_t payload_size = t_bloom_filter_header.numBytes;
+        if (payload_size < segment_v2::BloomFilter::MINIMUM_BYTES ||
+            payload_size > segment_v2::BloomFilter::MAXIMUM_BYTES || 
payload_size % 32 != 0) {
+            return Status::Corruption("Invalid Parquet bloom filter payload 
size {}", payload_size);
+        }
+        const uint64_t total_size =
+                static_cast<uint64_t>(t_bloom_filter_header_size) + 
payload_size;
+        if (total_size > available) {
+            return Status::Corruption("Parquet bloom filter range exceeds file 
size");
+        }
+        const auto expected_declared_length = static_cast<int64_t>(total_size);
+        if (column_meta_data.__isset.bloom_filter_length &&
+            column_meta_data.bloom_filter_length != expected_declared_length) {
+            return Status::Corruption("Invalid Parquet bloom filter declared 
length");
+        }
 
-        std::vector<uint8_t> data_buffer(t_bloom_filter_header.numBytes);
+        auto bloom_filter = std::make_unique<ParquetBlockSplitBloomFilter>();
+        // Read directly into one tracked allocation so a valid maximum-size 
filter is admitted
+        // against the task budget without doubling its peak memory during 
initialization.
+        
RETURN_IF_ERROR(bloom_filter->init_for_read(static_cast<size_t>(payload_size),
+                                                    
segment_v2::HashStrategyPB::XX_HASH_64));
         RETURN_IF_ERROR(file_reader->read_at(
-                column_meta_data.bloom_filter_offset + 
t_bloom_filter_header_size,
-                Slice(data_buffer.data(), t_bloom_filter_header.numBytes), 
&bytes_read, io_ctx));
+                static_cast<size_t>(bloom_offset) + t_bloom_filter_header_size,
+                Slice(bloom_filter->mutable_data(), bloom_filter->size()), 
&bytes_read, io_ctx));
+        if (bytes_read != bloom_filter->size()) {
+            return Status::Corruption("Truncated Parquet bloom filter 
payload");
+        }
 
-        RETURN_IF_ERROR(ans_stat->bloom_filter->init(
-                reinterpret_cast<const char*>(data_buffer.data()), 
t_bloom_filter_header.numBytes,
-                segment_v2::HashStrategyPB::XX_HASH_64));
+        ans_stat->bloom_filter = std::move(bloom_filter);
 
         return Status::OK();
     }
diff --git a/be/src/format/parquet/vparquet_reader.cpp 
b/be/src/format/parquet/vparquet_reader.cpp
index b5c4c01be40..b1b233aa0d5 100644
--- a/be/src/format/parquet/vparquet_reader.cpp
+++ b/be/src/format/parquet/vparquet_reader.cpp
@@ -1507,6 +1507,8 @@ Status ParquetReader::_process_column_stat_filter(
     // Cache bloom filters for each column to avoid reading the same bloom 
filter multiple times
     // when there are multiple predicates on the same column
     std::unordered_map<int, std::unique_ptr<ParquetBlockSplitBloomFilter>> 
bloom_filter_cache;
+    constexpr size_t MAX_CACHED_BLOOM_FILTER_BYTES = 16 * 1024 * 1024;
+    size_t cached_bloom_filter_bytes = 0;
 
     // Initialize output parameters
     *filtered_by_min_max = false;
@@ -1565,6 +1567,7 @@ Status ParquetReader::_process_column_stat_filter(
                     auto cache_iter = bloom_filter_cache.find(parquet_col_id);
                     if (cache_iter != bloom_filter_cache.end()) {
                         // Bloom filter already loaded for this column, reuse 
it
+                        cached_bloom_filter_bytes -= 
cache_iter->second->size();
                         stat->bloom_filter = std::move(cache_iter->second);
                         bloom_filter_cache.erase(cache_iter);
                         return stat->bloom_filter != nullptr;
@@ -1607,6 +1610,12 @@ Status ParquetReader::_process_column_stat_filter(
 
         // After evaluating, if the bloom filter was used, cache it for 
subsequent predicates
         if (stat.bloom_filter) {
+            // Large filters remain tracked but are not retained across 
predicates; this bounds the
+            // row-group cache independently of the number of predicate 
columns.
+            if (stat.bloom_filter->size() >
+                MAX_CACHED_BLOOM_FILTER_BYTES - cached_bloom_filter_bytes) {
+                continue;
+            }
             // Find the column id for caching
             for (auto* slot : _tuple_descriptor->slots()) {
                 if 
(_table_info_node_ptr->children_column_exists(slot->col_name())) {
@@ -1616,6 +1625,7 @@ Status ParquetReader::_process_column_stat_filter(
                             _file_metadata->schema().get_column(file_col_name);
                     int parquet_col_id = col_schema->physical_column_index;
                     if (stat.col_schema == col_schema) {
+                        cached_bloom_filter_bytes += stat.bloom_filter->size();
                         bloom_filter_cache[parquet_col_id] = 
std::move(stat.bloom_filter);
                         break;
                     }
diff --git a/be/test/format/parquet/parquet_statistics_test.cpp 
b/be/test/format/parquet/parquet_statistics_test.cpp
index d52320c5169..eeae08326cf 100644
--- a/be/test/format/parquet/parquet_statistics_test.cpp
+++ b/be/test/format/parquet/parquet_statistics_test.cpp
@@ -17,16 +17,192 @@
 
 #include <gtest/gtest.h>
 
+#include <algorithm>
+#include <cstring>
+#include <memory>
 #include <regex>
+#include <vector>
 
 #include "format/parquet/parquet_predicate.h"
+#include "util/thrift_util.h"
 
 namespace doris {
+namespace {
+
+class BloomFilterFileReader final : public io::FileReader {
+public:
+    explicit BloomFilterFileReader(std::vector<uint8_t> data, size_t 
logical_size = 0)
+            : _data(std::move(data)),
+              _logical_size(logical_size == 0 ? _data.size() : logical_size) {}
+
+    Status close() override {
+        _closed = true;
+        return Status::OK();
+    }
+
+    const io::Path& path() const override { return _path; }
+    size_t size() const override { return _logical_size; }
+    bool closed() const override { return _closed; }
+    int64_t mtime() const override { return 0; }
+    bool returned_short_nonzero_offset_read() const { return 
_returned_short_nonzero_offset_read; }
+
+protected:
+    Status read_at_impl(size_t offset, Slice result, size_t* bytes_read,
+                        const io::IOContext* io_ctx) override {
+        if (offset > _data.size()) {
+            return Status::IOError("Out of bounds");
+        }
+        *bytes_read = std::min(result.size, _data.size() - offset);
+        memcpy(result.data, _data.data() + offset, *bytes_read);
+        _returned_short_nonzero_offset_read |= offset > 0 && *bytes_read != 
result.size;
+        return Status::OK();
+    }
+
+private:
+    std::vector<uint8_t> _data;
+    size_t _logical_size;
+    io::Path _path = "parquet_bloom_filter_test";
+    bool _closed = false;
+    bool _returned_short_nonzero_offset_read = false;
+};
+
+Status read_test_bloom_filter(int32_t header_payload_size, size_t 
actual_payload_size,
+                              int32_t declared_length_adjustment = 0,
+                              size_t logical_payload_size = 0, bool* 
returned_short_read = nullptr,
+                              bool* installed_bloom_filter = nullptr) {
+    tparquet::BloomFilterAlgorithm algorithm;
+    algorithm.__set_BLOCK(tparquet::SplitBlockAlgorithm());
+    tparquet::BloomFilterHash hash;
+    hash.__set_XXHASH(tparquet::XxHash());
+    tparquet::BloomFilterCompression compression;
+    compression.__set_UNCOMPRESSED(tparquet::Uncompressed());
+    tparquet::BloomFilterHeader header;
+    header.__set_numBytes(header_payload_size);
+    header.__set_algorithm(algorithm);
+    header.__set_hash(hash);
+    header.__set_compression(compression);
+
+    std::vector<uint8_t> file_bytes;
+    ThriftSerializer serializer(/*compact=*/true, /*initial_buffer_size=*/64);
+    RETURN_IF_ERROR(serializer.serialize(&header, &file_bytes));
+    const size_t header_size = file_bytes.size();
+    file_bytes.resize(header_size + actual_payload_size);
+
+    tparquet::ColumnMetaData metadata;
+    metadata.__set_bloom_filter_offset(0);
+    metadata.__set_bloom_filter_length(static_cast<int32_t>(file_bytes.size()) 
+
+                                       declared_length_adjustment);
+    const size_t logical_size =
+            logical_payload_size == 0 ? file_bytes.size() : header_size + 
logical_payload_size;
+    auto reader = 
std::make_shared<BloomFilterFileReader>(std::move(file_bytes), logical_size);
+    ParquetPredicate::ColumnStat stat;
+    Status status = ParquetPredicate::read_bloom_filter(metadata, reader, 
nullptr, &stat);
+    if (returned_short_read != nullptr) {
+        *returned_short_read = reader->returned_short_nonzero_offset_read();
+    }
+    if (installed_bloom_filter != nullptr) {
+        *installed_bloom_filter = stat.bloom_filter != nullptr;
+    }
+    return status;
+}
+
+} // namespace
+
 class ParquetStatisticsTest : public testing::Test {
 public:
     ParquetStatisticsTest() = default;
 };
 
+TEST_F(ParquetStatisticsTest, reject_truncated_bloom_filter_payload) {
+    // The reader may legally return a short read at EOF, so accepting it 
would initialize a
+    // Bloom filter whose missing bytes came from zero-filled process memory.
+    bool returned_short_read = false;
+    bool installed_bloom_filter = true;
+    EXPECT_FALSE(read_test_bloom_filter(/*header_payload_size=*/64, 
/*actual_payload_size=*/32,
+                                        /*declared_length_adjustment=*/32,
+                                        /*logical_payload_size=*/64, 
&returned_short_read,
+                                        &installed_bloom_filter)
+                         .ok());
+    EXPECT_TRUE(returned_short_read);
+    EXPECT_FALSE(installed_bloom_filter);
+}
+
+TEST_F(ParquetStatisticsTest, reject_bloom_filter_range_beyond_file) {
+    bool returned_short_read = false;
+    EXPECT_FALSE(read_test_bloom_filter(/*header_payload_size=*/64, 
/*actual_payload_size=*/32,
+                                        /*declared_length_adjustment=*/0,
+                                        /*logical_payload_size=*/0, 
&returned_short_read)
+                         .ok());
+    EXPECT_FALSE(returned_short_read);
+}
+
+TEST_F(ParquetStatisticsTest, reject_declared_bloom_filter_length_mismatch) {
+    // A present length describes exactly one header and payload. Treating it 
as an upper bound can
+    // reinterpret a multi-block filter as a smaller filter and cause 
false-negative pruning.
+    EXPECT_FALSE(
+            read_test_bloom_filter(/*header_payload_size=*/32, 
/*actual_payload_size=*/64).ok());
+}
+
+TEST_F(ParquetStatisticsTest, reject_invalid_bloom_filter_block_sizes) {
+    EXPECT_FALSE(
+            read_test_bloom_filter(/*header_payload_size=*/16, 
/*actual_payload_size=*/16).ok());
+    EXPECT_FALSE(
+            read_test_bloom_filter(/*header_payload_size=*/33, 
/*actual_payload_size=*/33).ok());
+}
+
+TEST_F(ParquetStatisticsTest, reject_nonpositive_bloom_filter_declared_length) 
{
+    const int32_t declared_length_adjustment = -1000;
+    EXPECT_FALSE(read_test_bloom_filter(/*header_payload_size=*/32, 
/*actual_payload_size=*/32,
+                                        declared_length_adjustment)
+                         .ok());
+}
+
+TEST_F(ParquetStatisticsTest, accept_valid_bloom_filter_layout) {
+    EXPECT_TRUE(
+            read_test_bloom_filter(/*header_payload_size=*/32, 
/*actual_payload_size=*/32).ok());
+}
+
+TEST_F(ParquetStatisticsTest, 
accept_bloom_filter_without_declared_length_before_trailing_bytes) {
+    constexpr int32_t present_value = 1;
+    ParquetBlockSplitBloomFilter source;
+    ASSERT_TRUE(source.init(segment_v2::BloomFilter::MINIMUM_BYTES,
+                            segment_v2::HashStrategyPB::XX_HASH_64)
+                        .ok());
+    source.add_bytes(reinterpret_cast<const char*>(&present_value), 
sizeof(present_value));
+    int32_t absent_value = 2;
+    while (source.test_bytes(reinterpret_cast<const char*>(&absent_value), 
sizeof(absent_value))) {
+        ++absent_value;
+    }
+
+    tparquet::BloomFilterAlgorithm algorithm;
+    algorithm.__set_BLOCK(tparquet::SplitBlockAlgorithm());
+    tparquet::BloomFilterHash hash;
+    hash.__set_XXHASH(tparquet::XxHash());
+    tparquet::BloomFilterCompression compression;
+    compression.__set_UNCOMPRESSED(tparquet::Uncompressed());
+    tparquet::BloomFilterHeader header;
+    header.__set_numBytes(static_cast<int32_t>(source.size()));
+    header.__set_algorithm(algorithm);
+    header.__set_hash(hash);
+    header.__set_compression(compression);
+    std::vector<uint8_t> file_bytes;
+    ThriftSerializer serializer(/*compact=*/true, /*initial_buffer_size=*/64);
+    ASSERT_TRUE(serializer.serialize(&header, &file_bytes).ok());
+    file_bytes.insert(file_bytes.end(), source.data(), source.data() + 
source.size());
+    file_bytes.resize(file_bytes.size() + 64);
+
+    tparquet::ColumnMetaData metadata;
+    metadata.__set_bloom_filter_offset(0);
+    auto reader = 
std::make_shared<BloomFilterFileReader>(std::move(file_bytes));
+    ParquetPredicate::ColumnStat stat;
+    ASSERT_TRUE(ParquetPredicate::read_bloom_filter(metadata, reader, nullptr, 
&stat).ok());
+    ASSERT_NE(stat.bloom_filter, nullptr);
+    EXPECT_TRUE(stat.bloom_filter->test_bytes(reinterpret_cast<const 
char*>(&present_value),
+                                              sizeof(present_value)));
+    EXPECT_FALSE(stat.bloom_filter->test_bytes(reinterpret_cast<const 
char*>(&absent_value),
+                                               sizeof(absent_value)));
+}
+
 TEST_F(ParquetStatisticsTest, test_try_read_old_utf8_stats) {
     // [, bcé]: min is empty, max starts with ASCII
     {


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

Reply via email to