You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Arrow's ORC reader already supports column projection (reading only selected columns), but lacks row-level predicate pushdown. Currently, filtering rows from ORC files requires:
Reading all rows from selected columns (all stripes)
Applying filters post-read using Arrow compute
This is inefficient for large ORC files where only a small subset of rows match the filter criteria. ORC files store min/max statistics at the stripe level, which can be used to skip entire stripes that cannot contain matching rows, and avoids I/O for data that will be filtered out anyway.
Use Cases
Efficiently query large ORC datasets with selective predicates
Enable predicate pushdown for Iceberg tables stored in ORC format
Match the filtering capabilities already available for Parquet files
Following @raulcd's and @wgtmac's suggestions, here is the detailed plan for ORC predicate pushdown. I will post a consolidated PoC PR (draft) for design review, then work through sub-issues per this plan.
Architecture
The implementation follows Parquet's proven pattern, adapted for ORC semantics:
Query: dataset.to_table(filter=ds.field("id") > 1000)
1. Adapter layer -> boundary between liborc and Arrow via OrcColumnStatistics struct with attributes min/max as Arrow Scalars, plus null and validity info
2. Expression layer -> DeriveFieldGuarantee() builds (field >= min AND field <= max) expressions
3. Filter layer -> FilterStripes() calls SimplifyWithGuarantee() per stripe, drops those with literal(false)
4. Scan layer -> ScanBatchesAsync() passes surviving stripe indices to the reader effectively skipping I/O
5. Post-filter -> Scanner's Acero "filter" node applies the predicate row-by-row on kept stripes
Key design decisions
Encapsulate liborc in the adapter layer
ORC statistics come from liborc::ColumnStatistics which is an external library with its own type hierarchy. We introduce an OrcColumnStatistics struct that converts liborc types into Arrow Scalar values once at the adapter boundary. Then, all downstream filtering code (expression building, stripe pruning) works only with this Arrow-native struct and never depends on liborc.
Lazy Statistics Extraction
The lazy approach only extracts statistics for columns that actually appear in the predicate, and caches the results so subsequent queries against the same fragment don't redo the work. This is the same pattern Parquet uses for row group filtering.
Conservative filtering
When statistics are missing, corrupted, or for unsupported types, include the stripe so that the logic never produces false negatives.
hasNull() + getNumberOfValues() (no direct null count)
Must derive from stripe_rows - num_values; adapter reports -1 (unknown)
Statistics API
parquet::RowGroupMetaData
liborc ColumnStatistics subclasses
Different API, same information
Schema manifest
parquet::arrow::SchemaManifest
ORC type tree via Reader::getType()
Must build field to column mapping
Type support roadmap
Phase
Types
Notes
Initial
INT32, INT64
Core integer types, overflow protection for INT32
Follow-up
DOUBLE, FLOAT
NaN handling required
Follow-up
STRING
Potential truncation in ORC stats
Follow-up
DATE, TIMESTAMP
Unit conversion (days/millis/nanos)
Future
DECIMAL
Scale/precision matching
Future
Bloom filters
Equality predicates
Test plan
Stripe filtering with selective predicates (skip/keep verification)
All comparison and logical operators
NULL handling (IS NULL, IS NOT NULL, all-null stripes)
INT32 overflow protection
Missing/corrupted statistics → conservative include
Multi-stripe files with known per-stripe statistics
Python integration: Expression format, DNF tuple format, column projection
I'll post the consolidated PoC PR shortly for design review. Looking forward to feedback on the approach, particularly around the adapter API surface and the sub-issue/PR granularity.
Thanks for working on this and splitting them into several PRs. I will try to review them when I find time. (I'm now suffering from a flood of DoS from reviewing PRs across multiple projects)
Activity
cbb330 commentedon Feb 22, 2026
Implementation Plan
Following @raulcd's and @wgtmac's suggestions, here is the detailed plan for ORC predicate pushdown. I will post a consolidated PoC PR (draft) for design review, then work through sub-issues per this plan.
Architecture
The implementation follows Parquet's proven pattern, adapted for ORC semantics:
Key design decisions
Encapsulate liborc in the adapter layer
ORC statistics come from
liborc::ColumnStatisticswhich is an external library with its own type hierarchy. We introduce an OrcColumnStatistics struct that converts liborc types into Arrow Scalar values once at the adapter boundary. Then, all downstream filtering code (expression building, stripe pruning) works only with this Arrow-native struct and never depends on liborc.Lazy Statistics Extraction
The lazy approach only extracts statistics for columns that actually appear in the predicate, and caches the results so subsequent queries against the same fragment don't redo the work. This is the same pattern Parquet uses for row group filtering.
Conservative filtering
When statistics are missing, corrupted, or for unsupported types, include the stripe so that the logic never produces false negatives.
Sub-issues
Issues are in order to the layering noted above:
ORCFileReaderOrcFileFragmentwith stripe filtering and predicate pushdownfiltersparameter toorc.read_table()Differences from Parquet
Type support roadmap
Test plan
I'll post the consolidated PoC PR shortly for design review. Looking forward to feedback on the approach, particularly around the adapter API surface and the sub-issue/PR granularity.
cc @wgtmac @raulcd
wgtmac commentedon Feb 26, 2026
Thanks for working on this and splitting them into several PRs. I will try to review them when I find time. (I'm now suffering from a flood of DoS from reviewing PRs across multiple projects)