datafusion_physical_expr_adapter/
rewrite.rs1use std::sync::Arc;
29
30use arrow::datatypes::{DataType, Field};
31use datafusion_common::{
32 Result, ScalarValue,
33 tree_node::{Transformed, TreeNode, TreeNodeRecursion},
34};
35use datafusion_expr::ScalarUDFImpl;
36use datafusion_functions::core::file_row_index::FileRowIndexFunc;
37use datafusion_functions::core::input_file_name::InputFileNameFunc;
38use datafusion_physical_expr::ScalarFunctionExpr;
39use datafusion_physical_expr::expressions::{CastExpr, Column, Literal};
40use datafusion_physical_expr::projection::{ProjectionExpr, ProjectionExprs};
41use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
42
43pub fn expr_references_scalar_udf<T: ScalarUDFImpl>(
48 expr: &Arc<dyn PhysicalExpr>,
49) -> bool {
50 let mut found = false;
51
52 expr.apply(|node| {
53 if ScalarFunctionExpr::try_downcast_func::<T>(node.as_ref()).is_some() {
54 found = true;
55 return Ok(TreeNodeRecursion::Stop);
56 }
57 Ok(TreeNodeRecursion::Continue)
58 })
59 .expect("Infallible traversal of PhysicalExpr tree failed");
60
61 found
62}
63
64fn rewrite_scalar_udf<T, F>(
71 expr: Arc<dyn PhysicalExpr>,
72 mut replacement: F,
73) -> Result<Arc<dyn PhysicalExpr>>
74where
75 T: ScalarUDFImpl,
76 F: FnMut(&ScalarFunctionExpr) -> Result<Arc<dyn PhysicalExpr>>,
77{
78 expr.transform_up(|node| {
79 if let Some(scalar_fn) = ScalarFunctionExpr::try_downcast_func::<T>(node.as_ref())
80 {
81 Ok(Transformed::yes(replacement(scalar_fn)?))
82 } else {
83 Ok(Transformed::no(node))
84 }
85 })
86 .map(|transformed| transformed.data)
87}
88
89pub fn rewrite_file_row_index_expr(
98 expr: Arc<dyn PhysicalExpr>,
99 row_index_name: &str,
100 row_index_idx: usize,
101) -> Result<Arc<dyn PhysicalExpr>> {
102 rewrite_scalar_udf::<FileRowIndexFunc, _>(expr, |_| {
103 let source = Arc::new(Column::new(row_index_name, row_index_idx));
104 let target_field = Arc::new(Field::new("file_row_index", DataType::Int64, true));
105 Ok(Arc::new(CastExpr::new_with_target_field(
106 source,
107 target_field,
108 None,
109 )))
110 })
111}
112
113pub fn rewrite_file_row_index_projection(
126 base_projection: &ProjectionExprs,
127 projection: &ProjectionExprs,
128 row_index_col: &Column,
129) -> Result<ProjectionExprs> {
130 let mut base_exprs = base_projection.as_ref().to_vec();
131 let row_index_projection_idx =
132 base_projection.projected_column_position(row_index_col);
133
134 if row_index_projection_idx.is_none() {
136 base_exprs.push(ProjectionExpr {
137 expr: Arc::new(row_index_col.clone()),
138 alias: row_index_col.name().to_owned(),
139 });
140 }
141
142 let rewritten_projection = projection.clone().try_map_exprs(|expr| {
143 rewrite_file_row_index_expr(
144 expr,
145 row_index_col.name(),
146 row_index_projection_idx.unwrap_or(base_exprs.len() - 1),
147 )
148 })?;
149
150 ProjectionExprs::new(base_exprs).try_merge(&rewritten_projection)
151}
152
153pub fn rewrite_input_file_name_in_projection(
160 projection: ProjectionExprs,
161 file_name: &str,
162) -> Result<ProjectionExprs> {
163 if !projection
164 .iter()
165 .any(|p| expr_references_scalar_udf::<InputFileNameFunc>(&p.expr))
166 {
167 return Ok(projection);
168 }
169
170 let file_name_lit =
171 Arc::new(Literal::new(ScalarValue::Utf8(Some(file_name.to_string()))))
172 as Arc<dyn PhysicalExpr>;
173
174 projection.try_map_exprs(|expr| {
175 rewrite_scalar_udf::<InputFileNameFunc, _>(expr, |_| {
176 Ok(Arc::clone(&file_name_lit))
177 })
178 })
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 use arrow::datatypes::Schema;
186 use datafusion_common::config::ConfigOptions;
187 use datafusion_expr::{Operator, ScalarUDF};
188 use datafusion_physical_expr::expressions;
189 use std::collections::HashMap;
190
191 fn file_row_index_expr() -> Arc<dyn PhysicalExpr> {
192 Arc::new(ScalarFunctionExpr::new(
193 "file_row_index",
194 Arc::new(ScalarUDF::from(FileRowIndexFunc::new())),
195 vec![],
196 Arc::new(Field::new("file_row_index", DataType::Int64, true)),
197 Arc::new(ConfigOptions::default()),
198 ))
199 }
200
201 fn input_file_name_expr() -> Arc<dyn PhysicalExpr> {
202 Arc::new(ScalarFunctionExpr::new(
203 "input_file_name",
204 Arc::new(ScalarUDF::from(InputFileNameFunc::new())),
205 vec![],
206 Arc::new(Field::new("input_file_name", DataType::Utf8, true)),
207 Arc::new(ConfigOptions::default()),
208 ))
209 }
210
211 #[test]
212 fn test_rewrite_scalar_udf_replaces_nested_typed_udf() -> Result<()> {
213 let expr = Arc::new(expressions::BinaryExpr::new(
214 file_row_index_expr(),
215 Operator::Plus,
216 expressions::lit(ScalarValue::Int64(Some(1))),
217 )) as Arc<dyn PhysicalExpr>;
218
219 let rewritten = rewrite_scalar_udf::<FileRowIndexFunc, _>(expr, |_| {
220 Ok(expressions::lit(ScalarValue::Int64(Some(7))))
221 })?;
222
223 let binary = rewritten
224 .downcast_ref::<expressions::BinaryExpr>()
225 .expect("rewritten expression should remain binary");
226 assert_eq!(binary.op(), &Operator::Plus);
227
228 let left = binary
229 .left()
230 .downcast_ref::<Literal>()
231 .expect("left side should be rewritten to a literal");
232 assert_eq!(left.value(), &ScalarValue::Int64(Some(7)));
233
234 let right = binary
235 .right()
236 .downcast_ref::<Literal>()
237 .expect("right side should remain the original literal");
238 assert_eq!(right.value(), &ScalarValue::Int64(Some(1)));
239 Ok(())
240 }
241
242 #[test]
243 fn test_rewrite_input_file_name_in_projection() -> Result<()> {
244 let file_name = "part=west/data.parquet";
245 let projection = ProjectionExprs::new([
246 ProjectionExpr::new(input_file_name_expr(), "file_name"),
247 ProjectionExpr::new(
248 Arc::new(expressions::BinaryExpr::new(
249 input_file_name_expr(),
250 Operator::Eq,
251 expressions::lit(ScalarValue::Utf8(Some(file_name.to_string()))),
252 )),
253 "matches_file",
254 ),
255 ]);
256
257 let rewritten = rewrite_input_file_name_in_projection(projection, file_name)?;
258 let rewritten = rewritten.as_ref();
259 assert_eq!(rewritten[0].alias, "file_name");
260 assert_eq!(rewritten[1].alias, "matches_file");
261
262 let file_name_lit = rewritten[0]
263 .expr
264 .downcast_ref::<Literal>()
265 .expect("input_file_name should rewrite to a literal");
266 assert_eq!(
267 file_name_lit.value(),
268 &ScalarValue::Utf8(Some(file_name.to_string()))
269 );
270
271 let binary = rewritten[1]
272 .expr
273 .downcast_ref::<expressions::BinaryExpr>()
274 .expect("nested expression should remain binary");
275 assert_eq!(binary.op(), &Operator::Eq);
276
277 let left = binary
278 .left()
279 .downcast_ref::<Literal>()
280 .expect("nested input_file_name should rewrite to a literal");
281 assert_eq!(
282 left.value(),
283 &ScalarValue::Utf8(Some(file_name.to_string()))
284 );
285
286 let right = binary
287 .right()
288 .downcast_ref::<Literal>()
289 .expect("comparison literal should remain unchanged");
290 assert_eq!(
291 right.value(),
292 &ScalarValue::Utf8(Some(file_name.to_string()))
293 );
294 Ok(())
295 }
296
297 #[test]
298 fn test_rewrite_file_row_index_expr_to_source_column() -> Result<()> {
299 let expr = rewrite_file_row_index_expr(
300 file_row_index_expr(),
301 "__datafusion_file_row_index",
302 2,
303 )?;
304
305 let cast_expr = expr
306 .downcast_ref::<CastExpr>()
307 .expect("file row index expression should be a cast");
308 assert_eq!(cast_expr.cast_type(), &DataType::Int64);
309 let target_field = cast_expr.target_field();
310 assert_eq!(target_field.name(), "file_row_index");
311 assert_eq!(target_field.data_type(), &DataType::Int64);
312 assert!(target_field.is_nullable());
313 assert!(target_field.metadata().is_empty());
314
315 let source = cast_expr
316 .expr()
317 .downcast_ref::<Column>()
318 .expect("source column");
319 assert_eq!(source.name(), "__datafusion_file_row_index");
320 assert_eq!(source.index(), 2);
321
322 let input_schema = Schema::new(vec![
323 Field::new("value", DataType::Int64, true),
324 Field::new("__datafusion_file_row_index", DataType::Int64, false)
325 .with_metadata(HashMap::from([(
326 "source".to_string(),
327 "virtual".to_string(),
328 )])),
329 ]);
330 let return_field = expr.return_field(&input_schema)?;
331 assert_eq!(return_field.name(), "file_row_index");
332 assert_eq!(return_field.data_type(), &DataType::Int64);
333 assert!(return_field.is_nullable());
334 assert!(return_field.metadata().is_empty());
335 Ok(())
336 }
337}