@@ -24,6 +24,7 @@ use arrow::array::{
2424use arrow:: datatypes:: DataType ;
2525use arrow:: { array:: ArrayRef , datatypes:: SchemaRef , error:: ArrowError } ;
2626use datafusion_common:: { Column , DataFusionError , Result } ;
27+ use datafusion_expr:: Expr ;
2728use datafusion_optimizer:: utils:: split_conjunction;
2829use log:: { debug, trace} ;
2930use parquet:: schema:: types:: ColumnDescriptor ;
@@ -45,8 +46,8 @@ use crate::physical_plan::file_format::parquet::{
4546
4647use super :: metrics:: ParquetFileMetrics ;
4748
48- /// Create a RowSelection that may rule out ranges of rows based on
49- /// parquet page level statistics, if any.
49+ /// A [`PagePruningPredicate`] provides the ability to construct a [`RowSelection`]
50+ /// based on parquet page level statistics, if any
5051///
5152/// For example, given a row group with two column (chunks) for `A`
5253/// and `B` with the following with page level statistics:
@@ -99,94 +100,114 @@ use super::metrics::ParquetFileMetrics;
99100///
100101/// So we can entirely skip rows 0->199 and 250->299 as we know they
101102/// can not contain rows that match the predicate.
102- pub ( crate ) fn build_page_filter (
103- pruning_predicate : Option < & PruningPredicate > ,
104- schema : SchemaRef ,
105- row_groups : & [ usize ] ,
106- file_metadata : & ParquetMetaData ,
107- file_metrics : & ParquetFileMetrics ,
108- ) -> Result < Option < RowSelection > > {
109- // scoped timer updates on drop
110- let _timer_guard = file_metrics. page_index_eval_time . timer ( ) ;
111- let page_index_predicates =
112- extract_page_index_push_down_predicates ( pruning_predicate, schema) ?;
103+ #[ derive( Debug ) ]
104+ pub ( crate ) struct PagePruningPredicate {
105+ predicates : Vec < PruningPredicate > ,
106+ }
113107
114- if page_index_predicates. is_empty ( ) {
115- return Ok ( None ) ;
108+ impl PagePruningPredicate {
109+ /// Create a new [`PagePruningPredicate`]
110+ pub fn try_new ( expr : & Expr , schema : SchemaRef ) -> Result < Self > {
111+ let predicates = split_conjunction ( expr)
112+ . into_iter ( )
113+ . filter_map ( |predicate| match predicate. to_columns ( ) {
114+ Ok ( columns) if columns. len ( ) == 1 => {
115+ match PruningPredicate :: try_new ( predicate. clone ( ) , schema. clone ( ) ) {
116+ Ok ( p) if !p. allways_true ( ) => Some ( Ok ( p) ) ,
117+ _ => None ,
118+ }
119+ }
120+ _ => None ,
121+ } )
122+ . collect :: < Result < Vec < _ > > > ( ) ?;
123+ Ok ( Self { predicates } )
116124 }
117125
118- let groups = file_metadata. row_groups ( ) ;
126+ /// Returns a [`RowSelection`] for the given file
127+ pub fn prune (
128+ & self ,
129+ row_groups : & [ usize ] ,
130+ file_metadata : & ParquetMetaData ,
131+ file_metrics : & ParquetFileMetrics ,
132+ ) -> Result < Option < RowSelection > > {
133+ // scoped timer updates on drop
134+ let _timer_guard = file_metrics. page_index_eval_time . timer ( ) ;
135+ if self . predicates . is_empty ( ) {
136+ return Ok ( None ) ;
137+ }
119138
120- let file_offset_indexes = file_metadata. offset_indexes ( ) ;
121- let file_page_indexes = file_metadata. page_indexes ( ) ;
122- if let ( Some ( file_offset_indexes) , Some ( file_page_indexes) ) =
123- ( file_offset_indexes, file_page_indexes)
124- {
125- let mut row_selections = Vec :: with_capacity ( page_index_predicates. len ( ) ) ;
126- for predicate in page_index_predicates {
127- // `extract_page_index_push_down_predicates` only return predicate with one col.
128- // when building `PruningPredicate`, some single column filter like `abs(i) = 1`
129- // will be rewrite to `lit(true)`, so may have an empty required_columns.
130- if let Some ( & col_id) = predicate. need_input_columns_ids ( ) . iter ( ) . next ( ) {
131- let mut selectors = Vec :: with_capacity ( row_groups. len ( ) ) ;
132- for r in row_groups. iter ( ) {
133- let rg_offset_indexes = file_offset_indexes. get ( * r) ;
134- let rg_page_indexes = file_page_indexes. get ( * r) ;
135- if let ( Some ( rg_page_indexes) , Some ( rg_offset_indexes) ) =
136- ( rg_page_indexes, rg_offset_indexes)
137- {
138- selectors. extend (
139- prune_pages_in_one_row_group (
140- & groups[ * r] ,
141- & predicate,
142- rg_offset_indexes. get ( col_id) ,
143- rg_page_indexes. get ( col_id) ,
144- groups[ * r] . column ( col_id) . column_descr ( ) ,
145- file_metrics,
146- )
147- . map_err ( |e| {
148- ArrowError :: ParquetError ( format ! (
149- "Fail in prune_pages_in_one_row_group: {}" ,
150- e
151- ) )
152- } ) ,
153- ) ;
154- } else {
155- trace ! (
156- "Did not have enough metadata to prune with page indexes, falling back, falling back to all rows" ,
157- ) ;
158- // fallback select all rows
159- let all_selected =
160- vec ! [ RowSelector :: select( groups[ * r] . num_rows( ) as usize ) ] ;
161- selectors. push ( all_selected) ;
139+ let page_index_predicates = & self . predicates ;
140+ let groups = file_metadata. row_groups ( ) ;
141+
142+ let file_offset_indexes = file_metadata. offset_indexes ( ) ;
143+ let file_page_indexes = file_metadata. page_indexes ( ) ;
144+ if let ( Some ( file_offset_indexes) , Some ( file_page_indexes) ) =
145+ ( file_offset_indexes, file_page_indexes)
146+ {
147+ let mut row_selections = Vec :: with_capacity ( page_index_predicates. len ( ) ) ;
148+ for predicate in page_index_predicates {
149+ // `extract_page_index_push_down_predicates` only return predicate with one col.
150+ // when building `PruningPredicate`, some single column filter like `abs(i) = 1`
151+ // will be rewrite to `lit(true)`, so may have an empty required_columns.
152+ if let Some ( & col_id) = predicate. need_input_columns_ids ( ) . iter ( ) . next ( ) {
153+ let mut selectors = Vec :: with_capacity ( row_groups. len ( ) ) ;
154+ for r in row_groups. iter ( ) {
155+ let rg_offset_indexes = file_offset_indexes. get ( * r) ;
156+ let rg_page_indexes = file_page_indexes. get ( * r) ;
157+ if let ( Some ( rg_page_indexes) , Some ( rg_offset_indexes) ) =
158+ ( rg_page_indexes, rg_offset_indexes)
159+ {
160+ selectors. extend (
161+ prune_pages_in_one_row_group (
162+ & groups[ * r] ,
163+ predicate,
164+ rg_offset_indexes. get ( col_id) ,
165+ rg_page_indexes. get ( col_id) ,
166+ groups[ * r] . column ( col_id) . column_descr ( ) ,
167+ file_metrics,
168+ )
169+ . map_err ( |e| {
170+ ArrowError :: ParquetError ( format ! (
171+ "Fail in prune_pages_in_one_row_group: {}" ,
172+ e
173+ ) )
174+ } ) ,
175+ ) ;
176+ } else {
177+ trace ! (
178+ "Did not have enough metadata to prune with page indexes, falling back, falling back to all rows" ,
179+ ) ;
180+ // fallback select all rows
181+ let all_selected =
182+ vec ! [ RowSelector :: select( groups[ * r] . num_rows( ) as usize ) ] ;
183+ selectors. push ( all_selected) ;
184+ }
162185 }
186+ debug ! (
187+ "Use filter and page index create RowSelection {:?} from predicate: {:?}" ,
188+ & selectors,
189+ predicate. predicate_expr( ) ,
190+ ) ;
191+ row_selections
192+ . push ( selectors. into_iter ( ) . flatten ( ) . collect :: < Vec < _ > > ( ) ) ;
163193 }
164- debug ! (
165- "Use filter and page index create RowSelection {:?} from predicate: {:?}" ,
166- & selectors,
167- predicate. predicate_expr( ) ,
168- ) ;
169- row_selections. push ( selectors. into_iter ( ) . flatten ( ) . collect :: < Vec < _ > > ( ) ) ;
170194 }
195+ let final_selection = combine_multi_col_selection ( row_selections) ;
196+ let total_skip = final_selection. iter ( ) . fold ( 0 , |acc, x| {
197+ if x. skip {
198+ acc + x. row_count
199+ } else {
200+ acc
201+ }
202+ } ) ;
203+ file_metrics. page_index_rows_filtered . add ( total_skip) ;
204+ Ok ( Some ( final_selection) )
205+ } else {
206+ Ok ( None )
171207 }
172- let final_selection = combine_multi_col_selection ( row_selections) ;
173- let total_skip =
174- final_selection. iter ( ) . fold (
175- 0 ,
176- |acc, x| {
177- if x. skip {
178- acc + x. row_count
179- } else {
180- acc
181- }
182- } ,
183- ) ;
184- file_metrics. page_index_rows_filtered . add ( total_skip) ;
185- Ok ( Some ( final_selection) )
186- } else {
187- Ok ( None )
188208 }
189209}
210+
190211/// Intersects the [`RowSelector`]s
191212///
192213/// For exampe, given:
@@ -203,35 +224,6 @@ fn combine_multi_col_selection(row_selections: Vec<Vec<RowSelector>>) -> RowSele
203224 . unwrap ( )
204225}
205226
206- // Extract single col pruningPredicate from input predicate for evaluating page Index.
207- fn extract_page_index_push_down_predicates (
208- predicate : Option < & PruningPredicate > ,
209- schema : SchemaRef ,
210- ) -> Result < Vec < PruningPredicate > > {
211- let mut one_col_predicates = vec ! [ ] ;
212- if let Some ( predicate) = predicate {
213- let expr = predicate. logical_expr ( ) ;
214- // todo try use CNF rewrite when ready
215- let predicates = split_conjunction ( expr) ;
216- let mut one_col_expr = vec ! [ ] ;
217- predicates
218- . into_iter ( )
219- . try_for_each :: < _ , Result < ( ) > > ( |predicate| {
220- let columns = predicate. to_columns ( ) ?;
221- if columns. len ( ) == 1 {
222- one_col_expr. push ( predicate) ;
223- }
224- Ok ( ( ) )
225- } ) ?;
226- one_col_predicates = one_col_expr
227- . into_iter ( )
228- . map ( |e| PruningPredicate :: try_new ( e. clone ( ) , schema. clone ( ) ) )
229- . collect :: < Result < Vec < _ > > > ( )
230- . unwrap_or_default ( ) ;
231- }
232- Ok ( one_col_predicates)
233- }
234-
235227fn prune_pages_in_one_row_group (
236228 group : & RowGroupMetaData ,
237229 predicate : & PruningPredicate ,
0 commit comments