-
Notifications
You must be signed in to change notification settings - Fork 129
feat(parquet): add statistics-based row group filtering #956
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ | |
| #include "iceberg/arrow_c_data_guard_internal.h" | ||
| #include "iceberg/arrow_c_data_util_internal.h" | ||
| #include "iceberg/data/delete_filter.h" | ||
| #include "iceberg/expression/binder.h" | ||
| #include "iceberg/file_reader.h" | ||
| #include "iceberg/manifest/manifest_entry.h" | ||
| #include "iceberg/schema.h" | ||
|
|
@@ -164,10 +165,21 @@ class FileScanTaskReader::Impl { | |
| "Data file size must not be negative: {}", | ||
| data_file->file_size_in_bytes); | ||
|
|
||
| auto filter = task.residual_filter(); | ||
| if (filter) { | ||
| ICEBERG_ASSIGN_OR_RAISE(auto is_bound, IsBoundVisitor::IsBound(filter)); | ||
| if (!is_bound) { | ||
| ICEBERG_ASSIGN_OR_RAISE( | ||
| filter, | ||
| Binder::Bind(*table_schema_, filter, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please bind against the read/projection schema, not To align with Java, build the read schema from |
||
| properties_.Get(ReaderProperties::kFilterCaseSensitive))); | ||
| } | ||
| } | ||
|
|
||
| if (task.delete_files().empty()) { | ||
| auto options = MakeReaderOptions( | ||
| *data_file, io_, projected_schema_, task.residual_filter(), name_mapping_, | ||
| properties_, data_file->first_row_id, data_file->data_sequence_number); | ||
| *data_file, io_, projected_schema_, filter, name_mapping_, properties_, | ||
| data_file->first_row_id, data_file->data_sequence_number); | ||
| ICEBERG_ASSIGN_OR_RAISE( | ||
| auto reader, ReaderFactoryRegistry::Open(data_file->file_format, options)); | ||
| return MakeArrowArrayStream(std::move(reader)); | ||
|
|
@@ -191,9 +203,9 @@ class FileScanTaskReader::Impl { | |
| ProjectionContext::Make(*required_schema, *projected_schema_, | ||
| project_batch_function)); | ||
|
|
||
| auto options = MakeReaderOptions( | ||
| *data_file, io_, required_schema, task.residual_filter(), name_mapping_, | ||
| properties_, data_file->first_row_id, data_file->data_sequence_number); | ||
| auto options = MakeReaderOptions(*data_file, io_, required_schema, filter, | ||
| name_mapping_, properties_, data_file->first_row_id, | ||
| data_file->data_sequence_number); | ||
| ICEBERG_ASSIGN_OR_RAISE(auto reader, | ||
| ReaderFactoryRegistry::Open(data_file->file_format, options)); | ||
|
|
||
|
|
@@ -207,6 +219,7 @@ class FileScanTaskReader::Impl { | |
| Impl(Options options, DeleteFilter::FieldLookup field_lookup, | ||
| std::shared_ptr<DeleteCounter> delete_counter) | ||
| : io_(std::move(options.io)), | ||
| table_schema_(std::move(options.table_schema)), | ||
| schemas_(std::move(options.schemas)), | ||
| projected_schema_(std::move(options.projected_schema)), | ||
| name_mapping_(std::move(options.name_mapping)), | ||
|
|
@@ -215,6 +228,7 @@ class FileScanTaskReader::Impl { | |
| delete_counter_(std::move(delete_counter)) {} | ||
|
|
||
| std::shared_ptr<FileIO> io_; | ||
| std::shared_ptr<Schema> table_schema_; | ||
| std::vector<std::shared_ptr<Schema>> schemas_; | ||
| std::shared_ptr<Schema> projected_schema_; | ||
| std::shared_ptr<NameMapping> name_mapping_; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,6 +80,11 @@ class ICEBERG_EXPORT ReaderProperties : public ConfigBase<ReaderProperties> { | |
| /// Only the Parquet reader honors this option; other readers ignore it. | ||
| /// Default: false (use 32-bit offset list). | ||
| inline static Entry<bool> kArrowUseLargeList{"read.arrow.use-large-list", false}; | ||
| /// \brief Use footer statistics to prune Parquet row groups. | ||
| inline static Entry<bool> kParquetRowGroupFilter{ | ||
| "read.parquet.row-group-filter.enabled", true}; | ||
| /// \brief Case sensitivity when binding unbound filter references. | ||
| inline static Entry<bool> kFilterCaseSensitive{"read.filter.case-sensitive", true}; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Please add explicit fields: struct ReaderOptions {
...
bool filter_case_sensitive = true;
};
class FileScanTaskReader {
public:
struct Options {
...
bool filter_case_sensitive = true;
};
};Source it from the scan and pass it through: FileScanTaskReader::Make({
.io = scan->io(),
.table_schema = scan->table()->schema(),
.schemas = historical_schemas,
.projected_schema = *scan->schema(),
.filter_case_sensitive = scan->is_case_sensitive(),
});
Binder::Bind(*table_schema_, filter, options_.filter_case_sensitive);
|
||
| /// \brief Skip GenericDatum in Avro reader for better performance. | ||
| /// When true, decode directly from Avro to Arrow without GenericDatum intermediate. | ||
| /// Default: true (skip GenericDatum for better performance). | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A normal scan can pass
Trueas its residual, and constants can also be nested inAnd/Or.IsBoundVisitorcurrently returns an error for both constants, so these expressions fail before reading.Java avoids this because its
IsBoundVisitorreturnsnullfor constants, andBinder.BindVisitorhandles constants directly. Please mirror that behavior. Simply returningtruefor constants is not sufficient: forAnd(True, unbound), that would mark the whole expression as bound and leave the unbound predicate unbound.One concrete fix is a tri-state result:
Then
Binder::Bind()handles constants and unbound predicates. Add tests forTrue,False,And(True, pred), andOr(False, pred).