Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0a76858
Hybrid scan avoids null masks for REQUIRED unless page pruning
mhaseeb123 Aug 25, 2026
cd9563f
Only mark buffers with pruned pages
mhaseeb123 Aug 25, 2026
7167cc4
Simplify
mhaseeb123 Aug 25, 2026
9ed5801
Minor fix
mhaseeb123 Aug 25, 2026
1af5913
Minor
mhaseeb123 Aug 25, 2026
0d06e6f
Minor
mhaseeb123 Aug 25, 2026
12fbd21
Merge branch 'codex/hybrid-scan-required-null-mask' of https://github…
mhaseeb123 Aug 25, 2026
3ce9db0
Hybrid scan constructor that move in footers
mhaseeb123 Aug 25, 2026
1e9df79
Improve
mhaseeb123 Aug 25, 2026
75d7141
bug fixing
mhaseeb123 Aug 25, 2026
b0e8d47
Merge remote-tracking branch 'origin/codex/hybrid-scan-required-null-…
mhaseeb123 Aug 25, 2026
8da4ba9
style fix
mhaseeb123 Aug 25, 2026
3594c05
Avoid multiple page index setups
mhaseeb123 Aug 25, 2026
43f9a75
Merge branch 'main' into fea/hybrid-scan-footer-move-constructor
mhaseeb123 Aug 25, 2026
265610c
Merge branch 'main' into fea/hybrid-scan-footer-move-constructor
mhaseeb123 Aug 25, 2026
dc9b533
Address comments from @vuule
mhaseeb123 Aug 26, 2026
8dbdfb3
Simplify
mhaseeb123 Aug 26, 2026
80dde76
Simplify
mhaseeb123 Aug 26, 2026
5347f61
Apply suggestion from @coderabbitai
mhaseeb123 Aug 26, 2026
64a4137
Minor
mhaseeb123 Aug 26, 2026
46588d3
Fix
mhaseeb123 Aug 26, 2026
5cc15d6
Simplify
mhaseeb123 Aug 26, 2026
afb797d
Minor simplfication
mhaseeb123 Aug 26, 2026
ec01a02
Merge branch 'main' into fea/hybrid-scan-footer-move-constructor
mhaseeb123 Aug 26, 2026
7a78a0c
Minor
mhaseeb123 Aug 26, 2026
afa5b57
Remove stupid tests
mhaseeb123 Aug 26, 2026
e8b384f
style
mhaseeb123 Aug 26, 2026
1851633
Merge branch 'main' into fea/hybrid-scan-footer-move-constructor
mhaseeb123 Aug 27, 2026
dff3755
Merge branch 'main' into fea/hybrid-scan-footer-move-constructor
mhaseeb123 Aug 28, 2026
2386313
Merge branch 'main' into fea/hybrid-scan-footer-move-constructor
mhaseeb123 Aug 28, 2026
f0bfb56
clang format for the billionth time
mhaseeb123 Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions cpp/include/cudf/io/experimental/hybrid_scan_multifile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class hybrid_scan_multifile {
/**
* @brief Constructor for the multi-file experimental Parquet reader
*
* @throws std::invalid_argument if no sources are provided
*
* @param footer_bytes Host span of Parquet file footer byte spans, one per source
* @param options Parquet reader options
*/
Expand All @@ -70,12 +72,25 @@ class hybrid_scan_multifile {
/**
* @brief Constructor for the multi-file experimental Parquet reader
*
* @throws std::invalid_argument if no sources are provided
*
* @param parquet_metadata Host span of pre-populated Parquet file metadata, one per source
* @param options Parquet reader options
*/
explicit hybrid_scan_multifile(cudf::host_span<FileMetaData const> parquet_metadata,
parquet_reader_options const& options);

/**
* @brief Constructor that takes ownership of pre-populated Parquet file metadata
*
* @throws std::invalid_argument if no sources are provided
*
* @param parquet_metadata Pre-populated Parquet file metadata, one per source
* @param options Parquet reader options
*/
explicit hybrid_scan_multifile(std::vector<FileMetaData>&& parquet_metadata,
parquet_reader_options const& options);

/**
* @brief Destructor for the multi-file experimental Parquet reader
*/
Expand Down
4 changes: 2 additions & 2 deletions cpp/src/io/parquet/experimental/hybrid_scan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ hybrid_scan_metadata::hybrid_scan_metadata(cudf::host_span<uint8_t const> footer
: _metadata{std::make_shared<detail::aggregate_reader_metadata>(
std::vector<cudf::host_span<uint8_t const>>{footer_bytes},
options.is_enabled_use_arrow_schema(),
options.get_column_names().has_value() and options.is_enabled_allow_mismatched_pq_schemas())}
options.is_enabled_allow_mismatched_pq_schemas())}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simply enable this regardless of column selection. Hybrid scan selects columns later (filter/payload/all) later on anyway so no point checking things here.

{
}

Expand All @@ -27,7 +27,7 @@ hybrid_scan_metadata::hybrid_scan_metadata(FileMetaData const& parquet_metadata,
: _metadata{std::make_shared<detail::aggregate_reader_metadata>(
std::vector<FileMetaData>{parquet_metadata},
options.is_enabled_use_arrow_schema(),
options.get_column_names().has_value() and options.is_enabled_allow_mismatched_pq_schemas())}
options.is_enabled_allow_mismatched_pq_schemas())}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

