1use std::sync::Arc;
19
20use datafusion_common::{
21 DataFusionError, HashMap,
22 tree_node::{Transformed, TreeNodeRecursion, TreeNodeRewriter},
23};
24use datafusion_physical_expr::{PhysicalExpr, expressions::Column};
25
26pub struct PhysicalColumnRewriter<'a> {
35 pub column_map: &'a HashMap<Column, Arc<dyn PhysicalExpr>>,
37}
38
39impl<'a> PhysicalColumnRewriter<'a> {
40 pub fn new(column_map: &'a HashMap<Column, Arc<dyn PhysicalExpr>>) -> Self {
42 Self { column_map }
43 }
44}
45
46impl<'a> TreeNodeRewriter for PhysicalColumnRewriter<'a> {
47 type Node = Arc<dyn PhysicalExpr>;
48
49 fn f_down(
50 &mut self,
51 node: Self::Node,
52 ) -> datafusion_common::Result<Transformed<Self::Node>> {
53 if let Some(column) = node.downcast_ref::<Column>() {
54 if let Some(new_column) = self.column_map.get(column) {
55 return Ok(Transformed::new(
57 Arc::clone(new_column),
58 true,
59 TreeNodeRecursion::Jump,
60 ));
61 } else {
62 return Err(DataFusionError::Internal(format!(
64 "Column {column:?} not found in column mapping {:?}",
65 self.column_map
66 )));
67 }
68 }
69 Ok(Transformed::no(node))
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use arrow::datatypes::{DataType, Field, Schema};
77 use datafusion_common::{Result, tree_node::TreeNode};
78 use datafusion_physical_expr::{
79 PhysicalExpr,
80 expressions::{Column, binary, col, lit},
81 };
82
83 fn create_test_schema() -> Arc<Schema> {
85 Arc::new(Schema::new(vec![
86 Field::new("a", DataType::Int32, true),
87 Field::new("b", DataType::Int32, true),
88 Field::new("c", DataType::Int32, true),
89 Field::new("d", DataType::Int32, true),
90 Field::new("e", DataType::Int32, true),
91 Field::new("new_col", DataType::Int32, true),
92 Field::new("inner_col", DataType::Int32, true),
93 Field::new("another_col", DataType::Int32, true),
94 ]))
95 }
96
97 fn create_complex_expression(schema: &Schema) -> Arc<dyn PhysicalExpr> {
100 let col_a = col("a", schema).unwrap();
101 let col_b = col("b", schema).unwrap();
102 let col_c = col("c", schema).unwrap();
103 let col_d = col("d", schema).unwrap();
104 let col_e = col("e", schema).unwrap();
105
106 let add_expr =
107 binary(col_a, datafusion_expr::Operator::Plus, col_b, schema).unwrap();
108 let sub_expr =
109 binary(col_c, datafusion_expr::Operator::Minus, col_d, schema).unwrap();
110 let mul_expr = binary(
111 add_expr,
112 datafusion_expr::Operator::Multiply,
113 sub_expr,
114 schema,
115 )
116 .unwrap();
117 binary(mul_expr, datafusion_expr::Operator::Plus, col_e, schema).unwrap()
118 }
119
120 fn create_deeply_nested_expression(schema: &Schema) -> Arc<dyn PhysicalExpr> {
123 let col_a = col("a", schema).unwrap();
124 let col_b = col("b", schema).unwrap();
125 let col_c = col("c", schema).unwrap();
126 let col_d = col("d", schema).unwrap();
127 let col_e = col("e", schema).unwrap();
128
129 let inner1 =
130 binary(col_d, datafusion_expr::Operator::Plus, col_e, schema).unwrap();
131 let inner2 =
132 binary(col_c, datafusion_expr::Operator::Plus, inner1, schema).unwrap();
133 let inner3 =
134 binary(col_b, datafusion_expr::Operator::Plus, inner2, schema).unwrap();
135 binary(col_a, datafusion_expr::Operator::Plus, inner3, schema).unwrap()
136 }
137
138 #[test]
139 fn test_simple_column_replacement_with_jump() -> Result<()> {
140 let schema = create_test_schema();
141
142 let mut column_map = HashMap::new();
144 column_map.insert(Column::new_with_schema("a", &schema).unwrap(), lit(42i32));
145 column_map.insert(
146 Column::new_with_schema("b", &schema).unwrap(),
147 lit("replaced_b"),
148 );
149 column_map.insert(
150 Column::new_with_schema("c", &schema).unwrap(),
151 col("c", &schema).unwrap(),
152 );
153 column_map.insert(
154 Column::new_with_schema("d", &schema).unwrap(),
155 col("d", &schema).unwrap(),
156 );
157 column_map.insert(
158 Column::new_with_schema("e", &schema).unwrap(),
159 col("e", &schema).unwrap(),
160 );
161
162 let mut rewriter = PhysicalColumnRewriter::new(&column_map);
163 let expr = create_complex_expression(&schema);
164
165 let result = expr.rewrite(&mut rewriter)?;
166
167 assert!(result.transformed);
169
170 assert_eq!(
171 format!("{}", result.data),
172 "(42 + replaced_b) * (c@2 - d@3) + e@4"
173 );
174
175 Ok(())
176 }
177
178 #[test]
179 fn test_nested_column_replacement_with_jump() -> Result<()> {
180 let schema = create_test_schema();
181 let mut column_map = HashMap::new();
183 let replacement_expr = binary(
185 lit(100i32),
186 datafusion_expr::Operator::Plus,
187 col("new_col", &schema).unwrap(),
188 &schema,
189 )
190 .unwrap();
191 column_map.insert(
192 Column::new_with_schema("c", &schema).unwrap(),
193 replacement_expr,
194 );
195 column_map.insert(
196 Column::new_with_schema("a", &schema).unwrap(),
197 col("a", &schema).unwrap(),
198 );
199 column_map.insert(
200 Column::new_with_schema("b", &schema).unwrap(),
201 col("b", &schema).unwrap(),
202 );
203 column_map.insert(
204 Column::new_with_schema("d", &schema).unwrap(),
205 col("d", &schema).unwrap(),
206 );
207 column_map.insert(
208 Column::new_with_schema("e", &schema).unwrap(),
209 col("e", &schema).unwrap(),
210 );
211
212 let mut rewriter = PhysicalColumnRewriter::new(&column_map);
213 let expr = create_deeply_nested_expression(&schema);
214
215 let result = expr.rewrite(&mut rewriter)?;
216
217 assert!(result.transformed);
219
220 assert_eq!(
221 format!("{}", result.data),
222 "a@0 + b@1 + 100 + new_col@5 + d@3 + e@4"
223 );
224
225 Ok(())
226 }
227
228 #[test]
229 fn test_circular_reference_prevention() -> Result<()> {
230 let schema = create_test_schema();
231 let mut column_map = HashMap::new();
233
234 column_map.insert(
236 Column::new_with_schema("a", &schema).unwrap(),
237 col("b", &schema).unwrap(),
238 );
239 column_map.insert(
240 Column::new_with_schema("b", &schema).unwrap(),
241 col("a", &schema).unwrap(),
242 );
243
244 let mut rewriter = PhysicalColumnRewriter::new(&column_map);
245
246 let expr = binary(
248 col("a", &schema).unwrap(),
249 datafusion_expr::Operator::Plus,
250 col("b", &schema).unwrap(),
251 &schema,
252 )
253 .unwrap();
254
255 let result = expr.rewrite(&mut rewriter)?;
256
257 assert!(result.transformed);
259
260 assert_eq!(format!("{}", result.data), "b@1 + a@0");
261
262 Ok(())
263 }
264
265 #[test]
266 fn test_multiple_replacements_in_same_expression() -> Result<()> {
267 let schema = create_test_schema();
268 let mut column_map = HashMap::new();
270
271 column_map.insert(Column::new_with_schema("a", &schema).unwrap(), lit(10i32));
273 column_map.insert(Column::new_with_schema("c", &schema).unwrap(), lit(20i32));
274 column_map.insert(Column::new_with_schema("e", &schema).unwrap(), lit(30i32));
275 column_map.insert(
276 Column::new_with_schema("b", &schema).unwrap(),
277 col("b", &schema).unwrap(),
278 );
279 column_map.insert(
280 Column::new_with_schema("d", &schema).unwrap(),
281 col("d", &schema).unwrap(),
282 );
283
284 let mut rewriter = PhysicalColumnRewriter::new(&column_map);
285 let expr = create_complex_expression(&schema); let result = expr.rewrite(&mut rewriter)?;
288
289 assert!(result.transformed);
291 assert_eq!(format!("{}", result.data), "(10 + b@1) * (20 - d@3) + 30");
292
293 Ok(())
294 }
295
296 #[test]
297 fn test_jump_with_complex_replacement_expression() -> Result<()> {
298 let schema = create_test_schema();
299 let mut column_map = HashMap::new();
301
302 let inner_expr = binary(
304 lit(5i32),
305 datafusion_expr::Operator::Multiply,
306 col("a", &schema).unwrap(),
307 &schema,
308 )
309 .unwrap();
310 let middle_expr = binary(
311 inner_expr,
312 datafusion_expr::Operator::Plus,
313 lit(3i32),
314 &schema,
315 )
316 .unwrap();
317 let complex_replacement = binary(
318 middle_expr,
319 datafusion_expr::Operator::Minus,
320 col("another_col", &schema).unwrap(),
321 &schema,
322 )
323 .unwrap();
324
325 column_map.insert(
326 Column::new_with_schema("a", &schema).unwrap(),
327 complex_replacement,
328 );
329 column_map.insert(
330 Column::new_with_schema("b", &schema).unwrap(),
331 col("b", &schema).unwrap(),
332 );
333
334 let mut rewriter = PhysicalColumnRewriter::new(&column_map);
335
336 let expr = binary(
338 col("a", &schema).unwrap(),
339 datafusion_expr::Operator::Plus,
340 col("b", &schema).unwrap(),
341 &schema,
342 )
343 .unwrap();
344
345 let result = expr.rewrite(&mut rewriter)?;
346
347 assert_eq!(
348 format!("{}", result.data),
349 "5 * a@0 + 3 - another_col@7 + b@1"
350 );
351
352 assert!(result.transformed);
354
355 Ok(())
356 }
357
358 #[test]
359 fn test_unmapped_columns_detection() -> Result<()> {
360 let schema = create_test_schema();
361 let mut column_map = HashMap::new();
362
363 column_map.insert(Column::new_with_schema("a", &schema).unwrap(), lit(42i32));
365
366 let mut rewriter = PhysicalColumnRewriter::new(&column_map);
367
368 let expr = binary(
370 col("a", &schema).unwrap(),
371 datafusion_expr::Operator::Plus,
372 col("b", &schema).unwrap(),
373 &schema,
374 )
375 .unwrap();
376
377 let err = expr.rewrite(&mut rewriter).unwrap_err();
378 assert!(matches!(err, DataFusionError::Internal(_)));
379
380 Ok(())
381 }
382}