diff --git a/datafusion/functions/src/math/log.rs b/datafusion/functions/src/math/log.rs index 11d76d8086be9..732cfff6cf053 100644 --- a/datafusion/functions/src/math/log.rs +++ b/datafusion/functions/src/math/log.rs @@ -362,23 +362,27 @@ impl ScalarUDFImpl for LogFunc { } else { lit(ScalarValue::new_ten(&number_datatype)?) }; + let base_nullable = info.nullable(&base)?; match number { Expr::Literal(value, _) - if value == ScalarValue::new_one(&number_datatype)? => + if value == ScalarValue::new_one(&number_datatype)? && !base_nullable => { Ok(ExprSimplifyResult::Simplified(lit(ScalarValue::new_zero( &info.get_data_type(&base)?, )?))) } Expr::ScalarFunction(ScalarFunction { func, mut args }) - if is_pow(&func) && args.len() == 2 && base == args[0] => + if is_pow(&func) + && args.len() == 2 + && base == args[0] + && !base_nullable => { let b = args.pop().unwrap(); // length checked above Ok(ExprSimplifyResult::Simplified(b)) } number => { - if number == base { + if number == base && !base_nullable { Ok(ExprSimplifyResult::Simplified(lit(ScalarValue::new_one( &number_datatype, )?))) diff --git a/datafusion/functions/src/math/power.rs b/datafusion/functions/src/math/power.rs index 54ba0d3581e3a..30ac401b5ff7d 100644 --- a/datafusion/functions/src/math/power.rs +++ b/datafusion/functions/src/math/power.rs @@ -140,6 +140,7 @@ impl ScalarUDFImpl for PowerFunc { let [base, exponent] = take_function_args("power", args)?; let base_type = info.get_data_type(&base)?; let exponent_type = info.get_data_type(&exponent)?; + let base_nullable = info.nullable(&base)?; let return_type = self.return_type(&[base_type.clone(), exponent_type.clone()])?; @@ -167,7 +168,7 @@ impl ScalarUDFImpl for PowerFunc { match exponent { Expr::Literal(value, _) - if value == ScalarValue::new_zero(&exponent_type)? => + if value == ScalarValue::new_zero(&exponent_type)? && !base_nullable => { Ok(ExprSimplifyResult::Simplified(lit(ScalarValue::new_one( &return_type, @@ -179,7 +180,10 @@ impl ScalarUDFImpl for PowerFunc { ))) } Expr::ScalarFunction(ScalarFunction { func, mut args }) - if is_log(&func) && args.len() == 2 && base == args[0] => + if is_log(&func) + && args.len() == 2 + && base == args[0] + && !base_nullable => { let b = args.pop().unwrap(); // length checked above let b_type = info.get_data_type(&b)?; diff --git a/datafusion/sqllogictest/test_files/math.slt b/datafusion/sqllogictest/test_files/math.slt index 999709dfe77ea..b6bf51dd4799a 100644 --- a/datafusion/sqllogictest/test_files/math.slt +++ b/datafusion/sqllogictest/test_files/math.slt @@ -1182,6 +1182,55 @@ logical_plan 02)--TableScan: aggregate_simple projection=[] physical_plan DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/core/tests/data/aggregate_simple.csv]]}, projection=[NULL as log(NULL,aggregate_simple.c2)], file_type=csv, has_header=true +# Simplification must preserve NULLs from nullable columns +query RRRRR rowsort +SELECT + log(a, 1), + log(a, a), + log(a, power(a, b)), + power(a, 0), + power(a, log(a, b)) +FROM (VALUES (NULL::double, 2.0::double), (2.0, 3.0)) AS t(a, b); +---- +0 1 3 1 3 +NULL NULL NULL NULL NULL + +# Nullable bases must remain in the optimized plan so they can propagate NULL +query TT +EXPLAIN SELECT + log(a, 1) AS l1, + log(a, a) AS la, + power(a, 0) AS p0, + power(a, log(a, b)) AS pl +FROM (VALUES (NULL::double, 2.0::double), (2.0, 3.0)) AS t(a, b); +---- +logical_plan +01)Projection: log(t.a, Float64(1)) AS l1, log(t.a, t.a) AS la, power(t.a, Float64(0)) AS p0, power(t.a, log(t.a, t.b)) AS pl +02)--SubqueryAlias: t +03)----Projection: column1 AS a, column2 AS b +04)------Values: (Float64(NULL) AS NULL, Float64(2)), (Float64(2), Float64(3)) +physical_plan +01)ProjectionExec: expr=[log(column1@0, 1) as l1, log(column1@0, column1@0) as la, power(column1@0, 0) as p0, power(column1@0, log(column1@0, column2@1)) as pl] +02)--DataSourceExec: partitions=1, partition_sizes=[1] + +# Non-nullable bases still use the existing simplifications +query TT +EXPLAIN SELECT + log(a, 1) AS l1, + log(a, a) AS la, + power(a, 0) AS p0, + power(a, log(a, b)) AS pl +FROM (VALUES (2.0::double, 3.0::double)) AS t(a, b); +---- +logical_plan +01)Projection: Float64(0) AS l1, Float64(1) AS la, Float64(1) AS p0, t.b AS pl +02)--SubqueryAlias: t +03)----Projection: column2 AS b +04)------Values: (Float64(2), Float64(3)) +physical_plan +01)ProjectionExec: expr=[0 as l1, 1 as la, 1 as p0, column2@1 as pl] +02)--DataSourceExec: partitions=1, partition_sizes=[1] + # Float 16/32/64 for log query RT SELECT log(2.5, arrow_cast(10.9, 'Float16')), arrow_typeof(log(2.5, arrow_cast(10.9, 'Float16')));