{
}

Expand Down
38 changes: 14 additions & 24 deletions cpp/src/io/parquet/experimental/hybrid_scan_helpers.cpp

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleting the hybrid initialize_internals also opts the hybrid reader into base behavior it previously skipped: the cross-source "All sources must have the same schema" / column-count checks, the cross-source REQUIREDOPTIONAL promotion for the first source, and unconditional erasure of ARROW_SCHEMA_KEY (previously only when use_arrow_schema was set).

Is this an intended change in behavior?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed some commits yesterday. TLDR; both readers now only propagate REQUIRED->OPTIONAL in initialize_internals when mismatched schema isn't allowed. Otherwise, we do it in map_columns function during column selection so we only really do it for the columns we are actually reading. Better for hybrid scan as well since we do read different sets of columns in all/payload/filter cases.

Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,6 @@ using text::byte_range_info;

namespace {

// Construct a vector of FileMetaData from the input footer bytes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible regression point. Use the common parallel materializer from the base class.

[[nodiscard]] std::vector<FileMetaData> parquet_metadatas_from_footer_bytes(
cudf::host_span<cudf::host_span<uint8_t const> const> footer_bytes)
{
std::vector<FileMetaData> parquet_metadatas;
parquet_metadatas.reserve(footer_bytes.size());
std::transform(footer_bytes.begin(),
footer_bytes.end(),
std::back_inserter(parquet_metadatas),
[](auto const& footer_bytes) {
metadata parsed_metadata{footer_bytes};
return FileMetaData{std::move(parsed_metadata)};
});
return parquet_metadatas;
}

// Construct a vector of all row group indices from the input vectors
[[nodiscard]] auto all_row_group_indices(
std::span<std::vector<cudf::size_type> const> row_group_indices)
Expand Down Expand Up @@ -132,25 +116,31 @@ aggregate_reader_metadata::aggregate_reader_metadata(
cudf::host_span<cudf::host_span<uint8_t const> const> footer_bytes,
bool use_arrow_schema,
bool has_cols_from_mismatched_srcs)
: aggregate_reader_metadata_base(parquet_metadatas_from_footer_bytes(footer_bytes),
use_arrow_schema,
has_cols_from_mismatched_srcs)
: aggregate_reader_metadata(
parquet::detail::parallel_construct_metadatas(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reuses the base class's parallel footer(s) reader.

footer_bytes, [](auto const& bytes) { return FileMetaData{metadata{bytes}}; }),
use_arrow_schema,
has_cols_from_mismatched_srcs)
{
CUDF_EXPECTS(
not footer_bytes.empty(), "At least one source must be provided", std::invalid_argument);
}

aggregate_reader_metadata::aggregate_reader_metadata(
cudf::host_span<FileMetaData const> parquet_metadatas,
bool use_arrow_schema,
bool has_cols_from_mismatched_srcs)
: aggregate_reader_metadata_base(
: aggregate_reader_metadata(
std::vector<FileMetaData>{parquet_metadatas.begin(), parquet_metadatas.end()},
use_arrow_schema,
has_cols_from_mismatched_srcs)
{
CUDF_EXPECTS(
not parquet_metadatas.empty(), "At least one source must be provided", std::invalid_argument);
}

aggregate_reader_metadata::aggregate_reader_metadata(std::vector<FileMetaData>&& parquet_metadatas,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only constructor that calls into base. Others just call this one

bool use_arrow_schema,
bool has_cols_from_mismatched_srcs)
: aggregate_reader_metadata_base(
std::move(parquet_metadatas), use_arrow_schema, has_cols_from_mismatched_srcs)
{
}

std::vector<text::byte_range_info> aggregate_reader_metadata::page_index_byte_ranges() const
Expand Down
17 changes: 17 additions & 0 deletions cpp/src/io/parquet/experimental/hybrid_scan_helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class aggregate_reader_metadata : public aggregate_reader_metadata_base {
/**
* @brief Constructor for aggregate_reader_metadata
*
* @throws std::invalid_argument if no sources are provided
*
* @param footer_bytes Host span of Parquet file footer buffer bytes, one per source
* @param use_arrow_schema Whether to use Arrow schema
* @param has_cols_from_mismatched_srcs Whether to have columns from mismatched sources
Expand All @@ -92,6 +94,8 @@ class aggregate_reader_metadata : public aggregate_reader_metadata_base {
/**
* @brief Constructor for aggregate_reader_metadata
*
* @throws std::invalid_argument if no sources are provided
*
* @param parquet_metadatas Host span of pre-populated Parquet file metadata, one per source
* @param use_arrow_schema Whether to use Arrow schema
* @param has_cols_from_mismatched_srcs Whether to have columns from mismatched sources
Expand All @@ -100,6 +104,19 @@ class aggregate_reader_metadata : public aggregate_reader_metadata_base {
bool use_arrow_schema,
bool has_cols_from_mismatched_srcs);

/**
* @brief Constructor that takes ownership of pre-populated Parquet file metadata
*
* @throws std::invalid_argument if no sources are provided
*
* @param parquet_metadatas Pre-populated Parquet file metadata, one per source
* @param use_arrow_schema Whether to use Arrow schema
* @param has_cols_from_mismatched_srcs Whether to have columns from mismatched sources
*/
aggregate_reader_metadata(std::vector<FileMetaData>&& parquet_metadatas,
bool use_arrow_schema,
bool has_cols_from_mismatched_srcs);

aggregate_reader_metadata(aggregate_reader_metadata const&) = delete;
aggregate_reader_metadata& operator=(aggregate_reader_metadata const&) = delete;
aggregate_reader_metadata(aggregate_reader_metadata&&) = default;
Expand Down
27 changes: 20 additions & 7 deletions cpp/src/io/parquet/experimental/hybrid_scan_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ hybrid_scan_reader_impl::hybrid_scan_reader_impl(
cudf::host_span<cudf::host_span<uint8_t const> const> footer_bytes,
parquet_reader_options const& options)
{
_metadata = std::make_shared<aggregate_reader_metadata>(
footer_bytes, options.is_enabled_use_arrow_schema(), has_cols_from_mismatched_sources(options));
_metadata =
std::make_shared<aggregate_reader_metadata>(footer_bytes,
options.is_enabled_use_arrow_schema(),
options.is_enabled_allow_mismatched_pq_schemas());

_extended_metadata = static_cast<aggregate_reader_metadata*>(_metadata.get());
}
Expand All @@ -192,7 +194,17 @@ hybrid_scan_reader_impl::hybrid_scan_reader_impl(
_metadata =
std::make_shared<aggregate_reader_metadata>(parquet_metadatas,
options.is_enabled_use_arrow_schema(),
has_cols_from_mismatched_sources(options));
options.is_enabled_allow_mismatched_pq_schemas());
_extended_metadata = static_cast<aggregate_reader_metadata*>(_metadata.get());
}

hybrid_scan_reader_impl::hybrid_scan_reader_impl(std::vector<FileMetaData>&& parquet_metadatas,
parquet_reader_options const& options)
{
_metadata =
std::make_shared<aggregate_reader_metadata>(std::move(parquet_metadatas),
options.is_enabled_use_arrow_schema(),
options.is_enabled_allow_mismatched_pq_schemas());
_extended_metadata = static_cast<aggregate_reader_metadata*>(_metadata.get());
}

Expand Down Expand Up @@ -285,12 +297,13 @@ void hybrid_scan_reader_impl::select_columns(read_columns_mode read_columns_mode
// Save original output-buffer schema for reuse across materialization passes.
_original_output_buffers_template = make_empty_like_column_buffers(_output_buffers);

// Initialize mutable output-buffer template for this materialization pass.
reset_output_buffers_template();
// Initialize mutable output buffers for this materialization pass.
reset_output_buffers();
}

void hybrid_scan_reader_impl::reset_output_buffers_template()
void hybrid_scan_reader_impl::reset_output_buffers()
{
_output_buffers = make_empty_like_column_buffers(_original_output_buffers_template);
_output_buffers_template = make_empty_like_column_buffers(_original_output_buffers_template);
Comment thread
mhaseeb123 marked this conversation as resolved.
}

Expand Down Expand Up @@ -336,7 +349,7 @@ void hybrid_scan_reader_impl::prepare_materialization(read_columns_mode read_col
reset_internal_state();
initialize_options(options, num_sources, stream, mr);
select_columns(read_columns_mode, options);
reset_output_buffers_template();
reset_output_buffers();
}

std::vector<std::vector<cudf::size_type>>
Expand Down
13 changes: 11 additions & 2 deletions cpp/src/io/parquet/experimental/hybrid_scan_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ class hybrid_scan_reader_impl : public parquet::detail::reader_impl {
explicit hybrid_scan_reader_impl(cudf::host_span<FileMetaData const> parquet_metadatas,
parquet_reader_options const& options);

/**
* @brief Constructor that takes ownership of pre-populated Parquet file metadata
*
* @param parquet_metadatas Pre-populated Parquet file metadata, one per source
* @param options Parquet reader options
*/
explicit hybrid_scan_reader_impl(std::vector<FileMetaData>&& parquet_metadatas,
parquet_reader_options const& options);

/**
* @brief Constructor that takes shared ownership of pre-parsed Parquet metadata
*
Expand Down Expand Up @@ -400,9 +409,9 @@ class hybrid_scan_reader_impl : public parquet::detail::reader_impl {
void mark_buffers_nullable_for_pruned_pages();

/**
* @brief Initialize the mutable output-buffer template for this materialization
* @brief Reset the output buffers and their template from the original selected-columns schema
*/
void reset_output_buffers_template();
void reset_output_buffers();

/**
* @brief Select the columns to be read based on the read mode
Expand Down
6 changes: 6 additions & 0 deletions cpp/src/io/parquet/experimental/hybrid_scan_multifile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ hybrid_scan_multifile::hybrid_scan_multifile(cudf::host_span<FileMetaData const>
{
}

hybrid_scan_multifile::hybrid_scan_multifile(std::vector<FileMetaData>&& parquet_metadata,
parquet_reader_options const& options)
: _impl{std::make_unique<detail::hybrid_scan_reader_impl>(std::move(parquet_metadata), options)}
{
}

hybrid_scan_multifile::~hybrid_scan_multifile() = default;

std::vector<FileMetaData> hybrid_scan_multifile::parquet_metadatas() const
Expand Down
27 changes: 14 additions & 13 deletions cpp/src/io/parquet/reader_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,19 +413,6 @@ class reader_impl {
_file_itm_data._current_input_pass < _file_itm_data.num_passes();
}

/**

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to be protected. Move to private section

* @brief Check if the user has specified columns from mismatched sources
*
* @param options Reader options
* @return True if the user has specified columns from mismatched sources
*/
[[nodiscard]] bool has_cols_from_mismatched_sources(parquet_reader_options const& options) const
{
return (options.get_column_names().has_value() or
options.get_column_field_ids().has_value()) and
options.is_enabled_allow_mismatched_pq_schemas();
}

/**
* @brief Effective `ignore_missing_columns` policy for column selection
*
Expand All @@ -440,6 +427,20 @@ class reader_impl {
not(has_cols_from_mismatched_sources(options) and _metadata->get_num_sources() > 1);
}

private:
/**
* @brief Check if the user has specified columns from mismatched sources
*
* @param options Reader options
* @return True if the user has specified columns from mismatched sources
*/
[[nodiscard]] bool has_cols_from_mismatched_sources(parquet_reader_options const& options) const
{
return (options.get_column_names().has_value() or
options.get_column_field_ids().has_value()) and
options.is_enabled_allow_mismatched_pq_schemas();
}

protected:
/**
* @brief Check if the user has specified custom row bounds
Expand Down
54 changes: 30 additions & 24 deletions cpp/src/io/parquet/reader_impl_chunking.cu
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <thrust/transform_scan.h>

#include <numeric>
#include <unordered_map>

namespace cudf::io::parquet::detail {

Expand Down Expand Up @@ -419,29 +420,32 @@ void reader_impl::create_global_chunk_info()
auto const num_chunks = row_groups_info.size() * num_input_columns;

// Mapping of input column to page index column
std::vector<size_type> column_mapping;

if (_has_offset_index and not row_groups_info.empty()) {
// use first row group to define mappings (assumes same schema for each file)
auto const& rg = row_groups_info[0];
auto const& columns = _metadata->get_row_group(rg.index, rg.source_index).columns;
column_mapping.resize(num_input_columns);
std::transform(
_input_columns.begin(), _input_columns.end(), column_mapping.begin(), [&](auto const& col) {
// translate schema_idx into something we can use for the page indexes
if (auto it = std::find_if(columns.begin(),
columns.end(),
[&](auto const& col_chunk) {
return col_chunk.schema_idx ==
_metadata->map_schema_index(col.schema_idx,
rg.source_index);
});
it != columns.end()) {
return std::distance(columns.begin(), it);
}
CUDF_FAIL("cannot find column mapping");
});
}
auto column_mappings = std::unordered_map<size_type, std::vector<size_type>>{};

auto const column_mapping_for_source = [&](auto const& rg) -> std::vector<size_type> const& {
auto const [iter, inserted] = column_mappings.try_emplace(rg.source_index);
if (inserted) {
auto const& columns = _metadata->get_row_group(rg.index, rg.source_index).columns;
auto& mapping = iter->second;
mapping.resize(num_input_columns);
std::transform(
_input_columns.begin(), _input_columns.end(), mapping.begin(), [&](auto const& col) {
// translate schema_idx into something we can use for the page indexes
if (auto it = std::find_if(columns.begin(),
columns.end(),
[&](auto const& col_chunk) {
return col_chunk.schema_idx ==
_metadata->map_schema_index(col.schema_idx,
rg.source_index);
});
it != columns.end()) {
return static_cast<size_type>(std::distance(columns.begin(), it));
}
CUDF_FAIL("cannot find column mapping");
});
}
return iter->second;
};
Comment on lines +423 to +448

@mhaseeb123 mhaseeb123 Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create per-source column mappings instead since we could have row groups from different files with column chunks sitting at different positions.

This bug is unrelated to this PR but was discovered by the newly added tests so fixed it here.


// Initialize column chunk information
auto remaining_rows = num_rows;
Expand All @@ -454,6 +458,8 @@ void reader_impl::create_global_chunk_info()
auto row_group_rows =
std::min<size_t>(remaining_rows + adjusted_row_group_rows, row_group.num_rows);

auto const* const column_mapping = _has_offset_index ? &column_mapping_for_source(rg) : nullptr;

// generate ColumnChunkDesc objects for everything to be decoded (all input columns)
for (size_t i = 0; i < num_input_columns; ++i) {
auto col = _input_columns[i];
Expand All @@ -479,7 +485,7 @@ void reader_impl::create_global_chunk_info()

// grab the column_chunk_info for each chunk (if it exists)
column_chunk_info const* const chunk_info =
_has_offset_index ? &rg.column_chunks.value()[column_mapping[i]] : nullptr;
_has_offset_index ? &rg.column_chunks.value()[(*column_mapping)[i]] : nullptr;

chunks.emplace_back(col_meta.total_compressed_size,
nullptr,
Expand Down
Loading
Loading