Skip to content

Commit 0df9b99

Browse files
authored
Avoid changing expression names during constant folding (#1319)
* Avoid changing output column name before pushdown projection. * Avoid early optimization which is duplicate. * Modify test. * Revert "Modify test." This reverts commit ebf67d3. * Revert "Avoid early optimization which is duplicate." This reverts commit fe8445d. * Add aliases during constant folding. * Some expressions don't support name. * Don't create redundant alias. * Only add alias for certain plans. * Fix clippy. * Fix. * Revert "Fix." This reverts commit d767aeb. * Apply to all nodes and update tests. * Unalias when push donw to TableScan. * Update more tests. * Remove previous change.
1 parent 7905926 commit 0df9b99

5 files changed

Lines changed: 82 additions & 40 deletions

File tree

datafusion/src/logical_plan/expr.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,15 @@ pub fn unnormalize_cols(exprs: impl IntoIterator<Item = Expr>) -> Vec<Expr> {
13491349
exprs.into_iter().map(unnormalize_col).collect()
13501350
}
13511351

1352+
/// Recursively un-alias an expressions
1353+
#[inline]
1354+
pub fn unalias(expr: Expr) -> Expr {
1355+
match expr {
1356+
Expr::Alias(sub_expr, _) => unalias(*sub_expr),
1357+
_ => expr,
1358+
}
1359+
}
1360+
13521361
/// Create an expression to represent the min() aggregate function
13531362
pub fn min(expr: Expr) -> Expr {
13541363
Expr::AggregateFunction {

datafusion/src/logical_plan/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub use expr::{
4444
max, md5, min, normalize_col, normalize_cols, now, octet_length, or, random,
4545
regexp_match, regexp_replace, repeat, replace, replace_col, reverse, right, round,
4646
rpad, rtrim, sha224, sha256, sha384, sha512, signum, sin, split_part, sqrt,
47-
starts_with, strpos, substr, sum, tan, to_hex, translate, trim, trunc,
47+
starts_with, strpos, substr, sum, tan, to_hex, translate, trim, trunc, unalias,
4848
unnormalize_col, unnormalize_cols, upper, when, Column, Expr, ExprRewriter,
4949
ExpressionVisitor, Literal, Recursion, RewriteRecursion,
5050
};

datafusion/src/optimizer/constant_folding.rs

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ impl OptimizerRule for ConstantFolding {
9292
.expressions()
9393
.into_iter()
9494
.map(|e| {
95+
// We need to keep original expression name, if any.
96+
// Constant folding should not change expression name.
97+
let name = &e.name(plan.schema());
98+
9599
// TODO iterate until no changes are made
96100
// during rewrite (evaluating constants can
97101
// enable new simplifications and
@@ -101,7 +105,18 @@ impl OptimizerRule for ConstantFolding {
101105
// fold constants and then simplify
102106
.rewrite(&mut const_evaluator)?
103107
.rewrite(&mut simplifier)?;
104-
Ok(new_e)
108+
109+
let new_name = &new_e.name(plan.schema());
110+
111+
if let (Ok(expr_name), Ok(new_expr_name)) = (name, new_name) {
112+
if expr_name != new_expr_name {
113+
Ok(new_e.alias(expr_name))
114+
} else {
115+
Ok(new_e)
116+
}
117+
} else {
118+
Ok(new_e)
119+
}
105120
})
106121
.collect::<Result<Vec<_>>>()?;
107122

@@ -626,8 +641,8 @@ mod tests {
626641

627642
let expected = "\
628643
Projection: #test.a\
629-
\n Filter: NOT #test.c\
630-
\n Filter: #test.b\
644+
\n Filter: NOT #test.c AS test.c = Boolean(false)\
645+
\n Filter: #test.b AS test.b = Boolean(true)\
631646
\n TableScan: test projection=None";
632647

633648
assert_optimized_plan_eq(&plan, expected);
@@ -647,8 +662,8 @@ mod tests {
647662
let expected = "\
648663
Projection: #test.a\
649664
\n Limit: 1\
650-
\n Filter: #test.c\
651-
\n Filter: NOT #test.b\
665+
\n Filter: #test.c AS test.c != Boolean(false)\
666+
\n Filter: NOT #test.b AS test.b != Boolean(true)\
652667
\n TableScan: test projection=None";
653668

654669
assert_optimized_plan_eq(&plan, expected);
@@ -665,7 +680,7 @@ mod tests {
665680

666681
let expected = "\
667682
Projection: #test.a\
668-
\n Filter: NOT #test.b AND #test.c\
683+
\n Filter: NOT #test.b AND #test.c AS test.b != Boolean(true) AND test.c = Boolean(true)\
669684
\n TableScan: test projection=None";
670685

671686
assert_optimized_plan_eq(&plan, expected);
@@ -682,7 +697,7 @@ mod tests {
682697

683698
let expected = "\
684699
Projection: #test.a\
685-
\n Filter: NOT #test.b OR NOT #test.c\
700+
\n Filter: NOT #test.b OR NOT #test.c AS test.b != Boolean(true) OR test.c = Boolean(false)\
686701
\n TableScan: test projection=None";
687702

688703
assert_optimized_plan_eq(&plan, expected);
@@ -699,7 +714,7 @@ mod tests {
699714

700715
let expected = "\
701716
Projection: #test.a\
702-
\n Filter: #test.b\
717+
\n Filter: #test.b AS NOT test.b = Boolean(false)\
703718
\n TableScan: test projection=None";
704719

705720
assert_optimized_plan_eq(&plan, expected);
@@ -714,7 +729,7 @@ mod tests {
714729
.build()?;
715730

716731
let expected = "\
717-
Projection: #test.a, #test.d, NOT #test.b\
732+
Projection: #test.a, #test.d, NOT #test.b AS test.b = Boolean(false)\
718733
\n TableScan: test projection=None";
719734

720735
assert_optimized_plan_eq(&plan, expected);
@@ -733,7 +748,7 @@ mod tests {
733748
.build()?;
734749

735750
let expected = "\
736-
Aggregate: groupBy=[[#test.a, #test.c]], aggr=[[MAX(#test.b), MIN(#test.b)]]\
751+
Aggregate: groupBy=[[#test.a, #test.c]], aggr=[[MAX(#test.b) AS MAX(test.b = Boolean(true)), MIN(#test.b)]]\
737752
\n Projection: #test.a, #test.c, #test.b\
738753
\n TableScan: test projection=None";
739754

@@ -789,7 +804,7 @@ mod tests {
789804
.build()
790805
.unwrap();
791806

792-
let expected = "Projection: TimestampNanosecond(1599566400000000000)\
807+
let expected = "Projection: TimestampNanosecond(1599566400000000000) AS totimestamp(Utf8(\"2020-09-08T12:00:00+00:00\"))\
793808
\n TableScan: test projection=None"
794809
.to_string();
795810
let actual = get_optimized_plan_formatted(&plan, &Utc::now());
@@ -824,7 +839,7 @@ mod tests {
824839
.build()
825840
.unwrap();
826841

827-
let expected = "Projection: Int32(0)\
842+
let expected = "Projection: Int32(0) AS CAST(Utf8(\"0\") AS Int32)\
828843
\n TableScan: test projection=None";
829844
let actual = get_optimized_plan_formatted(&plan, &Utc::now());
830845
assert_eq!(expected, actual);
@@ -873,7 +888,7 @@ mod tests {
873888
// expect the same timestamp appears in both exprs
874889
let actual = get_optimized_plan_formatted(&plan, &time);
875890
let expected = format!(
876-
"Projection: TimestampNanosecond({}), TimestampNanosecond({}) AS t2\
891+
"Projection: TimestampNanosecond({}) AS now(), TimestampNanosecond({}) AS t2\
877892
\n TableScan: test projection=None",
878893
time.timestamp_nanos(),
879894
time.timestamp_nanos()
@@ -897,7 +912,8 @@ mod tests {
897912
.unwrap();
898913

899914
let actual = get_optimized_plan_formatted(&plan, &time);
900-
let expected = "Projection: NOT #test.a\
915+
let expected =
916+
"Projection: NOT #test.a AS Boolean(true) OR Boolean(false) != test.a\
901917
\n TableScan: test projection=None";
902918

903919
assert_eq!(actual, expected);
@@ -929,7 +945,7 @@ mod tests {
929945

930946
// Note that constant folder runs and folds the entire
931947
// expression down to a single constant (true)
932-
let expected = "Filter: Boolean(true)\
948+
let expected = "Filter: Boolean(true) AS CAST(now() AS Int64) < CAST(totimestamp(Utf8(\"2020-09-08T12:05:00+00:00\")) AS Int64) + Int32(50000)\
933949
\n TableScan: test projection=None";
934950
let actual = get_optimized_plan_formatted(&plan, &time);
935951

datafusion/src/physical_plan/planner.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use super::{
2525
use crate::execution::context::ExecutionContextState;
2626
use crate::logical_plan::plan::{EmptyRelation, Filter, Projection, Window};
2727
use crate::logical_plan::{
28-
unnormalize_cols, CrossJoin, DFSchema, Expr, LogicalPlan, Operator,
28+
unalias, unnormalize_cols, CrossJoin, DFSchema, Expr, LogicalPlan, Operator,
2929
Partitioning as LogicalPartitioning, PlanType, Repartition, ToStringifiedPlan, Union,
3030
UserDefinedLogicalNode,
3131
};
@@ -339,7 +339,8 @@ impl DefaultPhysicalPlanner {
339339
// doesn't know (nor should care) how the relation was
340340
// referred to in the query
341341
let filters = unnormalize_cols(filters.iter().cloned());
342-
source.scan(projection, batch_size, &filters, *limit).await
342+
let unaliased: Vec<Expr> = filters.into_iter().map(unalias).collect();
343+
source.scan(projection, batch_size, &unaliased, *limit).await
343344
}
344345
LogicalPlan::Values(Values {
345346
values,
@@ -1340,7 +1341,7 @@ impl DefaultPhysicalPlanner {
13401341
physical_input_schema: &Schema,
13411342
ctx_state: &ExecutionContextState,
13421343
) -> Result<Arc<dyn AggregateExpr>> {
1343-
// unpack aliased logical expressions, e.g. "sum(col) as total"
1344+
// unpack (nested) aliased logical expressions, e.g. "sum(col) as total"
13441345
let (name, e) = match e {
13451346
Expr::Alias(sub_expr, alias) => (alias.clone(), sub_expr.as_ref()),
13461347
_ => (physical_name(e)?, e),

datafusion/tests/sql.rs

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,22 @@ async fn csv_query_approx_count() -> Result<()> {
13911391
Ok(())
13921392
}
13931393

1394+
#[tokio::test]
1395+
async fn query_count_without_from() -> Result<()> {
1396+
let mut ctx = ExecutionContext::new();
1397+
let sql = "SELECT count(1 + 1)";
1398+
let actual = execute_to_batches(&mut ctx, sql).await;
1399+
let expected = vec![
1400+
"+----------------------------+",
1401+
"| COUNT(Int64(1) + Int64(1)) |",
1402+
"+----------------------------+",
1403+
"| 1 |",
1404+
"+----------------------------+",
1405+
];
1406+
assert_batches_eq!(expected, &actual);
1407+
Ok(())
1408+
}
1409+
13941410
#[tokio::test]
13951411
async fn csv_query_array_agg() -> Result<()> {
13961412
let mut ctx = ExecutionContext::new();
@@ -1663,12 +1679,12 @@ async fn csv_query_cast_literal() -> Result<()> {
16631679
let actual = execute_to_batches(&mut ctx, sql).await;
16641680

16651681
let expected = vec![
1666-
"+--------------------+------------+",
1667-
"| c12 | Float64(1) |",
1668-
"+--------------------+------------+",
1669-
"| 0.9294097332465232 | 1 |",
1670-
"| 0.3114712539863804 | 1 |",
1671-
"+--------------------+------------+",
1682+
"+--------------------+---------------------------+",
1683+
"| c12 | CAST(Int64(1) AS Float64) |",
1684+
"+--------------------+---------------------------+",
1685+
"| 0.9294097332465232 | 1 |",
1686+
"| 0.3114712539863804 | 1 |",
1687+
"+--------------------+---------------------------+",
16721688
];
16731689

16741690
assert_batches_eq!(expected, &actual);
@@ -4410,11 +4426,11 @@ async fn query_without_from() -> Result<()> {
44104426
let sql = "SELECT 1+2, 3/4, cos(0)";
44114427
let actual = execute_to_batches(&mut ctx, sql).await;
44124428
let expected = vec![
4413-
"+----------+----------+------------+",
4414-
"| Int64(3) | Int64(0) | Float64(1) |",
4415-
"+----------+----------+------------+",
4416-
"| 3 | 0 | 1 |",
4417-
"+----------+----------+------------+",
4429+
"+---------------------+---------------------+---------------+",
4430+
"| Int64(1) + Int64(2) | Int64(3) / Int64(4) | cos(Int64(0)) |",
4431+
"+---------------------+---------------------+---------------+",
4432+
"| 3 | 0 | 1 |",
4433+
"+---------------------+---------------------+---------------+",
44184434
];
44194435
assert_batches_eq!(expected, &actual);
44204436

@@ -5865,11 +5881,11 @@ async fn case_with_bool_type_result() -> Result<()> {
58655881
let sql = "select case when 'cpu' != 'cpu' then true else false end";
58665882
let actual = execute_to_batches(&mut ctx, sql).await;
58675883
let expected = vec![
5868-
"+----------------+",
5869-
"| Boolean(false) |",
5870-
"+----------------+",
5871-
"| false |",
5872-
"+----------------+",
5884+
"+---------------------------------------------------------------------------------+",
5885+
"| CASE WHEN Utf8(\"cpu\") != Utf8(\"cpu\") THEN Boolean(true) ELSE Boolean(false) END |",
5886+
"+---------------------------------------------------------------------------------+",
5887+
"| false |",
5888+
"+---------------------------------------------------------------------------------+",
58735889
];
58745890
assert_batches_eq!(expected, &actual);
58755891
Ok(())
@@ -5882,11 +5898,11 @@ async fn use_between_expression_in_select_query() -> Result<()> {
58825898
let sql = "SELECT 1 NOT BETWEEN 3 AND 5";
58835899
let actual = execute_to_batches(&mut ctx, sql).await;
58845900
let expected = vec![
5885-
"+---------------+",
5886-
"| Boolean(true) |",
5887-
"+---------------+",
5888-
"| true |",
5889-
"+---------------+",
5901+
"+--------------------------------------------+",
5902+
"| Int64(1) NOT BETWEEN Int64(3) AND Int64(5) |",
5903+
"+--------------------------------------------+",
5904+
"| true |",
5905+
"+--------------------------------------------+",
58905906
];
58915907
assert_batches_eq!(expected, &actual);
58925908

0 commit comments

Comments
 (0)