@@ -37,7 +37,7 @@ use log::debug;
3737use  crate :: { 
3838    error:: Result , 
3939    execution:: context:: ExecutionContext , 
40-     logical_plan:: { self ,  Expr } , 
40+     logical_plan:: { self ,  Expr ,   ExpressionVisitor ,   Recursion } , 
4141    physical_plan:: functions:: Volatility , 
4242    scalar:: ScalarValue , 
4343} ; 
@@ -51,93 +51,85 @@ const FILE_SIZE_COLUMN_NAME: &str = "_df_part_file_size_";
5151const  FILE_PATH_COLUMN_NAME :  & str  = "_df_part_file_path_" ; 
5252const  FILE_MODIFIED_COLUMN_NAME :  & str  = "_df_part_file_modified_" ; 
5353
54+ /// The `ExpressionVisitor` for `expr_applicable_for_cols`. Walks the tree to 
55+ /// validate that the given expression is applicable with only the `col_names` 
56+ /// set of columns. 
57+ struct  ApplicabilityVisitor < ' a >  { 
58+     col_names :  & ' a  [ String ] , 
59+     is_applicable :  & ' a  mut  bool , 
60+ } 
61+ 
62+ impl  ApplicabilityVisitor < ' _ >  { 
63+     fn  is_volatitlity_applicable ( self ,  volatility :  Volatility )  -> Recursion < Self >  { 
64+         match  volatility { 
65+             Volatility :: Immutable  => Recursion :: Continue ( self ) , 
66+             // TODO: Stable functions could be `applicable`, but that would require access to the context 
67+             Volatility :: Stable  | Volatility :: Volatile  => { 
68+                 * self . is_applicable  = false ; 
69+                 Recursion :: Stop ( self ) 
70+             } 
71+         } 
72+     } 
73+ } 
74+ 
75+ impl  ExpressionVisitor  for  ApplicabilityVisitor < ' _ >  { 
76+     fn  pre_visit ( self ,  expr :  & Expr )  -> Result < Recursion < Self > >  { 
77+         let  rec = match  expr { 
78+             Expr :: Column ( logical_plan:: Column  {  ref  name,  .. } )  => { 
79+                 * self . is_applicable  &= self . col_names . contains ( name) ; 
80+                 Recursion :: Stop ( self )  // leaf node anyway 
81+             } 
82+             Expr :: Literal ( _) 
83+             | Expr :: Alias ( _,  _) 
84+             | Expr :: ScalarVariable ( _) 
85+             | Expr :: Not ( _) 
86+             | Expr :: IsNotNull ( _) 
87+             | Expr :: IsNull ( _) 
88+             | Expr :: Negative ( _) 
89+             | Expr :: Cast  {  .. } 
90+             | Expr :: TryCast  {  .. } 
91+             | Expr :: BinaryExpr  {  .. } 
92+             | Expr :: Between  {  .. } 
93+             | Expr :: InList  {  .. } 
94+             | Expr :: Case  {  .. }  => Recursion :: Continue ( self ) , 
95+ 
96+             Expr :: ScalarFunction  {  fun,  .. }  => { 
97+                 self . is_volatitlity_applicable ( fun. volatility ( ) ) 
98+             } 
99+             Expr :: ScalarUDF  {  fun,  .. }  => { 
100+                 self . is_volatitlity_applicable ( fun. signature . volatility ) 
101+             } 
102+ 
103+             // TODO other expressions are not handled yet: 
104+             // - AGGREGATE, WINDOW and SORT should not end up in filter conditions, except maybe in some edge cases 
105+             // - Can `Wildcard` be considered as a `Literal`? 
106+             // - ScalarVariable could be `applicable`, but that would require access to the context 
107+             Expr :: AggregateUDF  {  .. } 
108+             | Expr :: AggregateFunction  {  .. } 
109+             | Expr :: Sort  {  .. } 
110+             | Expr :: WindowFunction  {  .. } 
111+             | Expr :: Wildcard  => { 
112+                 * self . is_applicable  = false ; 
113+                 Recursion :: Stop ( self ) 
114+             } 
115+         } ; 
116+         Ok ( rec) 
117+     } 
118+ } 
119+ 
54120/// Check whether the given expression can be resolved using only the columns `col_names`. 
55121/// This means that if this function returns true: 
56122/// - the table provider can filter the table partition values with this expression 
57123/// - the expression can be marked as `TableProviderFilterPushDown::Exact` once this filtering 
58124/// was performed 
59125pub  fn  expr_applicable_for_cols ( col_names :  & [ String ] ,  expr :  & Expr )  -> bool  { 
60-     match  expr { 
61-         // leaf 
62-         Expr :: Literal ( _)  => true , 
63-         // TODO how to handle qualified / unqualified names? 
64-         Expr :: Column ( logical_plan:: Column  {  ref  name,  .. } )  => col_names. contains ( name) , 
65-         // unary 
66-         Expr :: Alias ( child,  _) 
67-         | Expr :: Not ( child) 
68-         | Expr :: IsNotNull ( child) 
69-         | Expr :: IsNull ( child) 
70-         | Expr :: Negative ( child) 
71-         | Expr :: Cast  {  expr :  child,  .. } 
72-         | Expr :: TryCast  {  expr :  child,  .. }  => expr_applicable_for_cols ( col_names,  child) , 
73-         // binary 
74-         Expr :: BinaryExpr  { 
75-             ref  left, 
76-             ref  right, 
77-             ..
78-         }  => { 
79-             expr_applicable_for_cols ( col_names,  left) 
80-                 && expr_applicable_for_cols ( col_names,  right) 
81-         } 
82-         // ternary 
83-         Expr :: Between  { 
84-             expr :  item, 
85-             low, 
86-             high, 
87-             ..
88-         }  => { 
89-             expr_applicable_for_cols ( col_names,  item) 
90-                 && expr_applicable_for_cols ( col_names,  low) 
91-                 && expr_applicable_for_cols ( col_names,  high) 
92-         } 
93-         // variadic 
94-         Expr :: ScalarFunction  {  fun,  args }  => match  fun. volatility ( )  { 
95-             Volatility :: Immutable  => args
96-                 . iter ( ) 
97-                 . all ( |arg| expr_applicable_for_cols ( col_names,  arg) ) , 
98-             // TODO: Stable functions could be `applicable`, but that would require access to the context 
99-             Volatility :: Stable  => false , 
100-             Volatility :: Volatile  => false , 
101-         } , 
102-         Expr :: ScalarUDF  {  fun,  args }  => match  fun. signature . volatility  { 
103-             Volatility :: Immutable  => args
104-                 . iter ( ) 
105-                 . all ( |arg| expr_applicable_for_cols ( col_names,  arg) ) , 
106-             // TODO: Stable functions could be `applicable`, but that would require access to the context 
107-             Volatility :: Stable  => false , 
108-             Volatility :: Volatile  => false , 
109-         } , 
110-         Expr :: InList  { 
111-             expr :  item,  list,  ..
112-         }  => { 
113-             expr_applicable_for_cols ( col_names,  item) 
114-                 && list. iter ( ) . all ( |e| expr_applicable_for_cols ( col_names,  e) ) 
115-         } 
116-         Expr :: Case  { 
117-             expr, 
118-             when_then_expr, 
119-             else_expr, 
120-         }  => { 
121-             let  expr_constant = expr
122-                 . as_ref ( ) 
123-                 . map ( |e| expr_applicable_for_cols ( col_names,  e) ) 
124-                 . unwrap_or ( true ) ; 
125-             let  else_constant = else_expr
126-                 . as_ref ( ) 
127-                 . map ( |e| expr_applicable_for_cols ( col_names,  e) ) 
128-                 . unwrap_or ( true ) ; 
129-             let  when_then_constant = when_then_expr. iter ( ) . all ( |( w,  th) | { 
130-                 expr_applicable_for_cols ( col_names,  w) 
131-                     && expr_applicable_for_cols ( col_names,  th) 
132-             } ) ; 
133-             expr_constant && else_constant && when_then_constant
134-         } 
135-         // TODO other expressions are not handled yet: 
136-         // - AGGREGATE, WINDOW and SORT should not end up in filter conditions, except maybe in some edge cases 
137-         // - Can `Wildcard` be considered as a `Literal`? 
138-         // - ScalarVariable could be `applicable`, but that would require access to the context 
139-         _ => false , 
140-     } 
126+     let  mut  is_applicable = true ; 
127+     expr. accept ( ApplicabilityVisitor  { 
128+         col_names, 
129+         is_applicable :  & mut  is_applicable, 
130+     } ) 
131+     . unwrap ( ) ; 
132+     is_applicable
141133} 
142134
143135/// Partition the list of files into `n` groups 
@@ -191,8 +183,10 @@ pub async fn pruned_partition_list(
191183        . collect ( ) ; 
192184    let  stream_path = table_path. to_owned ( ) ; 
193185    if  applicable_filters. is_empty ( )  { 
194-         // parse the partition values while listing all the files 
195-         // TODO we might avoid parsing the partition values if they are not used in any projection 
186+         // Parse the partition values while listing all the files 
187+         // Note: We might avoid parsing the partition values if they are not used in any projection, 
188+         // but the cost of parsing will likely be far dominated by the time to fetch the listing from 
189+         // the object store. 
196190        let  table_partition_cols_stream = table_partition_cols. to_vec ( ) ; 
197191        Ok ( Box :: pin ( 
198192            store
0 commit comments