From 1cb92fc2c27b8eb1fadb9d6fb2376d9d77a56e43 Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Thu, 11 Jun 2026 23:01:35 +0200 Subject: [PATCH 01/13] Refactor hybrid scan multifile API to enhance bloom filter functionality - Renamed `secondary_filters_byte_ranges` to `bloom_filters_byte_ranges` for clarity and updated its return type to a single vector of byte ranges. - Introduced a new method `filter_row_groups_with_bloom_filters` to filter row groups using bloom filters. - Updated implementation in `hybrid_scan_impl` and `hybrid_scan_multifile` to support the new API. - Added tests for the new bloom filter functionality to ensure correctness. This change improves the usability and clarity of the hybrid scan multifile API regarding bloom filter operations. --- .../io/experimental/hybrid_scan_multifile.hpp | 29 +++++++--- .../parquet/experimental/hybrid_scan_impl.cpp | 16 +++++ .../parquet/experimental/hybrid_scan_impl.hpp | 7 +++ .../experimental/hybrid_scan_multifile.cpp | 16 ++++- .../hybrid_scan_multifile_filters_test.cpp | 58 +++++++++++++++++++ 5 files changed, 116 insertions(+), 10 deletions(-) diff --git a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp index 8da2f535e961..74982878b521 100644 --- a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp +++ b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp @@ -152,20 +152,35 @@ class hybrid_scan_multifile { rmm::cuda_stream_view stream) const; /** - * @brief Get byte ranges of bloom filters and dictionary pages (secondary filters) for row group - * pruning + * @brief Get byte ranges of bloom filters for row group pruning * * @note Device buffers for bloom filter byte ranges must be allocated using a 32 byte * aligned memory resource * * @param row_group_indices Input row group indices, one per source * @param options Parquet reader options - * @return Pair of vectors of byte ranges of column chunk with bloom filters and dictionary - * pages subject to filter predicate + * @return Vector of byte ranges of column chunk bloom filters subject to filter predicate */ - [[nodiscard]] std::pair, std::vector> - secondary_filters_byte_ranges(cudf::host_span const> row_group_indices, - parquet_reader_options const& options) const; + [[nodiscard]] std::vector bloom_filters_byte_ranges( + cudf::host_span const> row_group_indices, + parquet_reader_options const& options) const; + + /** + * @brief Filter the row groups using column chunk bloom filters + * + * @note The `bloom_filter_data` device spans must point to 32-byte aligned addresses + * + * @param bloom_filter_data Device spans of bloom filters, one per input column chunk + * @param row_group_indices Input row group indices + * @param options Parquet reader options + * @param stream CUDA stream used for device memory operations and kernel launches + * @return Filtered per-source row group indices (one inner vector per source) + */ + [[nodiscard]] std::vector> filter_row_groups_with_bloom_filters( + cudf::host_span const> bloom_filter_data, + cudf::host_span const> row_group_indices, + parquet_reader_options const& options, + rmm::cuda_stream_view stream) const; private: std::unique_ptr _impl; diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp index 0f0fd7de6d96..9d217e308a8b 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp @@ -297,6 +297,22 @@ hybrid_scan_reader_impl::secondary_filters_byte_ranges( return {bloom_filter_bytes, dictionary_page_bytes}; } +std::vector hybrid_scan_reader_impl::bloom_filters_byte_ranges( + cudf::host_span const> row_group_indices, + parquet_reader_options const& options) +{ + CUDF_EXPECTS(not row_group_indices.empty(), "Empty input row group indices encountered"); + auto [expr_conv, output_dtypes] = prepare_filter_and_output_types(options); + + auto const bloom_filter_bytes = + _extended_metadata->get_bloom_filter_bytes(row_group_indices, + output_dtypes, + _output_column_schemas, + expr_conv.get_converted_expr().value()); + + return bloom_filter_bytes; +} + std::vector> hybrid_scan_reader_impl::filter_row_groups_with_dictionary_pages( cudf::host_span const> dictionary_page_data, diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp b/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp index d11ae1e8ddb9..a7b11e6405da 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp @@ -112,6 +112,13 @@ class hybrid_scan_reader_impl : public parquet::detail::reader_impl { secondary_filters_byte_ranges(cudf::host_span const> row_group_indices, parquet_reader_options const& options); + /** + * @copydoc cudf::io::experimental::hybrid_scan_multifile::bloom_filters_byte_ranges + */ + [[nodiscard]] std::vector bloom_filters_byte_ranges( + cudf::host_span const> row_group_indices, + parquet_reader_options const& options); + /** * @copydoc cudf::io::experimental::hybrid_scan::filter_row_groups_with_dictionary_pages */ diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp index 59347367d223..aeb911532660 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp @@ -74,13 +74,23 @@ std::vector> hybrid_scan_multifile::filter_row_groups_wit return _impl->filter_row_groups_with_stats(row_group_indices, options, stream); } -std::pair, std::vector> -hybrid_scan_multifile::secondary_filters_byte_ranges( +std::vector hybrid_scan_multifile::bloom_filters_byte_ranges( cudf::host_span const> row_group_indices, parquet_reader_options const& options) const { CUDF_FUNC_RANGE(); - return _impl->secondary_filters_byte_ranges(row_group_indices, options); + return _impl->bloom_filters_byte_ranges(row_group_indices, options); +} + +std::vector> hybrid_scan_multifile::filter_row_groups_with_bloom_filters( + cudf::host_span const> bloom_filter_data, + cudf::host_span const> row_group_indices, + parquet_reader_options const& options, + rmm::cuda_stream_view stream) const +{ + CUDF_FUNC_RANGE(); + return _impl->filter_row_groups_with_bloom_filters( + bloom_filter_data, row_group_indices, options, stream); } } // namespace cudf::io::parquet::experimental diff --git a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp index 29c66093b1cf..476e27520dd1 100644 --- a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp +++ b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp @@ -314,3 +314,61 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithStats) EXPECT_TRUE(stats_filtered.front().empty()); EXPECT_TRUE(stats_filtered.back().empty()); } + +TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFilters) +{ + using T = uint32_t; + auto constexpr num_sources = 2; + auto const stream = cudf::get_default_stream(); + + // Two sources, each with 4 row groups + std::vector> file_buffers; + file_buffers.reserve(num_sources); + srand(0xb100); + file_buffers.emplace_back(std::get<1>(create_parquet_with_stats())); + srand(0x600d); + file_buffers.emplace_back(std::get<1>(create_parquet_with_stats())); + + auto inputs = build_multifile_inputs(file_buffers); + + // An equality predicate makes col0 eligible for bloom filtering. cuDF's Parquet writer does not + // emit bloom filters, so the per-source bloom byte ranges come back empty (same as single-file). + { + auto literal_value = cudf::numeric_scalar(T{42}, true, stream); + auto literal = cudf::ast::literal(literal_value); + auto col_ref = cudf::ast::column_name_reference("col0"); + auto filter = cudf::ast::operation(cudf::ast::ast_operator::EQUAL, col_ref, literal); + + auto options = cudf::io::parquet_reader_options::builder().filter(filter).build(); + auto const reader = std::make_unique( + inputs.footer_byte_spans, options); + + auto const input_row_group_indices = reader->all_row_groups(options); + ASSERT_EQ(input_row_group_indices.size(), num_sources); + + auto const bloom_byte_ranges = + reader->bloom_filters_byte_ranges(input_row_group_indices, options); + EXPECT_TRUE(bloom_byte_ranges.empty()); + } + + // Without any bloom-eligible (equality) predicate, bloom filtering is a no-op: the reader returns + // the input row groups unchanged, one inner vector per source. Validates the multifile bloom + // filter API delegation and per-source output shape. + { + auto literal_value = cudf::numeric_scalar(T{50}, true, stream); + auto literal = cudf::ast::literal(literal_value); + auto col_ref = cudf::ast::column_name_reference("col0"); + auto filter = cudf::ast::operation(cudf::ast::ast_operator::LESS, col_ref, literal); + + auto options = cudf::io::parquet_reader_options::builder().filter(filter).build(); + auto const reader = std::make_unique( + inputs.footer_byte_spans, options); + + auto const input_row_group_indices = reader->all_row_groups(options); + ASSERT_EQ(input_row_group_indices.size(), num_sources); + + auto const bloom_filtered = + reader->filter_row_groups_with_bloom_filters({}, input_row_group_indices, options, stream); + EXPECT_EQ(bloom_filtered, input_row_group_indices); + } +} From 815ca02f06d55d2976b295fae7d334a7a6bde1fe Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Sun, 14 Jun 2026 10:37:07 +0200 Subject: [PATCH 02/13] Enhance hybrid scan multifile API with improved bloom filter handling - Updated `bloom_filters_byte_ranges` to return a pair of byte ranges and a source-index map for better tracking of data origins. - Modified `filter_row_groups_with_bloom_filters` to accept a vector of device spans for bloom filter data, allowing for more flexible input. - Adjusted related methods and implementations in `hybrid_scan_helpers` and `hybrid_scan_impl` to accommodate the new return types and parameters. - Added tests to validate the new functionality and ensure correctness in handling bloom filters across multiple sources. These changes improve the API's usability and performance when working with bloom filters in hybrid scan operations. --- .../io/experimental/hybrid_scan_multifile.hpp | 15 +- .../experimental/hybrid_scan_helpers.cpp | 10 +- .../experimental/hybrid_scan_helpers.hpp | 15 +- .../parquet/experimental/hybrid_scan_impl.cpp | 27 +-- .../parquet/experimental/hybrid_scan_impl.hpp | 6 +- .../experimental/hybrid_scan_multifile.cpp | 25 +- .../hybrid_scan_multifile_filters_test.cpp | 217 +++++++++++++++++- 7 files changed, 273 insertions(+), 42 deletions(-) diff --git a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp index 74982878b521..47b82def8b17 100644 --- a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp +++ b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp @@ -17,6 +17,7 @@ #include #include +#include #include namespace cudf::io::parquet::experimental::detail { @@ -159,25 +160,27 @@ class hybrid_scan_multifile { * * @param row_group_indices Input row group indices, one per source * @param options Parquet reader options - * @return Vector of byte ranges of column chunk bloom filters subject to filter predicate + * @return A pair of (1) a flat vector of byte ranges of column chunk bloom filters subject to the + * filter predicate and (2) a parallel source-index map identifying the source each byte + * range must be fetched from */ - [[nodiscard]] std::vector bloom_filters_byte_ranges( - cudf::host_span const> row_group_indices, - parquet_reader_options const& options) const; + [[nodiscard]] std::pair, std::vector> + bloom_filters_byte_ranges(cudf::host_span const> row_group_indices, + parquet_reader_options const& options) const; /** * @brief Filter the row groups using column chunk bloom filters * * @note The `bloom_filter_data` device spans must point to 32-byte aligned addresses * - * @param bloom_filter_data Device spans of bloom filters, one per input column chunk + * @param bloom_filter_data Device spans of bloom filters, one inner vector per source * @param row_group_indices Input row group indices * @param options Parquet reader options * @param stream CUDA stream used for device memory operations and kernel launches * @return Filtered per-source row group indices (one inner vector per source) */ [[nodiscard]] std::vector> filter_row_groups_with_bloom_filters( - cudf::host_span const> bloom_filter_data, + cudf::host_span> const> bloom_filter_data, cudf::host_span const> row_group_indices, parquet_reader_options const& options, rmm::cuda_stream_view stream) const; diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp index e61b4ad09bcb..cf994f7cd72a 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp @@ -377,7 +377,8 @@ std::vector> aggregate_reader_metadata::filter_row_ return stats_filtered_row_group_indices.value_or(all_row_group_indices(row_group_indices)); } -std::vector aggregate_reader_metadata::get_bloom_filter_bytes( +std::pair, std::vector> +aggregate_reader_metadata::get_bloom_filter_bytes( cudf::host_span const> row_group_indices, host_span output_dtypes, host_span output_column_schemas, @@ -411,6 +412,10 @@ std::vector aggregate_reader_metadata::get_bloom_filter_bytes( std::vector bloom_filter_bytes; bloom_filter_bytes.reserve(num_chunks); + // Parallel map identifying the source each emitted byte range must be fetched from + std::vector chunk_source_map; + chunk_source_map.reserve(num_chunks); + // Flag to check if we have at least one valid bloom filter offset auto have_bloom_filters = false; @@ -431,6 +436,7 @@ std::vector aggregate_reader_metadata::get_bloom_filter_bytes( // Get bloom filter offsets and sizes bloom_filter_bytes.emplace_back(col_meta.bloom_filter_offset.value_or(0), col_meta.bloom_filter_length.value_or(0)); + chunk_source_map.emplace_back(static_cast(src_index)); // Set `have_bloom_filters` if `bloom_filter_offset` is valid if (col_meta.bloom_filter_offset.has_value()) { have_bloom_filters = true; } @@ -440,7 +446,7 @@ std::vector aggregate_reader_metadata::get_bloom_filter_bytes( if (not have_bloom_filters) { return {}; } - return bloom_filter_bytes; + return {std::move(bloom_filter_bytes), std::move(chunk_source_map)}; } std::vector aggregate_reader_metadata::get_dictionary_page_bytes( diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp index e65db678c2d1..d4552aa4b06e 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp @@ -222,13 +222,16 @@ class aggregate_reader_metadata : public aggregate_reader_metadata_base { * @param output_column_schemas schema indices of output columns * @param filter AST expression to filter row groups based on bloom filters * - * @return Byte ranges of bloom filters, one per column chunk with equality predicate + * @return A pair of (1) byte ranges of bloom filters, one per column chunk with equality + * predicate, and (2) a parallel source-index map identifying the source each byte range + * must be fetched from */ - [[nodiscard]] std::vector get_bloom_filter_bytes( - cudf::host_span const> row_group_indices, - cudf::host_span output_dtypes, - cudf::host_span output_column_schemas, - std::reference_wrapper filter); + [[nodiscard]] std::pair, + std::vector> + get_bloom_filter_bytes(cudf::host_span const> row_group_indices, + cudf::host_span output_dtypes, + cudf::host_span output_column_schemas, + std::reference_wrapper filter); /** * @brief Get the dictionary page byte ranges, one per column chunk with (in)equality predicate diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp index 9d217e308a8b..4b26b1367f02 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp @@ -283,11 +283,14 @@ hybrid_scan_reader_impl::secondary_filters_byte_ranges( CUDF_EXPECTS(not row_group_indices.empty(), "Empty input row group indices encountered"); auto [expr_conv, output_dtypes] = prepare_filter_and_output_types(options); - auto const bloom_filter_bytes = - _extended_metadata->get_bloom_filter_bytes(row_group_indices, - output_dtypes, - _output_column_schemas, - expr_conv.get_converted_expr().value()); + // Single-file combined view: take only the bloom byte ranges (`.first`). The per-range source + // map (`.second`) is unused here because the single-file reader has exactly one source. + auto const bloom_filter_bytes = _extended_metadata + ->get_bloom_filter_bytes(row_group_indices, + output_dtypes, + _output_column_schemas, + expr_conv.get_converted_expr().value()) + .first; auto const dictionary_page_bytes = _extended_metadata->get_dictionary_page_bytes(row_group_indices, output_dtypes, @@ -297,20 +300,18 @@ hybrid_scan_reader_impl::secondary_filters_byte_ranges( return {bloom_filter_bytes, dictionary_page_bytes}; } -std::vector hybrid_scan_reader_impl::bloom_filters_byte_ranges( +std::pair, std::vector> +hybrid_scan_reader_impl::bloom_filters_byte_ranges( cudf::host_span const> row_group_indices, parquet_reader_options const& options) { CUDF_EXPECTS(not row_group_indices.empty(), "Empty input row group indices encountered"); auto [expr_conv, output_dtypes] = prepare_filter_and_output_types(options); - auto const bloom_filter_bytes = - _extended_metadata->get_bloom_filter_bytes(row_group_indices, - output_dtypes, - _output_column_schemas, - expr_conv.get_converted_expr().value()); - - return bloom_filter_bytes; + return _extended_metadata->get_bloom_filter_bytes(row_group_indices, + output_dtypes, + _output_column_schemas, + expr_conv.get_converted_expr().value()); } std::vector> diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp b/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp index a7b11e6405da..c8a8bc2f8ac5 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp @@ -115,9 +115,9 @@ class hybrid_scan_reader_impl : public parquet::detail::reader_impl { /** * @copydoc cudf::io::experimental::hybrid_scan_multifile::bloom_filters_byte_ranges */ - [[nodiscard]] std::vector bloom_filters_byte_ranges( - cudf::host_span const> row_group_indices, - parquet_reader_options const& options); + [[nodiscard]] std::pair, std::vector> + bloom_filters_byte_ranges(cudf::host_span const> row_group_indices, + parquet_reader_options const& options); /** * @copydoc cudf::io::experimental::hybrid_scan::filter_row_groups_with_dictionary_pages diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp index aeb911532660..1def3bbeb2f6 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp @@ -8,6 +8,8 @@ #include #include +#include + namespace cudf::io::parquet::experimental { hybrid_scan_multifile::hybrid_scan_multifile( @@ -74,7 +76,8 @@ std::vector> hybrid_scan_multifile::filter_row_groups_wit return _impl->filter_row_groups_with_stats(row_group_indices, options, stream); } -std::vector hybrid_scan_multifile::bloom_filters_byte_ranges( +std::pair, std::vector> +hybrid_scan_multifile::bloom_filters_byte_ranges( cudf::host_span const> row_group_indices, parquet_reader_options const& options) const { @@ -83,14 +86,30 @@ std::vector hybrid_scan_multifile::bloom_filters_byte_ran } std::vector> hybrid_scan_multifile::filter_row_groups_with_bloom_filters( - cudf::host_span const> bloom_filter_data, + cudf::host_span> const> bloom_filter_data, cudf::host_span const> row_group_indices, parquet_reader_options const& options, rmm::cuda_stream_view stream) const { CUDF_FUNC_RANGE(); + + // Concatenate the per-source spans in source order + auto const num_chunks = + std::accumulate(bloom_filter_data.begin(), + bloom_filter_data.end(), + std::size_t{0}, + [](auto sum, auto const& source_data) { return sum + source_data.size(); }); + + std::vector> flattened_bloom_filter_data; + flattened_bloom_filter_data.reserve(num_chunks); + for (auto const& source_bloom_filter_data : bloom_filter_data) { + flattened_bloom_filter_data.insert(flattened_bloom_filter_data.end(), + source_bloom_filter_data.begin(), + source_bloom_filter_data.end()); + } + return _impl->filter_row_groups_with_bloom_filters( - bloom_filter_data, row_group_indices, options, stream); + flattened_bloom_filter_data, row_group_indices, options, stream); } } // namespace cudf::io::parquet::experimental diff --git a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp index 476e27520dd1..76351b98419d 100644 --- a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp +++ b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp @@ -17,8 +17,16 @@ #include #include +#include +#include + +#include +#include #include +#include #include +#include +#include #include namespace { @@ -74,6 +82,9 @@ std::vector create_empty_parquet_with_stats() return buffer; } +//! 32-byte alignment required for bloom filter device buffers +auto constexpr bloom_filter_alignment = rmm::CUDA_ALLOCATION_ALIGNMENT; + } // namespace struct HybridScanMultifileFiltersTest : public cudf::test::BaseFixture {}; @@ -318,16 +329,16 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithStats) TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFilters) { using T = uint32_t; - auto constexpr num_sources = 2; + auto constexpr num_sources = 32; auto const stream = cudf::get_default_stream(); - // Two sources, each with 4 row groups + // num_sources sources, each with the same schema std::vector> file_buffers; file_buffers.reserve(num_sources); - srand(0xb100); - file_buffers.emplace_back(std::get<1>(create_parquet_with_stats())); - srand(0x600d); - file_buffers.emplace_back(std::get<1>(create_parquet_with_stats())); + for (int i = 0; i < num_sources; ++i) { + srand(0xb100 + i); + file_buffers.emplace_back(std::get<1>(create_parquet_with_stats())); + } auto inputs = build_multifile_inputs(file_buffers); @@ -346,9 +357,12 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFilters) auto const input_row_group_indices = reader->all_row_groups(options); ASSERT_EQ(input_row_group_indices.size(), num_sources); - auto const bloom_byte_ranges = + auto const [bloom_byte_ranges, bloom_source_map] = reader->bloom_filters_byte_ranges(input_row_group_indices, options); + // cuDF's Parquet writer does not emit bloom filters, so the ranges come back empty. The source + // map is parallel to the ranges and must always match them in length (here, both empty). EXPECT_TRUE(bloom_byte_ranges.empty()); + EXPECT_EQ(bloom_byte_ranges.size(), bloom_source_map.size()); } // Without any bloom-eligible (equality) predicate, bloom filtering is a no-op: the reader returns @@ -367,8 +381,193 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFilters) auto const input_row_group_indices = reader->all_row_groups(options); ASSERT_EQ(input_row_group_indices.size(), num_sources); - auto const bloom_filtered = - reader->filter_row_groups_with_bloom_filters({}, input_row_group_indices, options, stream); + auto const empty_bloom_data = + std::vector>>(num_sources); + auto const bloom_filtered = reader->filter_row_groups_with_bloom_filters( + empty_bloom_data, input_row_group_indices, options, stream); EXPECT_EQ(bloom_filtered, input_row_group_indices); } } + +TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFiltersRealData) +{ + auto constexpr num_sources = 32; + auto const stream = cudf::get_default_stream(); + auto aligned_mr = rmm::mr::aligned_resource_adaptor(cudf::get_current_device_resource_ref(), + bloom_filter_alignment); + + // Embedded copy of cuDF's committed bloom-filter fixture (cuDF cannot write bloom filters, and + // cuDF tests avoid committed data files). Source: + // python/cudf/cudf/tests/data/parquet/bloom_filter_alignment.parquet (DuckDB-written; bloom + // filter on the r_reason_desc column). Regenerate with: xxd -i bloom_filter_alignment.parquet + constexpr std::array bloom_filter_alignment_parquet{ + 0x50, 0x41, 0x52, 0x31, 0x15, 0x04, 0x15, 0x98, 0x02, 0x15, 0xa0, 0x02, 0x4c, 0x15, 0x46, 0x15, + 0x00, 0x00, 0x00, 0x8c, 0x01, 0xf0, 0x8b, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, + 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, + 0x00, 0x00, 0x00, 0x15, 0x00, 0x15, 0x90, 0x03, 0x15, 0x62, 0x2c, 0x15, 0x46, 0x15, 0x10, 0x15, + 0x06, 0x15, 0x06, 0x00, 0x00, 0xc8, 0x01, 0x90, 0x02, 0x00, 0x00, 0x00, 0x46, 0x01, 0x06, 0x41, + 0x40, 0x20, 0x0c, 0x44, 0x61, 0x1c, 0x48, 0xa2, 0x2c, 0x4c, 0xe3, 0x3c, 0x50, 0x24, 0x4d, 0x54, + 0x65, 0x5d, 0x58, 0xa6, 0x6d, 0x5c, 0xe7, 0x7d, 0x60, 0x28, 0x02, 0x00, 0x00, 0xfe, 0x02, 0x00, + 0xfe, 0x02, 0x00, 0x8a, 0x02, 0x00, 0x15, 0x04, 0x15, 0xf8, 0x0a, 0x15, 0x86, 0x03, 0x4c, 0x15, + 0x46, 0x15, 0x00, 0x00, 0x00, 0xbc, 0x05, 0x10, 0x10, 0x00, 0x00, 0x00, 0x41, 0x0d, 0x01, 0x00, + 0x42, 0x0d, 0x08, 0x2e, 0x14, 0x00, 0x00, 0x43, 0x4a, 0x14, 0x00, 0x00, 0x44, 0x4a, 0x14, 0x00, + 0x00, 0x45, 0x4a, 0x14, 0x00, 0x00, 0x46, 0x4a, 0x14, 0x00, 0x00, 0x47, 0x4a, 0x14, 0x00, 0x00, + 0x48, 0x4a, 0x14, 0x00, 0x00, 0x49, 0x4a, 0x14, 0x00, 0x00, 0x4a, 0x4a, 0x14, 0x00, 0x00, 0x4b, + 0x4a, 0x14, 0x00, 0x00, 0x4c, 0x4a, 0x14, 0x00, 0x00, 0x4d, 0x4a, 0x14, 0x00, 0x00, 0x4e, 0x4a, + 0x14, 0x00, 0x00, 0x4f, 0x4a, 0x14, 0x00, 0x00, 0x50, 0x4a, 0x14, 0x00, 0x31, 0x2d, 0x2e, 0x2c, + 0x01, 0x00, 0x42, 0x2d, 0x41, 0x2e, 0x14, 0x00, 0x00, 0x43, 0x4a, 0x14, 0x00, 0x00, 0x44, 0x4a, + 0x14, 0x00, 0x00, 0x45, 0x4a, 0x14, 0x00, 0x00, 0x46, 0x4a, 0x14, 0x00, 0x00, 0x47, 0x4a, 0x14, + 0x00, 0x00, 0x48, 0x4a, 0x14, 0x00, 0x00, 0x49, 0x4a, 0x14, 0x00, 0x00, 0x4a, 0x4a, 0x14, 0x00, + 0x00, 0x4b, 0x4a, 0x14, 0x00, 0x00, 0x4c, 0x4a, 0x14, 0x00, 0x00, 0x4d, 0x4a, 0x14, 0x00, 0x00, + 0x4e, 0x4a, 0x14, 0x00, 0x00, 0x4f, 0x4a, 0x14, 0x00, 0x00, 0x50, 0x4a, 0x14, 0x00, 0x51, 0x59, + 0x2e, 0x2c, 0x01, 0x00, 0x42, 0x4d, 0x6d, 0x2e, 0x14, 0x00, 0x00, 0x43, 0x4a, 0x14, 0x00, 0x1c, + 0x44, 0x43, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x15, 0x00, 0x15, 0x90, 0x03, 0x15, 0x62, 0x2c, + 0x15, 0x46, 0x15, 0x10, 0x15, 0x06, 0x15, 0x06, 0x00, 0x00, 0xc8, 0x01, 0x90, 0x02, 0x00, 0x00, + 0x00, 0x46, 0x01, 0x06, 0x41, 0x40, 0x20, 0x0c, 0x44, 0x61, 0x1c, 0x48, 0xa2, 0x2c, 0x4c, 0xe3, + 0x3c, 0x50, 0x24, 0x4d, 0x54, 0x65, 0x5d, 0x58, 0xa6, 0x6d, 0x5c, 0xe7, 0x7d, 0x60, 0x28, 0x02, + 0x00, 0x00, 0xfe, 0x02, 0x00, 0xfe, 0x02, 0x00, 0x8a, 0x02, 0x00, 0x15, 0x04, 0x15, 0x82, 0x0b, + 0x15, 0xe0, 0x07, 0x4c, 0x15, 0x44, 0x15, 0x00, 0x00, 0x00, 0xc1, 0x05, 0xf0, 0x55, 0x13, 0x00, + 0x00, 0x00, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x64, 0x61, + 0x6d, 0x61, 0x67, 0x65, 0x64, 0x0f, 0x00, 0x00, 0x00, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, + 0x20, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x16, 0x00, 0x00, 0x00, 0x44, 0x69, 0x64, 0x20, + 0x6e, 0x6f, 0x74, 0x20, 0x67, 0x65, 0x74, 0x20, 0x69, 0x74, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x69, + 0x6d, 0x65, 0x1f, 0x00, 0x00, 0x00, 0x4e, 0x6f, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x01, 0x0c, 0x04, 0x61, 0x74, 0x05, 0x51, 0x18, 0x6f, 0x72, 0x64, 0x72, + 0x65, 0x64, 0x0d, 0x05, 0x67, 0x2c, 0x72, 0x74, 0x73, 0x20, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, + 0x67, 0x28, 0x01, 0x4e, 0x08, 0x6f, 0x65, 0x73, 0x05, 0x4f, 0x01, 0x62, 0x1c, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x61, 0x20, 0x32, 0x41, 0x00, 0x14, 0x49, 0x20, 0x68, 0x61, 0x76, 0x65, 0x01, + 0x3d, 0x34, 0x47, 0x69, 0x66, 0x74, 0x20, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x16, + 0x01, 0x3d, 0x0d, 0x8b, 0x34, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6c, 0x6f, 0x72, 0x52, 0x1a, 0x00, 0x14, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x15, 0x4e, 0x34, 0x00, + 0x10, 0x6d, 0x61, 0x6b, 0x65, 0x19, 0x4e, 0x19, 0x00, 0xf0, 0x3e, 0x77, 0x61, 0x72, 0x72, 0x61, + 0x6e, 0x74, 0x79, 0x1e, 0x00, 0x00, 0x00, 0x4e, 0x6f, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x6d, 0x79, + 0x20, 0x61, 0x72, 0x65, 0x61, 0x1f, 0x00, 0x00, 0x00, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x61, + 0x20, 0x62, 0x65, 0x74, 0x74, 0x65, 0x72, 0x20, 0x70, 0x72, 0x01, 0x2c, 0x28, 0x69, 0x6e, 0x20, + 0x61, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2b, 0x46, 0x23, 0x00, 0x14, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x64, 0x21, 0x5d, 0x0d, 0x69, 0x1d, 0x2f, 0x00, 0x14, 0x05, 0x74, 0x04, 0x74, 0x20, 0x21, + 0x16, 0x30, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x0b, 0x1d, + 0xa9, 0x3c, 0x66, 0x69, 0x74, 0x0a, 0x00, 0x00, 0x00, 0x57, 0x72, 0x6f, 0x6e, 0x67, 0x20, 0x73, + 0x69, 0x7a, 0x05, 0x1d, 0x28, 0x4c, 0x6f, 0x73, 0x74, 0x20, 0x6d, 0x79, 0x20, 0x6a, 0x6f, 0x62, + 0x01, 0x44, 0x80, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x70, + 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x12, 0x00, 0x00, 0x00, 0x64, 0x75, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x15, 0x16, 0x24, 0x0c, 0x00, 0x00, 0x00, 0x69, 0x74, 0x73, 0x20, 0x69, + 0x73, 0x01, 0xc5, 0x04, 0x6f, 0x79, 0x09, 0x10, 0x09, 0x0f, 0x40, 0x67, 0x69, 0x72, 0x6c, 0x09, + 0x00, 0x00, 0x00, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x20, 0x32, 0x33, 0x2e, 0x0d, 0x00, 0x00, + 0x34, 0x2e, 0x0d, 0x00, 0x00, 0x35, 0x2e, 0x0d, 0x00, 0x00, 0x36, 0x2e, 0x0d, 0x00, 0x00, 0x37, + 0x2e, 0x0d, 0x00, 0x00, 0x38, 0x2e, 0x0d, 0x00, 0x00, 0x39, 0x1d, 0x0d, 0x04, 0x33, 0x31, 0x2e, + 0x0d, 0x00, 0x00, 0x32, 0x2e, 0x0d, 0x00, 0x2e, 0x75, 0x00, 0x38, 0x33, 0x34, 0x09, 0x00, 0x00, + 0x00, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x20, 0x33, 0x35, 0x15, 0x00, 0x15, 0x90, 0x03, 0x15, + 0x62, 0x2c, 0x15, 0x46, 0x15, 0x10, 0x15, 0x06, 0x15, 0x06, 0x00, 0x00, 0xc8, 0x01, 0x90, 0x02, + 0x00, 0x00, 0x00, 0x46, 0x01, 0x06, 0x41, 0x40, 0x20, 0x0c, 0x44, 0x61, 0x1c, 0x48, 0xa2, 0x2c, + 0x4c, 0xe3, 0x3c, 0x50, 0x24, 0x4d, 0x54, 0x65, 0x5d, 0x58, 0xa6, 0x6d, 0x5c, 0xd7, 0x79, 0x1f, + 0x18, 0x02, 0x00, 0x00, 0xfe, 0x02, 0x00, 0xfe, 0x02, 0x00, 0x8a, 0x02, 0x00, 0x15, 0x80, 0x01, + 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x25, 0xce, + 0x22, 0x55, 0x71, 0x2a, 0xda, 0x42, 0x80, 0xf4, 0xf3, 0xe7, 0x0e, 0x91, 0x23, 0x38, 0x21, 0x89, + 0x8d, 0xb0, 0x63, 0x93, 0xe8, 0x01, 0xfd, 0x58, 0x11, 0xda, 0x28, 0x63, 0x87, 0x3f, 0x40, 0x7c, + 0x32, 0x1c, 0x08, 0xa8, 0x2f, 0xe2, 0xd8, 0xa3, 0x80, 0x2e, 0x4e, 0xa8, 0x4a, 0x86, 0x16, 0x24, + 0xce, 0xad, 0xea, 0x68, 0x00, 0x20, 0x36, 0xe6, 0xa2, 0x10, 0x99, 0x80, 0x6d, 0x15, 0x80, 0x01, + 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x4e, 0xf3, 0x6e, + 0x00, 0x4b, 0x4f, 0x44, 0x74, 0x0e, 0x73, 0xa0, 0x90, 0xe3, 0xbd, 0xe2, 0x0a, 0x32, 0x87, 0x50, + 0xc5, 0x41, 0x2a, 0x2c, 0xee, 0x3a, 0x12, 0x58, 0xa3, 0x0d, 0x05, 0xe5, 0x89, 0x66, 0x0f, 0xa3, + 0xc0, 0xb8, 0xa2, 0xe6, 0xc1, 0x4f, 0x00, 0xf6, 0x8c, 0x9a, 0xa2, 0xf0, 0x17, 0xc4, 0x29, 0x1f, + 0x06, 0x89, 0xbf, 0x13, 0x88, 0x58, 0x84, 0xc7, 0x38, 0xf9, 0x18, 0x01, 0x78, 0x15, 0x80, 0x01, + 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x50, 0x35, 0x06, + 0xbd, 0xf8, 0xeb, 0x00, 0x26, 0xc1, 0x0a, 0x5a, 0xd2, 0x4e, 0x69, 0x47, 0x82, 0xf4, 0x61, 0x61, + 0x68, 0x11, 0x32, 0x90, 0xe6, 0xa4, 0x8e, 0xa8, 0x44, 0x41, 0x53, 0x08, 0xd5, 0xea, 0x24, 0xfd, + 0x91, 0xae, 0x84, 0x81, 0xb8, 0xa6, 0x9a, 0x41, 0x75, 0xdc, 0x2b, 0x5c, 0x92, 0x06, 0xf0, 0x87, + 0xea, 0x38, 0x38, 0x52, 0x26, 0x08, 0x11, 0x7d, 0x6d, 0x28, 0xfc, 0x60, 0x89, 0x15, 0x02, 0x19, + 0x4c, 0x35, 0x00, 0x18, 0x0d, 0x64, 0x75, 0x63, 0x6b, 0x64, 0x62, 0x5f, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x15, 0x06, 0x00, 0x15, 0x02, 0x25, 0x02, 0x18, 0x0b, 0x72, 0x5f, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x6b, 0x25, 0x22, 0x00, 0x15, 0x0c, 0x25, 0x02, 0x18, 0x0b, 0x72, + 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x25, 0x00, 0x00, 0x15, 0x0c, 0x25, + 0x02, 0x18, 0x0d, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x73, 0x63, + 0x25, 0x00, 0x00, 0x16, 0x46, 0x19, 0x1c, 0x19, 0x3c, 0x26, 0x00, 0x1c, 0x15, 0x02, 0x19, 0x15, + 0x10, 0x19, 0x18, 0x0b, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x6b, 0x15, + 0x02, 0x16, 0x46, 0x16, 0xea, 0x05, 0x16, 0xc4, 0x03, 0x26, 0xc6, 0x02, 0x26, 0x08, 0x1c, 0x18, + 0x04, 0x23, 0x00, 0x00, 0x00, 0x18, 0x04, 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x16, 0x46, 0x18, + 0x04, 0x23, 0x00, 0x00, 0x00, 0x18, 0x04, 0x01, 0x00, 0x00, 0x00, 0x11, 0x11, 0x00, 0x26, 0xfa, + 0x10, 0x15, 0xa0, 0x01, 0x00, 0x00, 0x26, 0x00, 0x1c, 0x15, 0x0c, 0x19, 0x15, 0x10, 0x19, 0x18, + 0x0b, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x15, 0x02, 0x16, 0x46, + 0x16, 0xca, 0x0e, 0x16, 0xaa, 0x04, 0x26, 0xf0, 0x06, 0x26, 0xcc, 0x03, 0x1c, 0x18, 0x10, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x50, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x18, + 0x10, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x16, 0x00, 0x16, 0x46, 0x18, 0x10, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x50, + 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x18, 0x10, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x11, 0x11, 0x00, 0x26, 0x9a, 0x12, 0x15, + 0xa0, 0x01, 0x00, 0x00, 0x26, 0x00, 0x1c, 0x15, 0x0c, 0x19, 0x15, 0x10, 0x19, 0x18, 0x0d, 0x72, + 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x15, 0x02, 0x16, 0x46, + 0x16, 0xd4, 0x0e, 0x16, 0x84, 0x09, 0x26, 0xf4, 0x0f, 0x26, 0xf6, 0x07, 0x1c, 0x18, 0x14, 0x75, + 0x6e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x70, 0x75, 0x72, 0x63, 0x68, + 0x61, 0x73, 0x65, 0x18, 0x0b, 0x44, 0x69, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x66, 0x69, 0x74, + 0x16, 0x00, 0x16, 0x44, 0x18, 0x14, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x69, 0x7a, 0x65, + 0x64, 0x20, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x18, 0x0b, 0x44, 0x69, 0x64, 0x20, + 0x6e, 0x6f, 0x74, 0x20, 0x66, 0x69, 0x74, 0x11, 0x11, 0x00, 0x26, 0xba, 0x13, 0x15, 0xa0, 0x01, + 0x00, 0x00, 0x16, 0x88, 0x23, 0x16, 0x46, 0x26, 0x08, 0x00, 0x28, 0x28, 0x44, 0x75, 0x63, 0x6b, + 0x44, 0x42, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x76, 0x31, 0x2e, 0x33, 0x2e, + 0x30, 0x20, 0x28, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x37, 0x31, 0x63, 0x35, 0x63, 0x30, 0x37, + 0x63, 0x64, 0x64, 0x29, 0x00, 0xd8, 0x01, 0x00, 0x00, 0x50, 0x41, 0x52, 0x31, + }; + // Two sources backed by the embedded bloom-filter fixture + std::vector const fixture(bloom_filter_alignment_parquet.begin(), + bloom_filter_alignment_parquet.end()); + std::vector> file_buffers(num_sources, fixture); + + auto inputs = build_multifile_inputs(file_buffers); + + std::vector> datasource_refs; + datasource_refs.reserve(num_sources); + for (auto& ds : inputs.datasources) { + datasource_refs.emplace_back(*ds); + } + + // An equality predicate makes the "r_reason_desc" column bloom-eligible + auto literal_value = cudf::string_scalar("Did not like the color", true, stream); + auto literal = cudf::ast::literal(literal_value); + auto col_ref = cudf::ast::column_name_reference("r_reason_desc"); + auto filter = cudf::ast::operation(cudf::ast::ast_operator::EQUAL, col_ref, literal); + + auto options = cudf::io::parquet_reader_options::builder().filter(filter).build(); + auto const reader = std::make_unique( + cudf::host_span const>{inputs.footer_byte_spans}, options); + + auto const input_row_group_indices = reader->all_row_groups(options); + ASSERT_EQ(input_row_group_indices.size(), num_sources); + + auto const [bloom_byte_ranges, bloom_source_map] = + reader->bloom_filters_byte_ranges(input_row_group_indices, options); + ASSERT_EQ(bloom_byte_ranges.size(), static_cast(num_sources)); + ASSERT_EQ(bloom_byte_ranges.size(), bloom_source_map.size()); + std::vector expected_source_map(num_sources); + std::iota(expected_source_map.begin(), expected_source_map.end(), 0); + EXPECT_EQ(bloom_source_map, expected_source_map); + EXPECT_TRUE(std::none_of(bloom_byte_ranges.begin(), bloom_byte_ranges.end(), [](auto const& r) { + return r.is_empty(); + })); + + std::vector> ranges_per_source(num_sources); + for (size_t i = 0; i < bloom_byte_ranges.size(); ++i) { + ASSERT_LT(bloom_source_map[i], num_sources); + ranges_per_source[bloom_source_map[i]].push_back(bloom_byte_ranges[i]); + } + auto [bloom_buffers, bloom_data_per_source, bloom_tasks] = + cudf::io::parquet::fetch_byte_ranges_to_device_async( + datasource_refs, ranges_per_source, stream, aligned_mr); + bloom_tasks.get(); + for (auto const& per_source : bloom_data_per_source) { + ASSERT_EQ(per_source.size(), 1); + } + + auto const bloom_filtered = reader->filter_row_groups_with_bloom_filters( + bloom_data_per_source, input_row_group_indices, options, stream); + + // Shouldn't filter out any RG, since the queried value is present in every source. + EXPECT_EQ(bloom_filtered, input_row_group_indices); +} From 51e4c0e1878bc06cc5e5da686f95b5959020256d Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Sun, 14 Jun 2026 11:34:55 +0200 Subject: [PATCH 03/13] Update comments in hybrid scan multifile filters test for clarity - Revised comment in `FilterRowGroupsWithBloomFiltersRealData` test to enhance clarity regarding the use of the embedded bloom-filter fixture. - This change improves the readability of the test code, making it easier to understand the context of the test setup. --- .../io/experimental/hybrid_scan_multifile_filters_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp index 76351b98419d..5dfd39117cce 100644 --- a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp +++ b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp @@ -515,7 +515,7 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFiltersRealData) 0x30, 0x20, 0x28, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x37, 0x31, 0x63, 0x35, 0x63, 0x30, 0x37, 0x63, 0x64, 0x64, 0x29, 0x00, 0xd8, 0x01, 0x00, 0x00, 0x50, 0x41, 0x52, 0x31, }; - // Two sources backed by the embedded bloom-filter fixture + // Sources backed by the embedded bloom-filter fixture std::vector const fixture(bloom_filter_alignment_parquet.begin(), bloom_filter_alignment_parquet.end()); std::vector> file_buffers(num_sources, fixture); From b8baad740f778140709f5aaa4368026cb4683a8f Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Mon, 15 Jun 2026 23:04:17 +0200 Subject: [PATCH 04/13] Refactor bloom filter functions in hybrid scan implementation - Renamed `get_bloom_filter_bytes` to `bloom_filters_byte_ranges` for clarity and consistency. - Updated return types and parameter descriptions in the relevant header files to reflect the new function name. - Simplified the handling of bloom filter data by removing unnecessary flattening logic in `filter_row_groups_with_bloom_filters`. - Adjusted tests to accommodate the new function name and ensure proper functionality. This refactor enhances code readability and maintains consistency across the hybrid scan implementation. --- .../io/experimental/hybrid_scan_multifile.hpp | 11 ++-- .../experimental/hybrid_scan_helpers.cpp | 55 ++++++++++--------- .../experimental/hybrid_scan_helpers.hpp | 12 ++-- .../parquet/experimental/hybrid_scan_impl.cpp | 24 ++++---- .../experimental/hybrid_scan_multifile.cpp | 22 +------- .../hybrid_scan_multifile_filters_test.cpp | 27 ++++----- 6 files changed, 63 insertions(+), 88 deletions(-) diff --git a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp index d40abd957e23..5cfc56526fe9 100644 --- a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp +++ b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp @@ -160,9 +160,7 @@ class hybrid_scan_multifile { * * @param row_group_indices Input row group indices, one per source * @param options Parquet reader options - * @return A pair of (1) a flat vector of byte ranges of column chunk bloom filters subject to the - * filter predicate and (2) a parallel source-index map identifying the source each byte - * range must be fetched from + * @return A pair of vectors containing bloom filter byte ranges and corresponding source indices */ [[nodiscard]] std::pair, std::vector> bloom_filters_byte_ranges(cudf::host_span const> row_group_indices, @@ -173,14 +171,15 @@ class hybrid_scan_multifile { * * @note The `bloom_filter_data` device spans must point to 32-byte aligned addresses * - * @param bloom_filter_data Device spans of bloom filters, one inner vector per source + * @param bloom_filter_data Flattened device spans of bloom filters returned in the same order as + * `bloom_filters_byte_ranges` * @param row_group_indices Input row group indices * @param options Parquet reader options * @param stream CUDA stream used for device memory operations and kernel launches - * @return Filtered per-source row group indices (one inner vector per source) + * @return Vectors of filtered per-source row group indices, one per source */ [[nodiscard]] std::vector> filter_row_groups_with_bloom_filters( - cudf::host_span> const> bloom_filter_data, + cudf::host_span const> bloom_filter_data, cudf::host_span const> row_group_indices, parquet_reader_options const& options, rmm::cuda_stream_view stream) const; diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp index cf994f7cd72a..7077e311e743 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp @@ -378,7 +378,7 @@ std::vector> aggregate_reader_metadata::filter_row_ } std::pair, std::vector> -aggregate_reader_metadata::get_bloom_filter_bytes( +aggregate_reader_metadata::bloom_filters_byte_ranges( cudf::host_span const> row_group_indices, host_span output_dtypes, host_span output_column_schemas, @@ -413,40 +413,41 @@ aggregate_reader_metadata::get_bloom_filter_bytes( bloom_filter_bytes.reserve(num_chunks); // Parallel map identifying the source each emitted byte range must be fetched from - std::vector chunk_source_map; - chunk_source_map.reserve(num_chunks); + std::vector bloom_filter_source_map; + bloom_filter_source_map.reserve(num_chunks); // Flag to check if we have at least one valid bloom filter offset auto have_bloom_filters = false; // For all sources - std::for_each(cuda::counting_iterator{0}, - cuda::counting_iterator{row_group_indices.size()}, - [&](auto const src_index) { - // Get all row group indices in the data source - auto const& rg_indices = row_group_indices[src_index]; - // For all row groups - std::for_each(rg_indices.cbegin(), rg_indices.cend(), [&](auto const rg_index) { - // For all column chunks - std::for_each( - bloom_filter_col_schemas.begin(), - bloom_filter_col_schemas.end(), - [&](auto const schema_idx) { - auto& col_meta = get_column_metadata(rg_index, src_index, schema_idx); - // Get bloom filter offsets and sizes - bloom_filter_bytes.emplace_back(col_meta.bloom_filter_offset.value_or(0), - col_meta.bloom_filter_length.value_or(0)); - chunk_source_map.emplace_back(static_cast(src_index)); - - // Set `have_bloom_filters` if `bloom_filter_offset` is valid - if (col_meta.bloom_filter_offset.has_value()) { have_bloom_filters = true; } - }); - }); - }); + std::for_each( + cuda::counting_iterator{0}, + cuda::counting_iterator{row_group_indices.size()}, + [&](auto const src_index) { + // Get all row group indices in the data source + auto const& rg_indices = row_group_indices[src_index]; + // For all row groups + std::for_each(rg_indices.cbegin(), rg_indices.cend(), [&](auto const rg_index) { + // For all column chunks + std::for_each( + bloom_filter_col_schemas.begin(), + bloom_filter_col_schemas.end(), + [&](auto const schema_idx) { + auto& col_meta = get_column_metadata(rg_index, src_index, schema_idx); + // Get bloom filter offsets and sizes + bloom_filter_bytes.emplace_back(col_meta.bloom_filter_offset.value_or(0), + col_meta.bloom_filter_length.value_or(0)); + bloom_filter_source_map.emplace_back(static_cast(src_index)); + + // Set `have_bloom_filters` if `bloom_filter_offset` is valid + if (col_meta.bloom_filter_offset.has_value()) { have_bloom_filters = true; } + }); + }); + }); if (not have_bloom_filters) { return {}; } - return {std::move(bloom_filter_bytes), std::move(chunk_source_map)}; + return {std::move(bloom_filter_bytes), std::move(bloom_filter_source_map)}; } std::vector aggregate_reader_metadata::get_dictionary_page_bytes( diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp index d4552aa4b06e..fe35f7c1e396 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp @@ -222,16 +222,14 @@ class aggregate_reader_metadata : public aggregate_reader_metadata_base { * @param output_column_schemas schema indices of output columns * @param filter AST expression to filter row groups based on bloom filters * - * @return A pair of (1) byte ranges of bloom filters, one per column chunk with equality - * predicate, and (2) a parallel source-index map identifying the source each byte range - * must be fetched from + * @return A pair of vectors containing bloom filter byte ranges and corresponding source indices */ [[nodiscard]] std::pair, std::vector> - get_bloom_filter_bytes(cudf::host_span const> row_group_indices, - cudf::host_span output_dtypes, - cudf::host_span output_column_schemas, - std::reference_wrapper filter); + bloom_filters_byte_ranges(cudf::host_span const> row_group_indices, + cudf::host_span output_dtypes, + cudf::host_span output_column_schemas, + std::reference_wrapper filter); /** * @brief Get the dictionary page byte ranges, one per column chunk with (in)equality predicate diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp index 4b26b1367f02..42f8d7f9df86 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp @@ -283,14 +283,14 @@ hybrid_scan_reader_impl::secondary_filters_byte_ranges( CUDF_EXPECTS(not row_group_indices.empty(), "Empty input row group indices encountered"); auto [expr_conv, output_dtypes] = prepare_filter_and_output_types(options); - // Single-file combined view: take only the bloom byte ranges (`.first`). The per-range source - // map (`.second`) is unused here because the single-file reader has exactly one source. - auto const bloom_filter_bytes = _extended_metadata - ->get_bloom_filter_bytes(row_group_indices, - output_dtypes, - _output_column_schemas, - expr_conv.get_converted_expr().value()) - .first; + // Single source: keep only the bloom filter byte ranges, not the source map + auto const bloom_filter_bytes = + _extended_metadata + ->bloom_filters_byte_ranges(row_group_indices, + output_dtypes, + _output_column_schemas, + expr_conv.get_converted_expr().value()) + .first; auto const dictionary_page_bytes = _extended_metadata->get_dictionary_page_bytes(row_group_indices, output_dtypes, @@ -308,10 +308,10 @@ hybrid_scan_reader_impl::bloom_filters_byte_ranges( CUDF_EXPECTS(not row_group_indices.empty(), "Empty input row group indices encountered"); auto [expr_conv, output_dtypes] = prepare_filter_and_output_types(options); - return _extended_metadata->get_bloom_filter_bytes(row_group_indices, - output_dtypes, - _output_column_schemas, - expr_conv.get_converted_expr().value()); + return _extended_metadata->bloom_filters_byte_ranges(row_group_indices, + output_dtypes, + _output_column_schemas, + expr_conv.get_converted_expr().value()); } std::vector> diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp index 2979ecf23cf5..642d194d0a89 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp @@ -8,8 +8,6 @@ #include #include -#include - namespace cudf::io::parquet::experimental { hybrid_scan_multifile::hybrid_scan_multifile( @@ -86,30 +84,14 @@ hybrid_scan_multifile::bloom_filters_byte_ranges( } std::vector> hybrid_scan_multifile::filter_row_groups_with_bloom_filters( - cudf::host_span> const> bloom_filter_data, + cudf::host_span const> bloom_filter_data, cudf::host_span const> row_group_indices, parquet_reader_options const& options, rmm::cuda_stream_view stream) const { CUDF_FUNC_RANGE(); - - // Concatenate the per-source spans in source order - auto const num_chunks = - std::accumulate(bloom_filter_data.begin(), - bloom_filter_data.end(), - std::size_t{0}, - [](auto sum, auto const& source_data) { return sum + source_data.size(); }); - - std::vector> flattened_bloom_filter_data; - flattened_bloom_filter_data.reserve(num_chunks); - for (auto const& source_bloom_filter_data : bloom_filter_data) { - flattened_bloom_filter_data.insert(flattened_bloom_filter_data.end(), - source_bloom_filter_data.begin(), - source_bloom_filter_data.end()); - } - return _impl->filter_row_groups_with_bloom_filters( - flattened_bloom_filter_data, row_group_indices, options, stream); + bloom_filter_data, row_group_indices, options, stream); } std::unique_ptr hybrid_scan_multifile::build_all_true_row_mask( diff --git a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp index c723ad2159c7..6b78836be8ab 100644 --- a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp +++ b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -33,10 +32,8 @@ namespace { -// 32-byte alignment required for bloom filter device buffers auto constexpr bloom_filter_alignment = rmm::CUDA_ALLOCATION_ALIGNMENT; - /** * @brief Copy fixed-width column data to a host vector */ @@ -352,7 +349,7 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFilters) file_buffers.emplace_back(std::get<1>(create_parquet_with_stats())); } - auto inputs = build_multifile_inputs(file_buffers); + auto inputs = multifile_inputs(build_source_info(file_buffers)); // An equality predicate makes col0 eligible for bloom filtering. cuDF's Parquet writer does not // emit bloom filters, so the per-source bloom byte ranges come back empty (same as single-file). @@ -393,9 +390,8 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFilters) auto const input_row_group_indices = reader->all_row_groups(options); ASSERT_EQ(input_row_group_indices.size(), num_sources); - auto const empty_bloom_data = - std::vector>>(num_sources); - auto const bloom_filtered = reader->filter_row_groups_with_bloom_filters( + auto const empty_bloom_data = std::vector>{}; + auto const bloom_filtered = reader->filter_row_groups_with_bloom_filters( empty_bloom_data, input_row_group_indices, options, stream); EXPECT_EQ(bloom_filtered, input_row_group_indices); } @@ -532,13 +528,7 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFiltersRealData) bloom_filter_alignment_parquet.end()); std::vector> file_buffers(num_sources, fixture); - auto inputs = build_multifile_inputs(file_buffers); - - std::vector> datasource_refs; - datasource_refs.reserve(num_sources); - for (auto& ds : inputs.datasources) { - datasource_refs.emplace_back(*ds); - } + auto inputs = multifile_inputs(build_source_info(file_buffers)); // An equality predicate makes the "r_reason_desc" column bloom-eligible auto literal_value = cudf::string_scalar("Did not like the color", true, stream); @@ -571,14 +561,19 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFiltersRealData) } auto [bloom_buffers, bloom_data_per_source, bloom_tasks] = cudf::io::parquet::fetch_byte_ranges_to_device_async( - datasource_refs, ranges_per_source, stream, aligned_mr); + inputs.datasource_refs, ranges_per_source, stream, aligned_mr); bloom_tasks.get(); for (auto const& per_source : bloom_data_per_source) { ASSERT_EQ(per_source.size(), 1); } + // Flatten the per-source bloom filter data in source order + std::vector> bloom_filter_data; + for (auto const& per_source : bloom_data_per_source) { + bloom_filter_data.insert(bloom_filter_data.end(), per_source.begin(), per_source.end()); + } auto const bloom_filtered = reader->filter_row_groups_with_bloom_filters( - bloom_data_per_source, input_row_group_indices, options, stream); + bloom_filter_data, input_row_group_indices, options, stream); // Shouldn't filter out any RG, since the queried value is present in every source. EXPECT_EQ(bloom_filtered, input_row_group_indices); From 25c6be0b6155b791902200f1edaba7b35131516b Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Fri, 24 Jul 2026 10:30:59 +0200 Subject: [PATCH 05/13] Format --- .../io/parquet/experimental/hybrid_scan_helpers.hpp | 12 ++++++------ .../hybrid_scan_multifile_filters_test.cpp | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp index fb46f589fc92..0fcc0b18cabd 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp @@ -215,12 +215,12 @@ class aggregate_reader_metadata : public aggregate_reader_metadata_base { * * @return A pair of vectors containing bloom filter byte ranges and corresponding source indices */ - [[nodiscard]] std::pair, - std::vector> - bloom_filters_byte_ranges(std::span const> row_group_indices, - std::span output_dtypes, - std::span output_column_schemas, - std::reference_wrapper filter); + [[nodiscard]] std::pair, + std::vector> + bloom_filters_byte_ranges(std::span const> row_group_indices, + std::span output_dtypes, + std::span output_column_schemas, + std::reference_wrapper filter); /** * @brief Get the dictionary page byte ranges, one per column chunk with (in)equality predicate diff --git a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp index 4baed574a3bc..d4cbc297150e 100644 --- a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp +++ b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp @@ -24,8 +24,8 @@ #include #include -#include #include +#include #include #include @@ -34,7 +34,6 @@ #include #include #include -#include #include namespace { From 2484b203a855755faf6c1b9bd88fb40101627354 Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Mon, 27 Jul 2026 14:08:05 +0200 Subject: [PATCH 06/13] Enhance bloom filter validation in hybrid scan implementation This update adds a check to ensure that the size of the bloom filter data matches the expected number of row groups and columns with bloom filters. Additionally, the documentation for the `secondary_filters_byte_ranges` method has been corrected to reflect the proper class reference. The test for filtering row groups with bloom filters has been updated to ensure accurate assertions regarding the size of bloom byte ranges and their mapping to sources. --- .../experimental/hybrid_scan_helpers.cpp | 7 +++++ .../parquet/experimental/hybrid_scan_impl.hpp | 2 +- .../hybrid_scan_multifile_filters_test.cpp | 28 ++++++++++++------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp index 3b2a3c300673..494ee98d9a33 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp @@ -636,6 +636,13 @@ aggregate_reader_metadata::filter_row_groups_with_bloom_filters( // Compute total number of input row groups auto const total_row_groups = compute_total_row_groups(row_group_indices); + // Ensure there is one bloom filter data span per eligible column in each row group + CUDF_EXPECTS(bloom_filter_data.size() == + static_cast(total_row_groups) * bloom_filter_col_schemas.size(), + "Bloom filter data size must match the number of row groups times the number of " + "columns with bloom filters and an equality predicate", + std::invalid_argument); + // Transform bloom filter data to cuda::std::byte type for apply_bloom_filters std::vector> transformed_bloom_filter_data; transformed_bloom_filter_data.reserve(bloom_filter_data.size()); diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp b/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp index 8836dbd3bdb1..dc85c3367923 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp @@ -109,7 +109,7 @@ class hybrid_scan_reader_impl : public parquet::detail::reader_impl { rmm::cuda_stream_view stream); /** - * @copydoc cudf::io::parquet::experimental::hybrid_scan_multifile::secondary_filters_byte_ranges + * @copydoc cudf::io::parquet::experimental::hybrid_scan_reader::secondary_filters_byte_ranges */ [[nodiscard]] std::pair, std::vector> secondary_filters_byte_ranges(std::span const> row_group_indices, diff --git a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp index d4cbc297150e..db60c9315452 100644 --- a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp +++ b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp @@ -696,9 +696,19 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFiltersRealData) auto const input_row_group_indices = reader->all_row_groups(options); ASSERT_EQ(input_row_group_indices.size(), num_sources); - auto const [bloom_byte_ranges, bloom_source_map] = + auto const bloom_ranges_and_source_map = reader->bloom_filters_byte_ranges(input_row_group_indices, options); - ASSERT_EQ(bloom_byte_ranges.size(), static_cast(num_sources)); + auto const& [bloom_byte_ranges, bloom_source_map] = bloom_ranges_and_source_map; + + // One byte range per (row group, bloom filter column) pair across all sources. Only + // `r_reason_desc` carries an equality predicate, so there is a single bloom filter column. + auto constexpr num_bloom_filter_columns = 1; + auto const total_row_groups = + std::accumulate(input_row_group_indices.begin(), + input_row_group_indices.end(), + std::size_t{0}, + [](auto sum, auto const& rgs) { return sum + rgs.size(); }); + ASSERT_EQ(bloom_byte_ranges.size(), total_row_groups * num_bloom_filter_columns); ASSERT_EQ(bloom_byte_ranges.size(), bloom_source_map.size()); std::vector expected_source_map(num_sources); std::iota(expected_source_map.begin(), expected_source_map.end(), 0); @@ -707,15 +717,13 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFiltersRealData) return r.is_empty(); })); - std::vector> ranges_per_source(num_sources); - for (size_t i = 0; i < bloom_byte_ranges.size(); ++i) { - ASSERT_LT(bloom_source_map[i], num_sources); - ranges_per_source[bloom_source_map[i]].push_back(bloom_byte_ranges[i]); - } - auto [bloom_buffers, bloom_data_per_source, bloom_tasks] = - cudf::io::parquet::fetch_byte_ranges_to_device_async( + auto const ranges_per_source = + group_byte_ranges_by_source(bloom_ranges_and_source_map, inputs.datasources.size()); + + // Bloom filters must be fetched with `fetch_bloom_filters_to_device` + [[maybe_unused]] auto [bloom_buffers, bloom_data_per_source] = + cudf::io::parquet::fetch_bloom_filters_to_device( inputs.datasource_refs, ranges_per_source, stream, aligned_mr); - bloom_tasks.get(); for (auto const& per_source : bloom_data_per_source) { ASSERT_EQ(per_source.size(), 1); } From 4d4bbc92c3b945ebaf108c59e597f3d72c05f9f2 Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Mon, 27 Jul 2026 18:56:42 +0200 Subject: [PATCH 07/13] Refactor bloom filter parameter types to use std::span This update modifies the bloom filter methods in the hybrid scan implementation to utilize std::span instead of cudf::host_span. This change enhances type safety and simplifies the interface for handling row group indices and bloom filter data. Additionally, unnecessary includes and alignment specifications have been removed from the test files, streamlining the codebase. --- .../cudf/io/experimental/hybrid_scan_multifile.hpp | 12 ++++-------- .../io/parquet/experimental/hybrid_scan_impl.cpp | 3 +-- .../io/parquet/experimental/hybrid_scan_impl.hpp | 2 +- .../parquet/experimental/hybrid_scan_multifile.cpp | 6 +++--- .../hybrid_scan_multifile_filters_test.cpp | 13 +++---------- 5 files changed, 12 insertions(+), 24 deletions(-) diff --git a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp index 2922110d6826..fdeaf2b62bf3 100644 --- a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp +++ b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -159,22 +160,17 @@ class hybrid_scan_multifile { /** * @brief Get byte ranges of bloom filters for row group pruning * - * @note Device buffers for bloom filter byte ranges must be allocated using a 32 byte - * aligned memory resource - * * @param row_group_indices Span of vectors of input row group indices, one per source * @param options Parquet reader options * @return A pair of vectors containing bloom filter byte ranges and corresponding source indices */ [[nodiscard]] std::pair, std::vector> - bloom_filters_byte_ranges(cudf::host_span const> row_group_indices, + bloom_filters_byte_ranges(std::span const> row_group_indices, parquet_reader_options const& options) const; /** * @brief Filter the row groups using column chunk bloom filters * - * @note The `bloom_filter_data` device spans must point to 32-byte aligned addresses - * * @param bloom_filter_data Flattened device spans of bloom filters returned in the same order as * `bloom_filters_byte_ranges` * @param row_group_indices Input row group indices @@ -183,8 +179,8 @@ class hybrid_scan_multifile { * @return Vectors of filtered per-source row group indices, one per source */ [[nodiscard]] std::vector> filter_row_groups_with_bloom_filters( - cudf::host_span const> bloom_filter_data, - cudf::host_span const> row_group_indices, + std::span const> bloom_filter_data, + std::span const> row_group_indices, parquet_reader_options const& options, rmm::cuda_stream_view stream) const; diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp index e342a1237788..eb5faa82e6ff 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp @@ -319,8 +319,7 @@ hybrid_scan_reader_impl::dictionary_pages_byte_ranges( std::pair, std::vector> hybrid_scan_reader_impl::bloom_filters_byte_ranges( - cudf::host_span const> row_group_indices, - parquet_reader_options const& options) + std::span const> row_group_indices, parquet_reader_options const& options) { CUDF_EXPECTS(not row_group_indices.empty(), "Empty input row group indices encountered"); auto [expr_conv, output_dtypes] = prepare_filter_and_output_types(options); diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp b/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp index dc85c3367923..2debc0f29a5b 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp @@ -119,7 +119,7 @@ class hybrid_scan_reader_impl : public parquet::detail::reader_impl { * @copydoc cudf::io::parquet::experimental::hybrid_scan_multifile::bloom_filters_byte_ranges */ [[nodiscard]] std::pair, std::vector> - bloom_filters_byte_ranges(cudf::host_span const> row_group_indices, + bloom_filters_byte_ranges(std::span const> row_group_indices, parquet_reader_options const& options); /** diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp index 6559d52d899d..586e73e5cc4d 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp @@ -79,7 +79,7 @@ std::vector> hybrid_scan_multifile::filter_row_groups_wit std::pair, std::vector> hybrid_scan_multifile::bloom_filters_byte_ranges( - cudf::host_span const> row_group_indices, + std::span const> row_group_indices, parquet_reader_options const& options) const { CUDF_FUNC_RANGE(); @@ -87,8 +87,8 @@ hybrid_scan_multifile::bloom_filters_byte_ranges( } std::vector> hybrid_scan_multifile::filter_row_groups_with_bloom_filters( - cudf::host_span const> bloom_filter_data, - cudf::host_span const> row_group_indices, + std::span const> bloom_filter_data, + std::span const> row_group_indices, parquet_reader_options const& options, rmm::cuda_stream_view stream) const { diff --git a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp index db60c9315452..ff2c0a378fee 100644 --- a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp +++ b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp @@ -23,10 +23,6 @@ #include #include -#include -#include -#include - #include #include #include @@ -38,8 +34,6 @@ namespace { -auto constexpr bloom_filter_alignment = rmm::CUDA_ALLOCATION_ALIGNMENT; - /** * @brief Copy fixed-width column data to a host vector */ @@ -554,8 +548,7 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFiltersRealData) { auto constexpr num_sources = 32; auto const stream = cudf::get_default_stream(); - auto aligned_mr = rmm::mr::aligned_resource_adaptor(cudf::get_current_device_resource_ref(), - bloom_filter_alignment); + auto const mr = cudf::get_current_device_resource_ref(); // Embedded copy of cuDF's committed bloom-filter fixture (cuDF cannot write bloom filters, and // cuDF tests avoid committed data files). Source: @@ -691,7 +684,7 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFiltersRealData) auto options = cudf::io::parquet_reader_options::builder().filter(filter).build(); auto const reader = std::make_unique( - cudf::host_span const>{inputs.footer_byte_spans}, options); + inputs.footer_byte_spans, options); auto const input_row_group_indices = reader->all_row_groups(options); ASSERT_EQ(input_row_group_indices.size(), num_sources); @@ -723,7 +716,7 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFiltersRealData) // Bloom filters must be fetched with `fetch_bloom_filters_to_device` [[maybe_unused]] auto [bloom_buffers, bloom_data_per_source] = cudf::io::parquet::fetch_bloom_filters_to_device( - inputs.datasource_refs, ranges_per_source, stream, aligned_mr); + inputs.datasource_refs, ranges_per_source, stream, mr); for (auto const& per_source : bloom_data_per_source) { ASSERT_EQ(per_source.size(), 1); } From df86dc8a514e9d74ed9d2f6feb9f07cb79373940 Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Tue, 28 Jul 2026 12:37:05 +0200 Subject: [PATCH 08/13] Add tests for parquet bloom filter behavior with absent and mixed predicates --- .../bloom_filter_alignment_desc_only.parquet | Bin 0 -> 2042 bytes .../data_index_bloom_encoding_stats.parquet | Bin 0 -> 1643 bytes ...a_index_bloom_encoding_with_length.parquet | Bin 0 -> 2885 bytes .../cudf/tests/input_output/test_parquet.py | 46 ++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 python/cudf/cudf/tests/data/parquet/bloom_filter_alignment_desc_only.parquet create mode 100644 python/cudf/cudf/tests/data/parquet/data_index_bloom_encoding_stats.parquet create mode 100644 python/cudf/cudf/tests/data/parquet/data_index_bloom_encoding_with_length.parquet diff --git a/python/cudf/cudf/tests/data/parquet/bloom_filter_alignment_desc_only.parquet b/python/cudf/cudf/tests/data/parquet/bloom_filter_alignment_desc_only.parquet new file mode 100644 index 0000000000000000000000000000000000000000..f7f4e6ce29708993b966c509af0c54b8cfed17f1 GIT binary patch literal 2042 zcmb_d&2Jk;6rc51oZ6;N@GjnB($r}@0mK2RoDc`Zp&$+jBo6!mfXV@h11By>NFW4=AcRV|6odrjy|oiHX#_!HrQdIN z-kW(JGxOdqN=w7Mi+|0*zv;;FS)TL~@-nw|og;)e08W4l;0AaAT>vkj8*m8V1NZ>} zz+t{lZc$V|{&QX6xj?BCj&wV$scw+tnyL##E>XVz#J>8Ep zAno4|@h>^I)BeC6#K@%pN74ar0-~Or1Ii#xA!U)Kk!Fx)k>-%jBju3hk@83jNQ*$j zgNZojN~gO|{%)UPRPi!AX6f}hQ)yE-%37_6cOavww5D6M$SkT^ zRIgD>t1>+KJV>n)qjf`98w%%l$(Fki)(zEQYQSBL8A``f?f$O6ct;}ruieAx;r6VL%)m2vE^P?nK zmCH=bi)8E3rfe8;&1&Iw%|yO$k@o}U8_e@ z`&_foe$j<}+h+~g9q5*Q(K;Hoi}PFr+QK%-AE`SZ{`JfAA8o$=#BX1hS6;dO>*i16 zXFmVoy*K`>YIjOs@h&l!B4LlIlvq{fJ%o4kIl?|;rJeAKSth)Hmqb(hNDe!XEf4I> zRGF!CG(uL4h!Zgq;T>X%c!J_CgS2^MT;j`}8&%xODUUYb% zL*467c)yo_Dk8ktz2j845f!pAukcPU5hG#0w6wIae5QR=;hTbxX1 zrF2osN>Wjpv{h0Xn$@Ztr?|xqxv^by9&r0ppzpFH#04x0Uz>Q)caa z=h_3CktWgq%-ot%y;xF`$34|gIR-0y*>!tOK;M3 bW|-KllBvW{Vo038Vru_s2b|EW;jjNU*uumY literal 0 HcmV?d00001 diff --git a/python/cudf/cudf/tests/data/parquet/data_index_bloom_encoding_stats.parquet b/python/cudf/cudf/tests/data/parquet/data_index_bloom_encoding_stats.parquet new file mode 100644 index 0000000000000000000000000000000000000000..14ad1916831cc5548c44a7b3b3cce6af2dd0c058 GIT binary patch literal 1643 zcmds2O=uHQ5PsRFB|@Q%?XFo_a9!w+(!~5UX}4C&R&y{Jq(&3s;vPow{vzH%f0qkH>sBhF*5goI5_V zp-lDFUE85f&;5F}CV$vf$Cn} zFiem|Q&Y!ThUdT_a6WdC0gbSBy&w=$9$0JzQV3xP0S;78Y)2sX!c&n%>Ve0?0Gg7= zMgiD=fMaSP5g9`;3D&z!V5cYmxPCMvb$^~R(a#)eUW%D;^qbotxmsj+YfXc=!pbEa&{HrHW`-Vo-%BdAOfNRsu9{! zTcBY?wMI*NF{F-JW~h)J)^lp$x@STvj*sY4mt^T_)6%3iDX->mj)lT3lhqBaAaxB) zlnqYNU~+&1QodvuFfGu{NP}&XW~L!PKg1QY`HXH^x+Z2UGbgstj@bgvnx0WhMp5cQ z@Z{hm3HUEr*`jWRa;BymAuJ-`#ZpNil*(6|!btjD#YwP&kq(}1&-9C} zL#3>ti5)SyQW6%5segeOQ6W^>IE}(L#3Ig$EcV_lA^X+==%!*vQ%yayWhO*2}&dxEG zXN)bnp68!|vA*34MK8?2v~v)V8%9~0;E!n74qP$zhXt69-y3uVEslK09-k#++ZDUR zUi$?#o(2ABAUgg_n(}#;w z70l95%}g#|o?G90|LF_kCF2?^lnuS`APPW*v#MErn?$ZwRlR2xEdGYK&-rL<=g7ol-)_Uy7FEEg)fG ze{=(BQ)nq0>Q|umC_{?`jO0K_#;A!P<4=M11H_4Lkx6W+sK*I)+7Rj#C}0|!4i|+G zEBRmi%O@bPIaCz97CAK3<->i5$v!cXi$$L%VDe3lDNgb(s{Z4IP`5EwQ(&{A@}-Ev z1$6ojk;1t0+WI~Z#_=FWTH-@3Ro;j+g^5xBFed*i#FGf362;^|Td+=qm{u#nGPG&+ zw3KfOqRs;P^k7spBh4_NRl>oUQDw|9t4A8|z+zl8>Wd{iKr-_*Y;U`Ln`=ltrnO1h zG0m2dPayX&@Tae3f#6II9A;n*nMg{c4VW~>A9X0TFuS<$v~1RVJKO+ zX$^*+hi0fUC~K>3Ier@s*5Oz51|2sDTt{>Qzb|G~CN_h~j@z+^USu_C@xj&s(<`H( z7r8;B?>ny7AQF0xs4~4I*VgVoY^IbCGnWWgBwH5bszCePox-p4I4Vt2y%u~GE_h#FDZ}?;+CDV=c ctS<(kK&~40{DHW7y)oOEWhGq66(rE#0Cm*f?f?J) literal 0 HcmV?d00001 diff --git a/python/cudf/cudf/tests/input_output/test_parquet.py b/python/cudf/cudf/tests/input_output/test_parquet.py index bc7af5360010..dce03e801678 100644 --- a/python/cudf/cudf/tests/input_output/test_parquet.py +++ b/python/cudf/cudf/tests/input_output/test_parquet.py @@ -4784,6 +4784,52 @@ def test_parquet_bloom_filters_alignment(datadir, columns, memory_resource): assert_eq(expected, read) +@pytest.mark.parametrize( + "predicate", + [ + [("String", "==", "Hello")], # present, row group kept + [("String", "==", "not-in-this-file")], # absent, row group pruned + ], +) +def test_parquet_bloom_filters_length_absent(datadir, predicate): + # Reader must recover the bitset in case of absent length field in the header. + # Source: apache/parquet-testing (Apache-2.0) + fname_len_absent = datadir / "data_index_bloom_encoding_stats.parquet" + fname_with_len = datadir / "data_index_bloom_encoding_with_length.parquet" + + expected = pq.read_table(fname_len_absent, filters=predicate) + read_len_absent = cudf.read_parquet( + fname_len_absent, filters=predicate + ).to_arrow() + read_with_len = cudf.read_parquet( + fname_with_len, filters=predicate + ).to_arrow() + + assert_eq(expected, read_len_absent) + assert_eq(read_with_len, read_len_absent) + + +@pytest.mark.parametrize( + "predicate", + [ + [("r_reason_id", "==", "AAAAAAAABAAAAAAA")], # no bloom filter + [ + ("r_reason_desc", "==", "Did not like the color"), + ("r_reason_id", "==", "AAAAAAAAIAAAAAAA"), + ], # with and without a bloom filter + ], +) +def test_parquet_bloom_filters_mixed_presence(datadir, predicate): + # Source: same data as in bloom_filter_alignment.parquet, + # written with only r_reason_desc having a bloom filter + fname = datadir / "bloom_filter_alignment_desc_only.parquet" + + expected = pq.read_table(fname, filters=predicate) + read = cudf.read_parquet(fname, filters=predicate).to_arrow() + + assert_eq(expected, read) + + def test_parquet_reader_unsupported_compression(datadir): fname = datadir / "hadoop_lz4_compressed.parquet" From d9c3bed7a116858e4d4f9455f4cf2baba1b25515 Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Tue, 28 Jul 2026 12:40:47 +0200 Subject: [PATCH 09/13] Style --- python/cudf/cudf/tests/input_output/test_parquet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cudf/cudf/tests/input_output/test_parquet.py b/python/cudf/cudf/tests/input_output/test_parquet.py index dce03e801678..5c4bbb57abba 100644 --- a/python/cudf/cudf/tests/input_output/test_parquet.py +++ b/python/cudf/cudf/tests/input_output/test_parquet.py @@ -4820,7 +4820,7 @@ def test_parquet_bloom_filters_length_absent(datadir, predicate): ], ) def test_parquet_bloom_filters_mixed_presence(datadir, predicate): - # Source: same data as in bloom_filter_alignment.parquet, + # Source: same data as in bloom_filter_alignment.parquet, # written with only r_reason_desc having a bloom filter fname = datadir / "bloom_filter_alignment_desc_only.parquet" From 03c9df3b07fb0117f84f11263ea64fdfa699a36d Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Mon, 3 Aug 2026 11:54:34 +0200 Subject: [PATCH 10/13] Update bloom filter parameter description and fix comment in hybrid scan helpers - Clarified the description of the `bloom_filter_data` parameter in `hybrid_scan_multifile.hpp` to specify that it contains header-stripped bloom filter bitsets ordered to match the byte ranges. - Updated a comment in `hybrid_scan_helpers.cpp` to reflect that an empty vector of equality literals will now return an empty pair instead. --- cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp | 5 +++-- cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp index fdeaf2b62bf3..da4f3ed43e4f 100644 --- a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp +++ b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp @@ -171,8 +171,9 @@ class hybrid_scan_multifile { /** * @brief Filter the row groups using column chunk bloom filters * - * @param bloom_filter_data Flattened device spans of bloom filters returned in the same order as - * `bloom_filters_byte_ranges` + * @param bloom_filter_data Device spans of header-stripped bloom filter bitsets of column + * chunks with an equality predicate, ordered to match the bloom filter + * byte ranges returned by `bloom_filters_byte_ranges` * @param row_group_indices Input row group indices * @param options Parquet reader options * @param stream CUDA stream used for device memory operations and kernel launches diff --git a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp index 2300f8e582eb..33fbae3bb385 100644 --- a/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp +++ b/cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp @@ -465,7 +465,7 @@ aggregate_reader_metadata::bloom_filters_byte_ranges( std::back_inserter(bloom_filter_col_schemas), [](auto& bloom_filter_literals) { return not bloom_filter_literals.empty(); }); - // No equality literals found, return empty vector + // No equality literals found, return empty pair if (bloom_filter_col_schemas.empty()) { return {}; } // Compute total number of input row groups From 95388b0a9bceed130f57944ea37c7801f8471463 Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Wed, 5 Aug 2026 10:42:35 +0200 Subject: [PATCH 11/13] Add dictionary page filtering and update bloom filter documentation - Introduced new methods in `hybrid_scan_multifile` for obtaining byte ranges of column chunk dictionary pages and filtering row groups based on these pages. - Updated documentation to clarify the requirement for 32-byte aligned memory resources for bloom filter byte ranges. - Removed deprecated test cases related to bloom filters to streamline the test suite. This enhances the functionality for row group pruning in parquet files and improves code clarity. --- .../io/experimental/hybrid_scan_multifile.hpp | 61 +++--- .../hybrid_scan_multifile_filters_test.cpp | 191 ------------------ .../cudf/tests/input_output/test_parquet.py | 99 ++++++--- 3 files changed, 105 insertions(+), 246 deletions(-) diff --git a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp index da4f3ed43e4f..50db69d56d75 100644 --- a/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp +++ b/cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp @@ -160,6 +160,9 @@ class hybrid_scan_multifile { /** * @brief Get byte ranges of bloom filters for row group pruning * + * @note Device buffers for bloom filter byte ranges must be allocated using a 32 byte + * aligned memory resource + * * @param row_group_indices Span of vectors of input row group indices, one per source * @param options Parquet reader options * @return A pair of vectors containing bloom filter byte ranges and corresponding source indices @@ -185,6 +188,35 @@ class hybrid_scan_multifile { parquet_reader_options const& options, rmm::cuda_stream_view stream) const; + /** + * @brief Get byte ranges of column chunk dictionary pages for row group pruning + * + * @param row_group_indices Span of vectors of input row group indices, one per source + * @param options Parquet reader options + * @return Pair of flattened byte ranges to column chunk dictionary pages subject to the filter + * predicate and their corresponding source indices + */ + [[nodiscard]] std::pair, std::vector> + dictionary_pages_byte_ranges(cudf::host_span const> row_group_indices, + parquet_reader_options const& options) const; + + /** + * @brief Filter the row groups using column chunk dictionary pages + * + * @param dictionary_page_data Device spans of dictionary page data of column chunks with an + * (in)equality predicate, ordered to match the dictionary page byte + * ranges returned by `dictionary_pages_byte_ranges` + * @param row_group_indices Span of vectors of input row group indices, one per source + * @param options Parquet reader options + * @param stream CUDA stream used for device memory operations and kernel launches + * @return Vector of vectors of filtered row group indices, one per source + */ + [[nodiscard]] std::vector> filter_row_groups_with_dictionary_pages( + cudf::host_span const> dictionary_page_data, + cudf::host_span const> row_group_indices, + parquet_reader_options const& options, + rmm::cuda_stream_view stream) const; + /** * @brief Builds a boolean survival column of size equal to the total number of rows in the row * groups containing all `true` values @@ -468,35 +500,6 @@ class hybrid_scan_multifile { */ [[nodiscard]] bool has_next_table_chunk() const; - /** - * @brief Get byte ranges of column chunk dictionary pages for row group pruning - * - * @param row_group_indices Span of vectors of input row group indices, one per source - * @param options Parquet reader options - * @return Pair of flattened byte ranges to column chunk dictionary pages subject to the filter - * predicate and their corresponding source indices - */ - [[nodiscard]] std::pair, std::vector> - dictionary_pages_byte_ranges(cudf::host_span const> row_group_indices, - parquet_reader_options const& options) const; - - /** - * @brief Filter the row groups using column chunk dictionary pages - * - * @param dictionary_page_data Device spans of dictionary page data of column chunks with an - * (in)equality predicate, ordered to match the dictionary page byte - * ranges returned by `dictionary_pages_byte_ranges` - * @param row_group_indices Span of vectors of input row group indices, one per source - * @param options Parquet reader options - * @param stream CUDA stream used for device memory operations and kernel launches - * @return Vector of vectors of filtered row group indices, one per source - */ - [[nodiscard]] std::vector> filter_row_groups_with_dictionary_pages( - cudf::host_span const> dictionary_page_data, - cudf::host_span const> row_group_indices, - parquet_reader_options const& options, - rmm::cuda_stream_view stream) const; - private: std::unique_ptr _impl; }; diff --git a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp index ff2c0a378fee..a24b3205ffac 100644 --- a/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp +++ b/cpp/tests/io/experimental/hybrid_scan_multifile_filters_test.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -24,7 +23,6 @@ #include #include -#include #include #include #include @@ -544,195 +542,6 @@ TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFilters) } } -TEST_F(HybridScanMultifileFiltersTest, FilterRowGroupsWithBloomFiltersRealData) -{ - auto constexpr num_sources = 32; - auto const stream = cudf::get_default_stream(); - auto const mr = cudf::get_current_device_resource_ref(); - - // Embedded copy of cuDF's committed bloom-filter fixture (cuDF cannot write bloom filters, and - // cuDF tests avoid committed data files). Source: - // python/cudf/cudf/tests/data/parquet/bloom_filter_alignment.parquet (DuckDB-written; bloom - // filter on the r_reason_desc column). Regenerate with: xxd -i bloom_filter_alignment.parquet - constexpr std::array bloom_filter_alignment_parquet{ - 0x50, 0x41, 0x52, 0x31, 0x15, 0x04, 0x15, 0x98, 0x02, 0x15, 0xa0, 0x02, 0x4c, 0x15, 0x46, 0x15, - 0x00, 0x00, 0x00, 0x8c, 0x01, 0xf0, 0x8b, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0b, - 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, - 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x13, - 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, - 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x1b, - 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1f, - 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, - 0x00, 0x00, 0x00, 0x15, 0x00, 0x15, 0x90, 0x03, 0x15, 0x62, 0x2c, 0x15, 0x46, 0x15, 0x10, 0x15, - 0x06, 0x15, 0x06, 0x00, 0x00, 0xc8, 0x01, 0x90, 0x02, 0x00, 0x00, 0x00, 0x46, 0x01, 0x06, 0x41, - 0x40, 0x20, 0x0c, 0x44, 0x61, 0x1c, 0x48, 0xa2, 0x2c, 0x4c, 0xe3, 0x3c, 0x50, 0x24, 0x4d, 0x54, - 0x65, 0x5d, 0x58, 0xa6, 0x6d, 0x5c, 0xe7, 0x7d, 0x60, 0x28, 0x02, 0x00, 0x00, 0xfe, 0x02, 0x00, - 0xfe, 0x02, 0x00, 0x8a, 0x02, 0x00, 0x15, 0x04, 0x15, 0xf8, 0x0a, 0x15, 0x86, 0x03, 0x4c, 0x15, - 0x46, 0x15, 0x00, 0x00, 0x00, 0xbc, 0x05, 0x10, 0x10, 0x00, 0x00, 0x00, 0x41, 0x0d, 0x01, 0x00, - 0x42, 0x0d, 0x08, 0x2e, 0x14, 0x00, 0x00, 0x43, 0x4a, 0x14, 0x00, 0x00, 0x44, 0x4a, 0x14, 0x00, - 0x00, 0x45, 0x4a, 0x14, 0x00, 0x00, 0x46, 0x4a, 0x14, 0x00, 0x00, 0x47, 0x4a, 0x14, 0x00, 0x00, - 0x48, 0x4a, 0x14, 0x00, 0x00, 0x49, 0x4a, 0x14, 0x00, 0x00, 0x4a, 0x4a, 0x14, 0x00, 0x00, 0x4b, - 0x4a, 0x14, 0x00, 0x00, 0x4c, 0x4a, 0x14, 0x00, 0x00, 0x4d, 0x4a, 0x14, 0x00, 0x00, 0x4e, 0x4a, - 0x14, 0x00, 0x00, 0x4f, 0x4a, 0x14, 0x00, 0x00, 0x50, 0x4a, 0x14, 0x00, 0x31, 0x2d, 0x2e, 0x2c, - 0x01, 0x00, 0x42, 0x2d, 0x41, 0x2e, 0x14, 0x00, 0x00, 0x43, 0x4a, 0x14, 0x00, 0x00, 0x44, 0x4a, - 0x14, 0x00, 0x00, 0x45, 0x4a, 0x14, 0x00, 0x00, 0x46, 0x4a, 0x14, 0x00, 0x00, 0x47, 0x4a, 0x14, - 0x00, 0x00, 0x48, 0x4a, 0x14, 0x00, 0x00, 0x49, 0x4a, 0x14, 0x00, 0x00, 0x4a, 0x4a, 0x14, 0x00, - 0x00, 0x4b, 0x4a, 0x14, 0x00, 0x00, 0x4c, 0x4a, 0x14, 0x00, 0x00, 0x4d, 0x4a, 0x14, 0x00, 0x00, - 0x4e, 0x4a, 0x14, 0x00, 0x00, 0x4f, 0x4a, 0x14, 0x00, 0x00, 0x50, 0x4a, 0x14, 0x00, 0x51, 0x59, - 0x2e, 0x2c, 0x01, 0x00, 0x42, 0x4d, 0x6d, 0x2e, 0x14, 0x00, 0x00, 0x43, 0x4a, 0x14, 0x00, 0x1c, - 0x44, 0x43, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x15, 0x00, 0x15, 0x90, 0x03, 0x15, 0x62, 0x2c, - 0x15, 0x46, 0x15, 0x10, 0x15, 0x06, 0x15, 0x06, 0x00, 0x00, 0xc8, 0x01, 0x90, 0x02, 0x00, 0x00, - 0x00, 0x46, 0x01, 0x06, 0x41, 0x40, 0x20, 0x0c, 0x44, 0x61, 0x1c, 0x48, 0xa2, 0x2c, 0x4c, 0xe3, - 0x3c, 0x50, 0x24, 0x4d, 0x54, 0x65, 0x5d, 0x58, 0xa6, 0x6d, 0x5c, 0xe7, 0x7d, 0x60, 0x28, 0x02, - 0x00, 0x00, 0xfe, 0x02, 0x00, 0xfe, 0x02, 0x00, 0x8a, 0x02, 0x00, 0x15, 0x04, 0x15, 0x82, 0x0b, - 0x15, 0xe0, 0x07, 0x4c, 0x15, 0x44, 0x15, 0x00, 0x00, 0x00, 0xc1, 0x05, 0xf0, 0x55, 0x13, 0x00, - 0x00, 0x00, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x64, 0x61, - 0x6d, 0x61, 0x67, 0x65, 0x64, 0x0f, 0x00, 0x00, 0x00, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, - 0x20, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x16, 0x00, 0x00, 0x00, 0x44, 0x69, 0x64, 0x20, - 0x6e, 0x6f, 0x74, 0x20, 0x67, 0x65, 0x74, 0x20, 0x69, 0x74, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x69, - 0x6d, 0x65, 0x1f, 0x00, 0x00, 0x00, 0x4e, 0x6f, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, - 0x6f, 0x64, 0x75, 0x63, 0x01, 0x0c, 0x04, 0x61, 0x74, 0x05, 0x51, 0x18, 0x6f, 0x72, 0x64, 0x72, - 0x65, 0x64, 0x0d, 0x05, 0x67, 0x2c, 0x72, 0x74, 0x73, 0x20, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, - 0x67, 0x28, 0x01, 0x4e, 0x08, 0x6f, 0x65, 0x73, 0x05, 0x4f, 0x01, 0x62, 0x1c, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x20, 0x61, 0x20, 0x32, 0x41, 0x00, 0x14, 0x49, 0x20, 0x68, 0x61, 0x76, 0x65, 0x01, - 0x3d, 0x34, 0x47, 0x69, 0x66, 0x74, 0x20, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x16, - 0x01, 0x3d, 0x0d, 0x8b, 0x34, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6c, 0x6f, 0x72, 0x52, 0x1a, 0x00, 0x14, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x15, 0x4e, 0x34, 0x00, - 0x10, 0x6d, 0x61, 0x6b, 0x65, 0x19, 0x4e, 0x19, 0x00, 0xf0, 0x3e, 0x77, 0x61, 0x72, 0x72, 0x61, - 0x6e, 0x74, 0x79, 0x1e, 0x00, 0x00, 0x00, 0x4e, 0x6f, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x6d, 0x79, - 0x20, 0x61, 0x72, 0x65, 0x61, 0x1f, 0x00, 0x00, 0x00, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x61, - 0x20, 0x62, 0x65, 0x74, 0x74, 0x65, 0x72, 0x20, 0x70, 0x72, 0x01, 0x2c, 0x28, 0x69, 0x6e, 0x20, - 0x61, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2b, 0x46, 0x23, 0x00, 0x14, 0x65, 0x78, 0x74, 0x65, - 0x6e, 0x64, 0x21, 0x5d, 0x0d, 0x69, 0x1d, 0x2f, 0x00, 0x14, 0x05, 0x74, 0x04, 0x74, 0x20, 0x21, - 0x16, 0x30, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x0b, 0x1d, - 0xa9, 0x3c, 0x66, 0x69, 0x74, 0x0a, 0x00, 0x00, 0x00, 0x57, 0x72, 0x6f, 0x6e, 0x67, 0x20, 0x73, - 0x69, 0x7a, 0x05, 0x1d, 0x28, 0x4c, 0x6f, 0x73, 0x74, 0x20, 0x6d, 0x79, 0x20, 0x6a, 0x6f, 0x62, - 0x01, 0x44, 0x80, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x70, - 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x12, 0x00, 0x00, 0x00, 0x64, 0x75, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x15, 0x16, 0x24, 0x0c, 0x00, 0x00, 0x00, 0x69, 0x74, 0x73, 0x20, 0x69, - 0x73, 0x01, 0xc5, 0x04, 0x6f, 0x79, 0x09, 0x10, 0x09, 0x0f, 0x40, 0x67, 0x69, 0x72, 0x6c, 0x09, - 0x00, 0x00, 0x00, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x20, 0x32, 0x33, 0x2e, 0x0d, 0x00, 0x00, - 0x34, 0x2e, 0x0d, 0x00, 0x00, 0x35, 0x2e, 0x0d, 0x00, 0x00, 0x36, 0x2e, 0x0d, 0x00, 0x00, 0x37, - 0x2e, 0x0d, 0x00, 0x00, 0x38, 0x2e, 0x0d, 0x00, 0x00, 0x39, 0x1d, 0x0d, 0x04, 0x33, 0x31, 0x2e, - 0x0d, 0x00, 0x00, 0x32, 0x2e, 0x0d, 0x00, 0x2e, 0x75, 0x00, 0x38, 0x33, 0x34, 0x09, 0x00, 0x00, - 0x00, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x20, 0x33, 0x35, 0x15, 0x00, 0x15, 0x90, 0x03, 0x15, - 0x62, 0x2c, 0x15, 0x46, 0x15, 0x10, 0x15, 0x06, 0x15, 0x06, 0x00, 0x00, 0xc8, 0x01, 0x90, 0x02, - 0x00, 0x00, 0x00, 0x46, 0x01, 0x06, 0x41, 0x40, 0x20, 0x0c, 0x44, 0x61, 0x1c, 0x48, 0xa2, 0x2c, - 0x4c, 0xe3, 0x3c, 0x50, 0x24, 0x4d, 0x54, 0x65, 0x5d, 0x58, 0xa6, 0x6d, 0x5c, 0xd7, 0x79, 0x1f, - 0x18, 0x02, 0x00, 0x00, 0xfe, 0x02, 0x00, 0xfe, 0x02, 0x00, 0x8a, 0x02, 0x00, 0x15, 0x80, 0x01, - 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x2e, 0x25, 0xce, - 0x22, 0x55, 0x71, 0x2a, 0xda, 0x42, 0x80, 0xf4, 0xf3, 0xe7, 0x0e, 0x91, 0x23, 0x38, 0x21, 0x89, - 0x8d, 0xb0, 0x63, 0x93, 0xe8, 0x01, 0xfd, 0x58, 0x11, 0xda, 0x28, 0x63, 0x87, 0x3f, 0x40, 0x7c, - 0x32, 0x1c, 0x08, 0xa8, 0x2f, 0xe2, 0xd8, 0xa3, 0x80, 0x2e, 0x4e, 0xa8, 0x4a, 0x86, 0x16, 0x24, - 0xce, 0xad, 0xea, 0x68, 0x00, 0x20, 0x36, 0xe6, 0xa2, 0x10, 0x99, 0x80, 0x6d, 0x15, 0x80, 0x01, - 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x4e, 0xf3, 0x6e, - 0x00, 0x4b, 0x4f, 0x44, 0x74, 0x0e, 0x73, 0xa0, 0x90, 0xe3, 0xbd, 0xe2, 0x0a, 0x32, 0x87, 0x50, - 0xc5, 0x41, 0x2a, 0x2c, 0xee, 0x3a, 0x12, 0x58, 0xa3, 0x0d, 0x05, 0xe5, 0x89, 0x66, 0x0f, 0xa3, - 0xc0, 0xb8, 0xa2, 0xe6, 0xc1, 0x4f, 0x00, 0xf6, 0x8c, 0x9a, 0xa2, 0xf0, 0x17, 0xc4, 0x29, 0x1f, - 0x06, 0x89, 0xbf, 0x13, 0x88, 0x58, 0x84, 0xc7, 0x38, 0xf9, 0x18, 0x01, 0x78, 0x15, 0x80, 0x01, - 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x50, 0x35, 0x06, - 0xbd, 0xf8, 0xeb, 0x00, 0x26, 0xc1, 0x0a, 0x5a, 0xd2, 0x4e, 0x69, 0x47, 0x82, 0xf4, 0x61, 0x61, - 0x68, 0x11, 0x32, 0x90, 0xe6, 0xa4, 0x8e, 0xa8, 0x44, 0x41, 0x53, 0x08, 0xd5, 0xea, 0x24, 0xfd, - 0x91, 0xae, 0x84, 0x81, 0xb8, 0xa6, 0x9a, 0x41, 0x75, 0xdc, 0x2b, 0x5c, 0x92, 0x06, 0xf0, 0x87, - 0xea, 0x38, 0x38, 0x52, 0x26, 0x08, 0x11, 0x7d, 0x6d, 0x28, 0xfc, 0x60, 0x89, 0x15, 0x02, 0x19, - 0x4c, 0x35, 0x00, 0x18, 0x0d, 0x64, 0x75, 0x63, 0x6b, 0x64, 0x62, 0x5f, 0x73, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x15, 0x06, 0x00, 0x15, 0x02, 0x25, 0x02, 0x18, 0x0b, 0x72, 0x5f, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x6b, 0x25, 0x22, 0x00, 0x15, 0x0c, 0x25, 0x02, 0x18, 0x0b, 0x72, - 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x25, 0x00, 0x00, 0x15, 0x0c, 0x25, - 0x02, 0x18, 0x0d, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x73, 0x63, - 0x25, 0x00, 0x00, 0x16, 0x46, 0x19, 0x1c, 0x19, 0x3c, 0x26, 0x00, 0x1c, 0x15, 0x02, 0x19, 0x15, - 0x10, 0x19, 0x18, 0x0b, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x6b, 0x15, - 0x02, 0x16, 0x46, 0x16, 0xea, 0x05, 0x16, 0xc4, 0x03, 0x26, 0xc6, 0x02, 0x26, 0x08, 0x1c, 0x18, - 0x04, 0x23, 0x00, 0x00, 0x00, 0x18, 0x04, 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x16, 0x46, 0x18, - 0x04, 0x23, 0x00, 0x00, 0x00, 0x18, 0x04, 0x01, 0x00, 0x00, 0x00, 0x11, 0x11, 0x00, 0x26, 0xfa, - 0x10, 0x15, 0xa0, 0x01, 0x00, 0x00, 0x26, 0x00, 0x1c, 0x15, 0x0c, 0x19, 0x15, 0x10, 0x19, 0x18, - 0x0b, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x15, 0x02, 0x16, 0x46, - 0x16, 0xca, 0x0e, 0x16, 0xaa, 0x04, 0x26, 0xf0, 0x06, 0x26, 0xcc, 0x03, 0x1c, 0x18, 0x10, 0x41, - 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x50, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x18, - 0x10, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x16, 0x00, 0x16, 0x46, 0x18, 0x10, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x50, - 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x18, 0x10, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, - 0x41, 0x41, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x11, 0x11, 0x00, 0x26, 0x9a, 0x12, 0x15, - 0xa0, 0x01, 0x00, 0x00, 0x26, 0x00, 0x1c, 0x15, 0x0c, 0x19, 0x15, 0x10, 0x19, 0x18, 0x0d, 0x72, - 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x15, 0x02, 0x16, 0x46, - 0x16, 0xd4, 0x0e, 0x16, 0x84, 0x09, 0x26, 0xf4, 0x0f, 0x26, 0xf6, 0x07, 0x1c, 0x18, 0x14, 0x75, - 0x6e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x70, 0x75, 0x72, 0x63, 0x68, - 0x61, 0x73, 0x65, 0x18, 0x0b, 0x44, 0x69, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x66, 0x69, 0x74, - 0x16, 0x00, 0x16, 0x44, 0x18, 0x14, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x69, 0x7a, 0x65, - 0x64, 0x20, 0x70, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x18, 0x0b, 0x44, 0x69, 0x64, 0x20, - 0x6e, 0x6f, 0x74, 0x20, 0x66, 0x69, 0x74, 0x11, 0x11, 0x00, 0x26, 0xba, 0x13, 0x15, 0xa0, 0x01, - 0x00, 0x00, 0x16, 0x88, 0x23, 0x16, 0x46, 0x26, 0x08, 0x00, 0x28, 0x28, 0x44, 0x75, 0x63, 0x6b, - 0x44, 0x42, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x76, 0x31, 0x2e, 0x33, 0x2e, - 0x30, 0x20, 0x28, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x37, 0x31, 0x63, 0x35, 0x63, 0x30, 0x37, - 0x63, 0x64, 0x64, 0x29, 0x00, 0xd8, 0x01, 0x00, 0x00, 0x50, 0x41, 0x52, 0x31, - }; - // Sources backed by the embedded bloom-filter fixture - std::vector const fixture(bloom_filter_alignment_parquet.begin(), - bloom_filter_alignment_parquet.end()); - std::vector> file_buffers(num_sources, fixture); - - auto inputs = multifile_inputs(build_source_info(file_buffers)); - - // An equality predicate makes the "r_reason_desc" column bloom-eligible - auto literal_value = cudf::string_scalar("Did not like the color", true, stream); - auto literal = cudf::ast::literal(literal_value); - auto col_ref = cudf::ast::column_name_reference("r_reason_desc"); - auto filter = cudf::ast::operation(cudf::ast::ast_operator::EQUAL, col_ref, literal); - - auto options = cudf::io::parquet_reader_options::builder().filter(filter).build(); - auto const reader = std::make_unique( - inputs.footer_byte_spans, options); - - auto const input_row_group_indices = reader->all_row_groups(options); - ASSERT_EQ(input_row_group_indices.size(), num_sources); - - auto const bloom_ranges_and_source_map = - reader->bloom_filters_byte_ranges(input_row_group_indices, options); - auto const& [bloom_byte_ranges, bloom_source_map] = bloom_ranges_and_source_map; - - // One byte range per (row group, bloom filter column) pair across all sources. Only - // `r_reason_desc` carries an equality predicate, so there is a single bloom filter column. - auto constexpr num_bloom_filter_columns = 1; - auto const total_row_groups = - std::accumulate(input_row_group_indices.begin(), - input_row_group_indices.end(), - std::size_t{0}, - [](auto sum, auto const& rgs) { return sum + rgs.size(); }); - ASSERT_EQ(bloom_byte_ranges.size(), total_row_groups * num_bloom_filter_columns); - ASSERT_EQ(bloom_byte_ranges.size(), bloom_source_map.size()); - std::vector expected_source_map(num_sources); - std::iota(expected_source_map.begin(), expected_source_map.end(), 0); - EXPECT_EQ(bloom_source_map, expected_source_map); - EXPECT_TRUE(std::none_of(bloom_byte_ranges.begin(), bloom_byte_ranges.end(), [](auto const& r) { - return r.is_empty(); - })); - - auto const ranges_per_source = - group_byte_ranges_by_source(bloom_ranges_and_source_map, inputs.datasources.size()); - - // Bloom filters must be fetched with `fetch_bloom_filters_to_device` - [[maybe_unused]] auto [bloom_buffers, bloom_data_per_source] = - cudf::io::parquet::fetch_bloom_filters_to_device( - inputs.datasource_refs, ranges_per_source, stream, mr); - for (auto const& per_source : bloom_data_per_source) { - ASSERT_EQ(per_source.size(), 1); - } - - // Flatten the per-source bloom filter data in source order - std::vector> bloom_filter_data; - for (auto const& per_source : bloom_data_per_source) { - bloom_filter_data.insert(bloom_filter_data.end(), per_source.begin(), per_source.end()); - } - auto const bloom_filtered = reader->filter_row_groups_with_bloom_filters( - bloom_filter_data, input_row_group_indices, options, stream); - - // Shouldn't filter out any RG, since the queried value is present in every source. - EXPECT_EQ(bloom_filtered, input_row_group_indices); -} - TEST_F(HybridScanMultifileFiltersTest, BuildAllTrueRowMask) { using T = uint64_t; diff --git a/python/cudf/cudf/tests/input_output/test_parquet.py b/python/cudf/cudf/tests/input_output/test_parquet.py index 5c4bbb57abba..b3271c9106a6 100644 --- a/python/cudf/cudf/tests/input_output/test_parquet.py +++ b/python/cudf/cudf/tests/input_output/test_parquet.py @@ -21,6 +21,8 @@ from packaging import version from pyarrow import parquet as pq +import pylibcudf as plc + import cudf from cudf.io.parquet import ( ParquetDatasetWriter, @@ -4768,45 +4770,90 @@ def memory_resource(request): rmm.mr.set_current_device_resource(current_mr) +def _read_parquet_with_pruning_metadata( + sources, filter_expression, columns=None +): + options = plc.io.parquet.ParquetReaderOptions.builder( + plc.io.SourceInfo(sources) + ).build() + if columns is not None: + options.set_column_names(columns) + options.set_filter(filter_expression) + return plc.io.parquet.read_parquet(options) + + @pytest.mark.parametrize("columns", [["r_reason_desc"], None]) -def test_parquet_bloom_filters_alignment(datadir, columns, memory_resource): +@pytest.mark.parametrize( + "value,expected_row_groups", + [ + ("Did not like the color", 2), + ("not-in-this-file", 0), + ], +) +def test_parquet_bloom_filters_alignment_and_pruning( + datadir, columns, value, expected_row_groups, memory_resource +): fname = datadir / "bloom_filter_alignment.parquet" - filters = [("r_reason_desc", "==", "Did not like the color")] + sources = [fname, fname] + filters = [("r_reason_desc", "==", value)] # Read expected table using pyarrow - expected = pq.read_table(fname, columns=columns, filters=filters) + expected = pq.read_table(sources, columns=columns, filters=filters) - # Read with cudf using the memory resource from fixture - read = cudf.read_parquet( - fname, columns=columns, filters=filters - ).to_arrow() + # Read with pylibcudf to inspect row group pruning + filter_expression = plc.expressions.Operation( + plc.expressions.ASTOperator.EQUAL, + plc.expressions.ColumnNameReference("r_reason_desc"), + plc.expressions.Literal(plc.Scalar.from_arrow(pa.scalar(value))), + ) + result = _read_parquet_with_pruning_metadata( + sources, filter_expression, columns + ) - assert_eq(expected, read) + # Verify bloom pruning and table contents + assert result.num_input_row_groups == 2 + assert result.num_row_groups_after_stats_filter == 2 + assert result.num_row_groups_after_bloom_filter == expected_row_groups + assert_eq(expected, cudf.DataFrame.from_pylibcudf(result).to_arrow()) @pytest.mark.parametrize( - "predicate", + "filename", + [ + "data_index_bloom_encoding_stats.parquet", + "data_index_bloom_encoding_with_length.parquet", + ], + ids=["length-absent", "length-present"], +) +@pytest.mark.parametrize( + "value,expected_row_groups", [ - [("String", "==", "Hello")], # present, row group kept - [("String", "==", "not-in-this-file")], # absent, row group pruned + ("Hello", 1), + ("not-in-this-file", 0), ], ) -def test_parquet_bloom_filters_length_absent(datadir, predicate): - # Reader must recover the bitset in case of absent length field in the header. +def test_parquet_bloom_filters_length( + datadir, filename, value, expected_row_groups +): # Source: apache/parquet-testing (Apache-2.0) - fname_len_absent = datadir / "data_index_bloom_encoding_stats.parquet" - fname_with_len = datadir / "data_index_bloom_encoding_with_length.parquet" - - expected = pq.read_table(fname_len_absent, filters=predicate) - read_len_absent = cudf.read_parquet( - fname_len_absent, filters=predicate - ).to_arrow() - read_with_len = cudf.read_parquet( - fname_with_len, filters=predicate - ).to_arrow() - - assert_eq(expected, read_len_absent) - assert_eq(read_with_len, read_len_absent) + fname = datadir / filename + filters = [("String", "==", value)] + + # Read expected table using pyarrow + expected = pq.read_table(fname, filters=filters) + + filter_expression = plc.expressions.Operation( + plc.expressions.ASTOperator.EQUAL, + plc.expressions.ColumnNameReference("String"), + plc.expressions.Literal(plc.Scalar.from_arrow(pa.scalar(value))), + ) + + # Verify bloom pruning and table contents + result = _read_parquet_with_pruning_metadata([fname], filter_expression) + assert result.num_input_row_groups == 1 + assert result.num_row_groups_after_stats_filter == 1 + assert result.num_row_groups_after_bloom_filter == expected_row_groups + assert_eq(expected, cudf.DataFrame.from_pylibcudf(result).to_arrow()) @pytest.mark.parametrize( From 969cbac963f39f6b446b05b0d9d7a12523c10817 Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Thu, 6 Aug 2026 10:50:39 +0200 Subject: [PATCH 12/13] Enhance Parquet testing with bloom filter alignment and pruning - Added a new test for verifying bloom filter alignment in Parquet files, utilizing parameterized columns. - Updated the existing test for bloom filters to focus on pruning across multiple sources, improving clarity and functionality. - Removed unnecessary column parameters from the `_read_parquet_with_pruning_metadata` function to streamline the code. --- .../cudf/tests/input_output/test_parquet.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/python/cudf/cudf/tests/input_output/test_parquet.py b/python/cudf/cudf/tests/input_output/test_parquet.py index b3271c9106a6..228b46b668e3 100644 --- a/python/cudf/cudf/tests/input_output/test_parquet.py +++ b/python/cudf/cudf/tests/input_output/test_parquet.py @@ -4770,19 +4770,30 @@ def memory_resource(request): rmm.mr.set_current_device_resource(current_mr) -def _read_parquet_with_pruning_metadata( - sources, filter_expression, columns=None -): +@pytest.mark.parametrize("columns", [["r_reason_desc"], None]) +def test_parquet_bloom_filters_alignment(datadir, columns, memory_resource): + fname = datadir / "bloom_filter_alignment.parquet" + filters = [("r_reason_desc", "==", "Did not like the color")] + + # Read expected table using pyarrow + expected = pq.read_table(fname, columns=columns, filters=filters) + + # Read with cudf using the memory resource from fixture + read = cudf.read_parquet( + fname, columns=columns, filters=filters + ).to_arrow() + + assert_eq(expected, read) + + +def _read_parquet_with_pruning_metadata(sources, filter_expression): options = plc.io.parquet.ParquetReaderOptions.builder( plc.io.SourceInfo(sources) ).build() - if columns is not None: - options.set_column_names(columns) options.set_filter(filter_expression) return plc.io.parquet.read_parquet(options) -@pytest.mark.parametrize("columns", [["r_reason_desc"], None]) @pytest.mark.parametrize( "value,expected_row_groups", [ @@ -4790,15 +4801,15 @@ def _read_parquet_with_pruning_metadata( ("not-in-this-file", 0), ], ) -def test_parquet_bloom_filters_alignment_and_pruning( - datadir, columns, value, expected_row_groups, memory_resource +def test_parquet_bloom_filters_pruning_multisource( + datadir, value, expected_row_groups ): fname = datadir / "bloom_filter_alignment.parquet" sources = [fname, fname] filters = [("r_reason_desc", "==", value)] # Read expected table using pyarrow - expected = pq.read_table(sources, columns=columns, filters=filters) + expected = pq.read_table(sources, filters=filters) # Read with pylibcudf to inspect row group pruning filter_expression = plc.expressions.Operation( @@ -4806,9 +4817,7 @@ def test_parquet_bloom_filters_alignment_and_pruning( plc.expressions.ColumnNameReference("r_reason_desc"), plc.expressions.Literal(plc.Scalar.from_arrow(pa.scalar(value))), ) - result = _read_parquet_with_pruning_metadata( - sources, filter_expression, columns - ) + result = _read_parquet_with_pruning_metadata(sources, filter_expression) # Verify bloom pruning and table contents assert result.num_input_row_groups == 2 From eae08943086073e415e2c98a140b8359928db0fa Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Tue, 11 Aug 2026 09:09:20 +0200 Subject: [PATCH 13/13] Let `test_parquet` use `read_parquet` only --- .../cudf/tests/input_output/test_parquet.py | 70 ++----------------- 1 file changed, 5 insertions(+), 65 deletions(-) diff --git a/python/cudf/cudf/tests/input_output/test_parquet.py b/python/cudf/cudf/tests/input_output/test_parquet.py index 228b46b668e3..1fd17d910d0f 100644 --- a/python/cudf/cudf/tests/input_output/test_parquet.py +++ b/python/cudf/cudf/tests/input_output/test_parquet.py @@ -21,8 +21,6 @@ from packaging import version from pyarrow import parquet as pq -import pylibcudf as plc - import cudf from cudf.io.parquet import ( ParquetDatasetWriter, @@ -4786,46 +4784,6 @@ def test_parquet_bloom_filters_alignment(datadir, columns, memory_resource): assert_eq(expected, read) -def _read_parquet_with_pruning_metadata(sources, filter_expression): - options = plc.io.parquet.ParquetReaderOptions.builder( - plc.io.SourceInfo(sources) - ).build() - options.set_filter(filter_expression) - return plc.io.parquet.read_parquet(options) - - -@pytest.mark.parametrize( - "value,expected_row_groups", - [ - ("Did not like the color", 2), - ("not-in-this-file", 0), - ], -) -def test_parquet_bloom_filters_pruning_multisource( - datadir, value, expected_row_groups -): - fname = datadir / "bloom_filter_alignment.parquet" - sources = [fname, fname] - filters = [("r_reason_desc", "==", value)] - - # Read expected table using pyarrow - expected = pq.read_table(sources, filters=filters) - - # Read with pylibcudf to inspect row group pruning - filter_expression = plc.expressions.Operation( - plc.expressions.ASTOperator.EQUAL, - plc.expressions.ColumnNameReference("r_reason_desc"), - plc.expressions.Literal(plc.Scalar.from_arrow(pa.scalar(value))), - ) - result = _read_parquet_with_pruning_metadata(sources, filter_expression) - - # Verify bloom pruning and table contents - assert result.num_input_row_groups == 2 - assert result.num_row_groups_after_stats_filter == 2 - assert result.num_row_groups_after_bloom_filter == expected_row_groups - assert_eq(expected, cudf.DataFrame.from_pylibcudf(result).to_arrow()) - - @pytest.mark.parametrize( "filename", [ @@ -4834,35 +4792,17 @@ def test_parquet_bloom_filters_pruning_multisource( ], ids=["length-absent", "length-present"], ) -@pytest.mark.parametrize( - "value,expected_row_groups", - [ - ("Hello", 1), - ("not-in-this-file", 0), - ], -) -def test_parquet_bloom_filters_length( - datadir, filename, value, expected_row_groups -): +@pytest.mark.parametrize("value", ["Hello", "not-in-this-file"]) +def test_parquet_bloom_filters_length(datadir, filename, value): + # Header may omit the bloom filter length. # Source: apache/parquet-testing (Apache-2.0) fname = datadir / filename filters = [("String", "==", value)] - # Read expected table using pyarrow expected = pq.read_table(fname, filters=filters) + read = cudf.read_parquet(fname, filters=filters).to_arrow() - filter_expression = plc.expressions.Operation( - plc.expressions.ASTOperator.EQUAL, - plc.expressions.ColumnNameReference("String"), - plc.expressions.Literal(plc.Scalar.from_arrow(pa.scalar(value))), - ) - - # Verify bloom pruning and table contents - result = _read_parquet_with_pruning_metadata([fname], filter_expression) - assert result.num_input_row_groups == 1 - assert result.num_row_groups_after_stats_filter == 1 - assert result.num_row_groups_after_bloom_filter == expected_row_groups - assert_eq(expected, cudf.DataFrame.from_pylibcudf(result).to_arrow()) + assert_eq(expected, read) @pytest.mark.parametrize(