This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-datafusion.git
The following commit(s) were added to refs/heads/master by this push:
new d7c0e420c remove type coercion in the binary physical expr (#3396)
d7c0e420c is described below
commit d7c0e420ce6362a253e1f55a873a0732052e0e2f
Author: Kun Liu <[email protected]>
AuthorDate: Sat Sep 24 19:51:12 2022 +0800
remove type coercion in the binary physical expr (#3396)
* remove type coercion binary from phy
* fix test case
* revert the fix for #3387
* type coercion before simplify expression
* complete remove the type coercion in the physical plan
* refactor
* merge master
* refactor
* do type coercion in the simplify expression
* Add comments
* fix: fmt
Co-authored-by: Andrew Lamb <[email protected]>
---
datafusion/core/src/execution/context.rs | 4 +
.../src/physical_optimizer/aggregate_statistics.rs | 5 +-
.../core/src/physical_plan/file_format/parquet.rs | 22 +-
datafusion/core/src/physical_plan/planner.rs | 4 +-
datafusion/core/tests/sql/aggregates.rs | 10 +-
datafusion/core/tests/sql/decimal.rs | 114 +++++-----
datafusion/core/tests/sql/predicates.rs | 13 +-
datafusion/core/tests/sql/select.rs | 4 +-
datafusion/expr/src/binary_rule.rs | 229 +++++++++++++++++++++
datafusion/optimizer/src/simplify_expressions.rs | 96 ++++++---
datafusion/optimizer/src/type_coercion.rs | 57 +++--
datafusion/optimizer/tests/integration-test.rs | 12 +-
datafusion/physical-expr/src/expressions/binary.rs | 211 +++++++++----------
13 files changed, 529 insertions(+), 252 deletions(-)
diff --git a/datafusion/core/src/execution/context.rs
b/datafusion/core/src/execution/context.rs
index ec6374d7a..27476313c 100644
--- a/datafusion/core/src/execution/context.rs
+++ b/datafusion/core/src/execution/context.rs
@@ -1454,7 +1454,11 @@ impl SessionState {
rules.push(Arc::new(FilterNullJoinKeys::default()));
}
rules.push(Arc::new(ReduceOuterJoin::new()));
+ // TODO: https://github.com/apache/arrow-datafusion/issues/3557
+ // remove this, after the issue fixed.
rules.push(Arc::new(TypeCoercion::new()));
+ // after the type coercion, can do simplify expression again
+ rules.push(Arc::new(SimplifyExpressions::new()));
rules.push(Arc::new(FilterPushDown::new()));
rules.push(Arc::new(LimitPushDown::new()));
rules.push(Arc::new(SingleDistinctToGroupBy::new()));
diff --git a/datafusion/core/src/physical_optimizer/aggregate_statistics.rs
b/datafusion/core/src/physical_optimizer/aggregate_statistics.rs
index 4a941ec4b..bb1e49cf3 100644
--- a/datafusion/core/src/physical_optimizer/aggregate_statistics.rs
+++ b/datafusion/core/src/physical_optimizer/aggregate_statistics.rs
@@ -261,6 +261,7 @@ mod tests {
use arrow::array::{Int32Array, Int64Array};
use arrow::datatypes::{DataType, Field, Schema};
use arrow::record_batch::RecordBatch;
+ use datafusion_physical_expr::expressions::cast;
use datafusion_physical_expr::PhysicalExpr;
use crate::error::Result;
@@ -525,7 +526,7 @@ mod tests {
expressions::binary(
expressions::col("a", &schema)?,
Operator::Gt,
- expressions::lit(1u32),
+ cast(expressions::lit(1u32), &schema, DataType::Int32)?,
&schema,
)?,
source,
@@ -568,7 +569,7 @@ mod tests {
expressions::binary(
expressions::col("a", &schema)?,
Operator::Gt,
- expressions::lit(1u32),
+ cast(expressions::lit(1u32), &schema, DataType::Int32)?,
&schema,
)?,
source,
diff --git a/datafusion/core/src/physical_plan/file_format/parquet.rs
b/datafusion/core/src/physical_plan/file_format/parquet.rs
index f4c52e5dc..ff6507c7f 100644
--- a/datafusion/core/src/physical_plan/file_format/parquet.rs
+++ b/datafusion/core/src/physical_plan/file_format/parquet.rs
@@ -871,13 +871,14 @@ mod tests {
physical_plan::collect,
};
use arrow::array::Float32Array;
+ use arrow::datatypes::DataType::Decimal128;
use arrow::record_batch::RecordBatch;
use arrow::{
array::{Int64Array, Int8Array, StringArray},
datatypes::{DataType, Field},
};
use chrono::{TimeZone, Utc};
- use datafusion_expr::{col, lit};
+ use datafusion_expr::{cast, col, lit};
use futures::StreamExt;
use object_store::local::LocalFileSystem;
use object_store::path::Path;
@@ -1768,6 +1769,7 @@ mod tests {
// In this case, construct four types of statistics to filtered with
the decimal predication.
// INT32: c1 > 5, the c1 is decimal(9,2)
+ // The type of scalar value if decimal(9,2), don't need to do cast
let expr = col("c1").gt(lit(ScalarValue::Decimal128(Some(500), 9, 2)));
let schema =
Schema::new(vec![Field::new("c1", DataType::Decimal128(9, 2),
false)]);
@@ -1809,11 +1811,15 @@ mod tests {
);
// INT32: c1 > 5, but parquet decimal type has different precision or
scale to arrow decimal
+ // The c1 type is decimal(9,0) in the parquet file, and the type of
scalar is decimal(5,2).
+ // We should convert all type to the coercion type, which is
decimal(11,2)
// The decimal of arrow is decimal(5,2), the decimal of parquet is
decimal(9,0)
- let expr = col("c1").gt(lit(ScalarValue::Decimal128(Some(500), 5, 2)));
+ let expr = cast(col("c1"), DataType::Decimal128(11, 2)).gt(cast(
+ lit(ScalarValue::Decimal128(Some(500), 5, 2)),
+ Decimal128(11, 2),
+ ));
let schema =
- Schema::new(vec![Field::new("c1", DataType::Decimal128(5, 2),
false)]);
- // The decimal of parquet is decimal(9,0)
+ Schema::new(vec![Field::new("c1", DataType::Decimal128(9, 0),
false)]);
let schema_descr = get_test_schema_descr(vec![(
"c1",
PhysicalType::INT32,
@@ -1901,11 +1907,13 @@ mod tests {
vec![1]
);
- // FIXED_LENGTH_BYTE_ARRAY: c1 = 100, the c1 is decimal(28,2)
+ // FIXED_LENGTH_BYTE_ARRAY: c1 = decimal128(100000, 28, 3), the c1 is
decimal(18,2)
// the type of parquet is decimal(18,2)
- let expr = col("c1").eq(lit(ScalarValue::Decimal128(Some(100000), 28,
3)));
let schema =
- Schema::new(vec![Field::new("c1", DataType::Decimal128(18, 3),
false)]);
+ Schema::new(vec![Field::new("c1", DataType::Decimal128(18, 2),
false)]);
+ // cast the type of c1 to decimal(28,3)
+ let left = cast(col("c1"), DataType::Decimal128(28, 3));
+ let expr = left.eq(lit(ScalarValue::Decimal128(Some(100000), 28, 3)));
let schema_descr = get_test_schema_descr(vec![(
"c1",
PhysicalType::FIXED_LEN_BYTE_ARRAY,
diff --git a/datafusion/core/src/physical_plan/planner.rs
b/datafusion/core/src/physical_plan/planner.rs
index 48b78722f..8b247a452 100644
--- a/datafusion/core/src/physical_plan/planner.rs
+++ b/datafusion/core/src/physical_plan/planner.rs
@@ -1685,7 +1685,7 @@ mod tests {
use crate::execution::runtime_env::RuntimeEnv;
use crate::logical_plan::plan::Extension;
use crate::physical_plan::{
- expressions, DisplayFormatType, Partitioning, Statistics,
+ expressions, DisplayFormatType, Partitioning, PhysicalPlanner,
Statistics,
};
use crate::prelude::{SessionConfig, SessionContext};
use crate::scalar::ScalarValue;
@@ -1736,10 +1736,10 @@ mod tests {
let exec_plan = plan(&logical_plan).await?;
// verify that the plan correctly casts u8 to i64
+ // the cast from u8 to i64 for literal will be simplified, and get
lit(int64(5))
// the cast here is implicit so has CastOptions with safe=true
let expected = "BinaryExpr { left: Column { name: \"c7\", index: 2 },
op: Lt, right: Literal { value: Int64(5) } }";
assert!(format!("{:?}", exec_plan).contains(expected));
-
Ok(())
}
diff --git a/datafusion/core/tests/sql/aggregates.rs
b/datafusion/core/tests/sql/aggregates.rs
index b7f24992c..357addbc0 100644
--- a/datafusion/core/tests/sql/aggregates.rs
+++ b/datafusion/core/tests/sql/aggregates.rs
@@ -1834,11 +1834,11 @@ async fn aggregate_avg_add() -> Result<()> {
assert_eq!(results.len(), 1);
let expected = vec![
-
"+--------------+---------------------------+---------------------------+---------------------------+",
- "| AVG(test.c1) | AVG(test.c1) + Float64(1) | AVG(test.c1) +
Float64(2) | Float64(1) + AVG(test.c1) |",
-
"+--------------+---------------------------+---------------------------+---------------------------+",
- "| 1.5 | 2.5 | 3.5
| 2.5 |",
-
"+--------------+---------------------------+---------------------------+---------------------------+",
+
"+--------------+-------------------------+-------------------------+-------------------------+",
+ "| AVG(test.c1) | AVG(test.c1) + Int64(1) | AVG(test.c1) + Int64(2) |
Int64(1) + AVG(test.c1) |",
+
"+--------------+-------------------------+-------------------------+-------------------------+",
+ "| 1.5 | 2.5 | 3.5 |
2.5 |",
+
"+--------------+-------------------------+-------------------------+-------------------------+",
];
assert_batches_sorted_eq!(expected, &results);
diff --git a/datafusion/core/tests/sql/decimal.rs
b/datafusion/core/tests/sql/decimal.rs
index 0898be62c..9d32f1c31 100644
--- a/datafusion/core/tests/sql/decimal.rs
+++ b/datafusion/core/tests/sql/decimal.rs
@@ -376,25 +376,25 @@ async fn decimal_arithmetic_op() -> Result<()> {
actual[0].schema().field(0).data_type()
);
let expected = vec![
- "+----------------------------------------------------+",
- "| decimal_simple.c1 + Decimal128(Some(1000000),27,6) |",
- "+----------------------------------------------------+",
- "| 1.000010 |",
- "| 1.000020 |",
- "| 1.000020 |",
- "| 1.000030 |",
- "| 1.000030 |",
- "| 1.000030 |",
- "| 1.000040 |",
- "| 1.000040 |",
- "| 1.000040 |",
- "| 1.000040 |",
- "| 1.000050 |",
- "| 1.000050 |",
- "| 1.000050 |",
- "| 1.000050 |",
- "| 1.000050 |",
- "+----------------------------------------------------+",
+ "+------------------------------+",
+ "| decimal_simple.c1 + Int64(1) |",
+ "+------------------------------+",
+ "| 1.000010 |",
+ "| 1.000020 |",
+ "| 1.000020 |",
+ "| 1.000030 |",
+ "| 1.000030 |",
+ "| 1.000030 |",
+ "| 1.000040 |",
+ "| 1.000040 |",
+ "| 1.000040 |",
+ "| 1.000040 |",
+ "| 1.000050 |",
+ "| 1.000050 |",
+ "| 1.000050 |",
+ "| 1.000050 |",
+ "| 1.000050 |",
+ "+------------------------------+",
];
assert_batches_eq!(expected, &actual);
// array decimal(10,6) + array decimal(12,7) => decimal(13,7)
@@ -434,25 +434,25 @@ async fn decimal_arithmetic_op() -> Result<()> {
actual[0].schema().field(0).data_type()
);
let expected = vec![
- "+----------------------------------------------------+",
- "| decimal_simple.c1 - Decimal128(Some(1000000),27,6) |",
- "+----------------------------------------------------+",
- "| -0.999990 |",
- "| -0.999980 |",
- "| -0.999980 |",
- "| -0.999970 |",
- "| -0.999970 |",
- "| -0.999970 |",
- "| -0.999960 |",
- "| -0.999960 |",
- "| -0.999960 |",
- "| -0.999960 |",
- "| -0.999950 |",
- "| -0.999950 |",
- "| -0.999950 |",
- "| -0.999950 |",
- "| -0.999950 |",
- "+----------------------------------------------------+",
+ "+------------------------------+",
+ "| decimal_simple.c1 - Int64(1) |",
+ "+------------------------------+",
+ "| -0.999990 |",
+ "| -0.999980 |",
+ "| -0.999980 |",
+ "| -0.999970 |",
+ "| -0.999970 |",
+ "| -0.999970 |",
+ "| -0.999960 |",
+ "| -0.999960 |",
+ "| -0.999960 |",
+ "| -0.999960 |",
+ "| -0.999950 |",
+ "| -0.999950 |",
+ "| -0.999950 |",
+ "| -0.999950 |",
+ "| -0.999950 |",
+ "+------------------------------+",
];
assert_batches_eq!(expected, &actual);
@@ -492,25 +492,25 @@ async fn decimal_arithmetic_op() -> Result<()> {
actual[0].schema().field(0).data_type()
);
let expected = vec![
- "+-----------------------------------------------------+",
- "| decimal_simple.c1 * Decimal128(Some(20000000),31,6) |",
- "+-----------------------------------------------------+",
- "| 0.000200 |",
- "| 0.000400 |",
- "| 0.000400 |",
- "| 0.000600 |",
- "| 0.000600 |",
- "| 0.000600 |",
- "| 0.000800 |",
- "| 0.000800 |",
- "| 0.000800 |",
- "| 0.000800 |",
- "| 0.001000 |",
- "| 0.001000 |",
- "| 0.001000 |",
- "| 0.001000 |",
- "| 0.001000 |",
- "+-----------------------------------------------------+",
+ "+-------------------------------+",
+ "| decimal_simple.c1 * Int64(20) |",
+ "+-------------------------------+",
+ "| 0.000200 |",
+ "| 0.000400 |",
+ "| 0.000400 |",
+ "| 0.000600 |",
+ "| 0.000600 |",
+ "| 0.000600 |",
+ "| 0.000800 |",
+ "| 0.000800 |",
+ "| 0.000800 |",
+ "| 0.000800 |",
+ "| 0.001000 |",
+ "| 0.001000 |",
+ "| 0.001000 |",
+ "| 0.001000 |",
+ "| 0.001000 |",
+ "+-------------------------------+",
];
assert_batches_eq!(expected, &actual);
diff --git a/datafusion/core/tests/sql/predicates.rs
b/datafusion/core/tests/sql/predicates.rs
index 5b1fa92e5..67432b08f 100644
--- a/datafusion/core/tests/sql/predicates.rs
+++ b/datafusion/core/tests/sql/predicates.rs
@@ -389,6 +389,7 @@ async fn csv_in_set_test() -> Result<()> {
#[tokio::test]
async fn multiple_or_predicates() -> Result<()> {
+ // TODO https://github.com/apache/arrow-datafusion/issues/3587
let ctx = SessionContext::new();
register_tpch_csv(&ctx, "lineitem").await?;
register_tpch_csv(&ctx, "part").await?;
@@ -424,15 +425,13 @@ async fn multiple_or_predicates() -> Result<()> {
let plan = state.optimize(&plan)?;
// Note that we expect `#part.p_partkey = #lineitem.l_partkey` to have been
// factored out and appear only once in the following plan
- let expected =vec![
+ let expected = vec![
"Explain [plan_type:Utf8, plan:Utf8]",
" Projection: #lineitem.l_partkey [l_partkey:Int64]",
- " Projection: #part.p_size >= Int32(1) AS #part.p_size >=
Int32(1)Int32(1)#part.p_size, #lineitem.l_partkey, #lineitem.l_quantity,
#part.p_brand, #part.p_size [#part.p_size >=
Int32(1)Int32(1)#part.p_size:Boolean;N, l_partkey:Int64,
l_quantity:Decimal128(15, 2), p_brand:Utf8, p_size:Int32]",
- " Filter: #part.p_brand = Utf8(\"Brand#12\") AND
#lineitem.l_quantity >= Decimal128(Some(100),15,2) AND #lineitem.l_quantity <=
Decimal128(Some(1100),15,2) AND #part.p_size <= Int32(5) OR #part.p_brand =
Utf8(\"Brand#23\") AND #lineitem.l_quantity >= Decimal128(Some(1000),15,2) AND
#lineitem.l_quantity <= Decimal128(Some(2000),15,2) AND #part.p_size <=
Int32(10) OR #part.p_brand = Utf8(\"Brand#34\") AND #lineitem.l_quantity >=
Decimal128(Some(2000),15,2) AND #lineitem.l_quan [...]
- " Inner Join: #lineitem.l_partkey = #part.p_partkey
[l_partkey:Int64, l_quantity:Decimal128(15, 2), p_partkey:Int64, p_brand:Utf8,
p_size:Int32]",
- " TableScan: lineitem projection=[l_partkey, l_quantity]
[l_partkey:Int64, l_quantity:Decimal128(15, 2)]",
- " Filter: #part.p_size >= Int32(1) [p_partkey:Int64,
p_brand:Utf8, p_size:Int32]",
- " TableScan: part projection=[p_partkey, p_brand, p_size],
partial_filters=[#part.p_size >= Int32(1)] [p_partkey:Int64, p_brand:Utf8,
p_size:Int32]",
+ " Filter: #part.p_brand = Utf8(\"Brand#12\") AND
#lineitem.l_quantity >= Decimal128(Some(100),15,2) AND #lineitem.l_quantity <=
Decimal128(Some(1100),15,2) AND CAST(#part.p_size AS Int64) BETWEEN Int64(1)
AND Int64(5) OR #part.p_brand = Utf8(\"Brand#23\") AND #lineitem.l_quantity >=
Decimal128(Some(1000),15,2) AND #lineitem.l_quantity <=
Decimal128(Some(2000),15,2) AND CAST(#part.p_size AS Int64) BETWEEN Int64(1)
AND Int64(10) OR #part.p_brand = Utf8(\"Brand#34\") AND #lineite [...]
+ " Inner Join: #lineitem.l_partkey = #part.p_partkey
[l_partkey:Int64, l_quantity:Decimal128(15, 2), p_partkey:Int64, p_brand:Utf8,
p_size:Int32]",
+ " TableScan: lineitem projection=[l_partkey, l_quantity]
[l_partkey:Int64, l_quantity:Decimal128(15, 2)]",
+ " TableScan: part projection=[p_partkey, p_brand, p_size]
[p_partkey:Int64, p_brand:Utf8, p_size:Int32]",
];
let formatted = plan.display_indent_schema().to_string();
let actual: Vec<&str> = formatted.trim().lines().collect();
diff --git a/datafusion/core/tests/sql/select.rs
b/datafusion/core/tests/sql/select.rs
index 461764e1e..5823f4c0f 100644
--- a/datafusion/core/tests/sql/select.rs
+++ b/datafusion/core/tests/sql/select.rs
@@ -523,12 +523,12 @@ async fn use_between_expression_in_select_query() ->
Result<()> {
.unwrap()
.to_string();
+ // TODO https://github.com/apache/arrow-datafusion/issues/3587
// Only test that the projection exprs are correct, rather than entire
output
let needle = "ProjectionExec: expr=[c1@0 >= 2 AND c1@0 <= 3 as test.c1
BETWEEN Int64(2) AND Int64(3)]";
assert_contains!(&formatted, needle);
- let needle = "Projection: #test.c1 >= Int64(2) AND #test.c1 <= Int64(3)";
+ let needle = "Projection: #test.c1 BETWEEN Int64(2) AND Int64(3)";
assert_contains!(&formatted, needle);
-
Ok(())
}
diff --git a/datafusion/expr/src/binary_rule.rs
b/datafusion/expr/src/binary_rule.rs
index c3c1c4290..77e02cf3a 100644
--- a/datafusion/expr/src/binary_rule.rs
+++ b/datafusion/expr/src/binary_rule.rs
@@ -298,6 +298,8 @@ fn mathematics_numerical_coercion(
};
// same type => all good
+ // TODO: remove this
+ // bug: https://github.com/apache/arrow-datafusion/issues/3387
if lhs_type == rhs_type {
return Some(lhs_type.clone());
}
@@ -632,6 +634,7 @@ fn null_coercion(lhs_type: &DataType, rhs_type: &DataType)
-> Option<DataType> {
_ => None,
}
}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -799,4 +802,230 @@ mod tests {
Some(rhs_type.clone())
);
}
+
+ macro_rules! test_coercion_binary_rule {
+ ($A_TYPE:expr, $B_TYPE:expr, $OP:expr, $C_TYPE:expr) => {{
+ let result = coerce_types(&$A_TYPE, &$OP, &$B_TYPE)?;
+ assert_eq!(result, $C_TYPE);
+ }};
+ }
+
+ #[test]
+ fn test_type_coercion() -> Result<()> {
+ test_coercion_binary_rule!(
+ DataType::Utf8,
+ DataType::Utf8,
+ Operator::Like,
+ DataType::Utf8
+ );
+ test_coercion_binary_rule!(
+ DataType::Utf8,
+ DataType::Date32,
+ Operator::Eq,
+ DataType::Date32
+ );
+ test_coercion_binary_rule!(
+ DataType::Utf8,
+ DataType::Date64,
+ Operator::Lt,
+ DataType::Date64
+ );
+ test_coercion_binary_rule!(
+ DataType::Utf8,
+ DataType::Utf8,
+ Operator::RegexMatch,
+ DataType::Utf8
+ );
+ test_coercion_binary_rule!(
+ DataType::Utf8,
+ DataType::Utf8,
+ Operator::RegexNotMatch,
+ DataType::Utf8
+ );
+ test_coercion_binary_rule!(
+ DataType::Utf8,
+ DataType::Utf8,
+ Operator::RegexNotIMatch,
+ DataType::Utf8
+ );
+ test_coercion_binary_rule!(
+ DataType::Int16,
+ DataType::Int64,
+ Operator::BitwiseAnd,
+ DataType::Int64
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn test_type_coercion_arithmetic() -> Result<()> {
+ // integer
+ test_coercion_binary_rule!(
+ DataType::Int32,
+ DataType::UInt32,
+ Operator::Plus,
+ DataType::Int32
+ );
+ test_coercion_binary_rule!(
+ DataType::Int32,
+ DataType::UInt16,
+ Operator::Minus,
+ DataType::Int32
+ );
+ test_coercion_binary_rule!(
+ DataType::Int8,
+ DataType::Int64,
+ Operator::Multiply,
+ DataType::Int64
+ );
+ // float
+ test_coercion_binary_rule!(
+ DataType::Float32,
+ DataType::Int32,
+ Operator::Plus,
+ DataType::Float32
+ );
+ test_coercion_binary_rule!(
+ DataType::Float32,
+ DataType::Float64,
+ Operator::Multiply,
+ DataType::Float64
+ );
+ // decimal
+ // bug: https://github.com/apache/arrow-datafusion/issues/3387 will be
fixed in the next pr
+ // test_coercion_binary_rule!(
+ // DataType::Decimal128(10, 2),
+ // DataType::Decimal128(10, 2),
+ // Operator::Plus,
+ // DataType::Decimal128(11, 2)
+ // );
+ test_coercion_binary_rule!(
+ DataType::Int32,
+ DataType::Decimal128(10, 2),
+ Operator::Plus,
+ DataType::Decimal128(13, 2)
+ );
+ test_coercion_binary_rule!(
+ DataType::Int32,
+ DataType::Decimal128(10, 2),
+ Operator::Minus,
+ DataType::Decimal128(13, 2)
+ );
+ test_coercion_binary_rule!(
+ DataType::Int32,
+ DataType::Decimal128(10, 2),
+ Operator::Multiply,
+ DataType::Decimal128(21, 2)
+ );
+ test_coercion_binary_rule!(
+ DataType::Int32,
+ DataType::Decimal128(10, 2),
+ Operator::Divide,
+ DataType::Decimal128(23, 11)
+ );
+ test_coercion_binary_rule!(
+ DataType::Int32,
+ DataType::Decimal128(10, 2),
+ Operator::Modulo,
+ DataType::Decimal128(10, 2)
+ );
+ // TODO add other data type
+ Ok(())
+ }
+
+ #[test]
+ fn test_type_coercion_compare() -> Result<()> {
+ // boolean
+ test_coercion_binary_rule!(
+ DataType::Boolean,
+ DataType::Boolean,
+ Operator::Eq,
+ DataType::Boolean
+ );
+ // float
+ test_coercion_binary_rule!(
+ DataType::Float32,
+ DataType::Int64,
+ Operator::Eq,
+ DataType::Float32
+ );
+ test_coercion_binary_rule!(
+ DataType::Float32,
+ DataType::Float64,
+ Operator::GtEq,
+ DataType::Float64
+ );
+ // signed integer
+ test_coercion_binary_rule!(
+ DataType::Int8,
+ DataType::Int32,
+ Operator::LtEq,
+ DataType::Int32
+ );
+ test_coercion_binary_rule!(
+ DataType::Int64,
+ DataType::Int32,
+ Operator::LtEq,
+ DataType::Int64
+ );
+ // unsigned integer
+ test_coercion_binary_rule!(
+ DataType::UInt32,
+ DataType::UInt8,
+ Operator::Gt,
+ DataType::UInt32
+ );
+ // numeric/decimal
+ test_coercion_binary_rule!(
+ DataType::Int64,
+ DataType::Decimal128(10, 0),
+ Operator::Eq,
+ DataType::Decimal128(20, 0)
+ );
+ test_coercion_binary_rule!(
+ DataType::Int64,
+ DataType::Decimal128(10, 2),
+ Operator::Lt,
+ DataType::Decimal128(22, 2)
+ );
+ test_coercion_binary_rule!(
+ DataType::Float64,
+ DataType::Decimal128(10, 3),
+ Operator::Gt,
+ DataType::Decimal128(30, 15)
+ );
+ test_coercion_binary_rule!(
+ DataType::Int64,
+ DataType::Decimal128(10, 0),
+ Operator::Eq,
+ DataType::Decimal128(20, 0)
+ );
+ test_coercion_binary_rule!(
+ DataType::Decimal128(14, 2),
+ DataType::Decimal128(10, 3),
+ Operator::GtEq,
+ DataType::Decimal128(15, 3)
+ );
+
+ // TODO add other data type
+ Ok(())
+ }
+
+ #[test]
+ fn test_type_coercion_logical_op() -> Result<()> {
+ test_coercion_binary_rule!(
+ DataType::Boolean,
+ DataType::Boolean,
+ Operator::And,
+ DataType::Boolean
+ );
+
+ test_coercion_binary_rule!(
+ DataType::Boolean,
+ DataType::Boolean,
+ Operator::Or,
+ DataType::Boolean
+ );
+ Ok(())
+ }
}
diff --git a/datafusion/optimizer/src/simplify_expressions.rs
b/datafusion/optimizer/src/simplify_expressions.rs
index a9c0d4b1f..6ab0eb87c 100644
--- a/datafusion/optimizer/src/simplify_expressions.rs
+++ b/datafusion/optimizer/src/simplify_expressions.rs
@@ -18,6 +18,7 @@
//! Simplify expressions optimizer rule
use crate::expr_simplifier::ExprSimplifiable;
+use crate::type_coercion::TypeCoercionRewriter;
use crate::{expr_simplifier::SimplifyInfo, OptimizerConfig, OptimizerRule};
use arrow::array::new_null_array;
use arrow::datatypes::{DataType, Field, Schema};
@@ -33,6 +34,7 @@ use datafusion_expr::{
ColumnarValue, Expr, ExprSchemable, Operator, Volatility,
};
use datafusion_physical_expr::{create_physical_expr,
execution_props::ExecutionProps};
+use std::sync::Arc;
/// Provides simplification information based on schema and properties
pub(crate) struct SimplifyContext<'a, 'b> {
@@ -360,6 +362,9 @@ pub struct ConstEvaluator<'a> {
execution_props: &'a ExecutionProps,
input_schema: DFSchema,
input_batch: RecordBatch,
+ // Needed until we ensure type coercion is done before any optimizations
+ // https://github.com/apache/arrow-datafusion/issues/3557
+ type_coercion_helper: TypeCoercionRewriter,
}
impl<'a> ExprRewriter for ConstEvaluator<'a> {
@@ -411,16 +416,17 @@ impl<'a> ConstEvaluator<'a> {
static DUMMY_COL_NAME: &str = ".";
let schema = Schema::new(vec![Field::new(DUMMY_COL_NAME,
DataType::Null, true)]);
let input_schema = DFSchema::try_from(schema.clone())?;
-
// Need a single "input" row to produce a single output row
let col = new_null_array(&DataType::Null, 1);
let input_batch = RecordBatch::try_new(std::sync::Arc::new(schema),
vec![col])?;
+ let type_coercion =
TypeCoercionRewriter::new(Arc::new(input_schema.clone()));
Ok(Self {
can_evaluate: vec![],
execution_props,
input_schema,
input_batch,
+ type_coercion_helper: type_coercion,
})
}
@@ -484,11 +490,20 @@ impl<'a> ConstEvaluator<'a> {
}
/// Internal helper to evaluates an Expr
- pub(crate) fn evaluate_to_scalar(&self, expr: Expr) -> Result<ScalarValue>
{
+ pub(crate) fn evaluate_to_scalar(&mut self, expr: Expr) ->
Result<ScalarValue> {
if let Expr::Literal(s) = expr {
return Ok(s);
}
+ // TODO: https://github.com/apache/arrow-datafusion/issues/3582
+ // TODO: https://github.com/apache/arrow-datafusion/issues/3556
+ // Do the type coercion in the simplify expression
+ // this is just a work around for removing the type coercion in the
physical phase
+ // we need to support eval the result without the physical expr.
+ // If we don't do the type coercion, we will meet the
+ // https://github.com/apache/arrow-datafusion/issues/3556 when create
the physical expr
+ // to try evaluate the result.
+ let expr = expr.rewrite(&mut self.type_coercion_helper)?;
let phys_expr = create_physical_expr(
&expr,
&self.input_schema,
@@ -804,24 +819,28 @@ impl<'a, S: SimplifyInfo> ExprRewriter for Simplifier<'a,
S> {
//
// Rules for Between
//
+ // TODO https://github.com/apache/arrow-datafusion/issues/3587
+ // we remove between optimization temporarily, and will recover it
after above issue fixed.
+ // We should check compatibility for `expr` `low` and `high` expr
first.
+ // The rule only can work, when these three exprs can be casted to
a same data type.
// a between 3 and 5 --> a >= 3 AND a <=5
// a not between 3 and 5 --> a < 3 OR a > 5
- Between {
- expr,
- low,
- high,
- negated,
- } => {
- if negated {
- let l = *expr.clone();
- let r = *expr;
- or(l.lt(*low), r.gt(*high))
- } else {
- and(expr.clone().gt_eq(*low), expr.lt_eq(*high))
- }
- }
+ // Between {
+ // expr,
+ // low,
+ // high,
+ // negated,
+ // } => {
+ // if negated {
+ // let l = *expr.clone();
+ // let r = *expr;
+ // or(l.lt(*low), r.gt(*high))
+ // } else {
+ // and(expr.clone().gt_eq(*low), expr.lt_eq(*high))
+ // }
+ // }
expr => {
// no additional rewrites possible
expr
@@ -1107,6 +1126,12 @@ mod tests {
assert_eq!(simplify(expr_eq), lit(true));
}
+ #[test]
+ fn test_simplify_with_type_coercion() {
+ let expr_plus = binary_expr(lit(1_i32), Operator::Plus, lit(1_i64));
+ assert_eq!(simplify(expr_plus), lit(2_i64));
+ }
+
// ------------------------------
// --- ConstEvaluator tests -----
// ------------------------------
@@ -1186,7 +1211,6 @@ mod tests {
let ts_nanos = 1599566400000000000i64;
let time = chrono::Utc.timestamp_nanos(ts_nanos);
let ts_string = "2020-09-08T12:05:00+00:00";
-
// now() --> ts
test_evaluate_with_start_time(now_expr(),
lit_timestamp_nano(ts_nanos), &time);
@@ -1194,7 +1218,7 @@ mod tests {
let expr = cast_to_int64_expr(now_expr()) + lit(100);
test_evaluate_with_start_time(expr, lit(ts_nanos + 100), &time);
- // now() < cast(to_timestamp(...) as int) + 50000 ---> true
+ // CAST(now() as int64) < cast(to_timestamp(...) as int64) + 50000
---> true
let expr = cast_to_int64_expr(now_expr())
.lt(cast_to_int64_expr(to_timestamp_expr(ts_string)) + lit(50000));
test_evaluate_with_start_time(expr, lit(true), &time);
@@ -1517,7 +1541,7 @@ mod tests {
expr: None,
when_then_expr: vec![
(Box::new(col("c1")), Box::new(lit(true)),),
- (Box::new(col("c2")), Box::new(lit(false)),)
+ (Box::new(col("c2")), Box::new(lit(false)),),
],
else_expr: Some(Box::new(lit(true))),
})),
@@ -1536,7 +1560,7 @@ mod tests {
expr: None,
when_then_expr: vec![
(Box::new(col("c1")), Box::new(lit(true)),),
- (Box::new(col("c2")), Box::new(lit(false)),)
+ (Box::new(col("c2")), Box::new(lit(false)),),
],
else_expr: Some(Box::new(lit(true))),
})),
@@ -1564,6 +1588,8 @@ mod tests {
// null || false is always null
assert_eq!(simplify(lit_bool_null().or(lit(false))), lit_bool_null(),);
+ // TODO change the result
+ // https://github.com/apache/arrow-datafusion/issues/3587
// ( c1 BETWEEN Int32(0) AND Int32(10) ) OR Boolean(NULL)
// it can be either NULL or TRUE depending on the value of `c1
BETWEEN Int32(0) AND Int32(10)`
// and should not be rewritten
@@ -1573,13 +1599,15 @@ mod tests {
low: Box::new(lit(0)),
high: Box::new(lit(10)),
};
+ let between_expr = expr.clone();
let expr = expr.or(lit_bool_null());
let result = simplify(expr);
- let expected_expr = or(
- and(col("c1").gt_eq(lit(0)), col("c1").lt_eq(lit(10))),
- lit_bool_null(),
- );
+ let expected_expr = or(between_expr, lit_bool_null());
+ // let expected_expr = or(
+ // and(col("c1").gt_eq(lit(0)), col("c1").lt_eq(lit(10))),
+ // lit_bool_null(),
+ //);
assert_eq!(expected_expr, result);
}
@@ -1602,6 +1630,8 @@ mod tests {
// null && false is always false
assert_eq!(simplify(lit_bool_null().and(lit(false))), lit(false),);
+ // TODO change the result
+ // https://github.com/apache/arrow-datafusion/issues/3587
// c1 BETWEEN Int32(0) AND Int32(10) AND Boolean(NULL)
// it can be either NULL or FALSE depending on the value of `c1
BETWEEN Int32(0) AND Int32(10)`
// and the Boolean(NULL) should remain
@@ -1611,17 +1641,21 @@ mod tests {
low: Box::new(lit(0)),
high: Box::new(lit(10)),
};
+ let between_expr = expr.clone();
let expr = expr.and(lit_bool_null());
let result = simplify(expr);
- let expected_expr = and(
- and(col("c1").gt_eq(lit(0)), col("c1").lt_eq(lit(10))),
- lit_bool_null(),
- );
+ let expected_expr = and(between_expr, lit_bool_null());
+ // let expected_expr = and(
+ // and(col("c1").gt_eq(lit(0)), col("c1").lt_eq(lit(10))),
+ // lit_bool_null(),
+ // );
assert_eq!(expected_expr, result);
}
#[test]
+ #[ignore]
+ // https://github.com/apache/arrow-datafusion/issues/3587
fn simplify_expr_between() {
// c2 between 3 and 4 is c2 >= 3 and c2 <= 4
let expr = Expr::Between {
@@ -2037,7 +2071,7 @@ mod tests {
let ts_string = "2020-09-08T12:05:00+00:00";
let time = chrono::Utc.timestamp_nanos(1599566400000000000i64);
- // now() < cast(to_timestamp(...) as int) + 5000000000
+ // cast(now() as int) < cast(to_timestamp(...) as int) + 5000000000
let plan = LogicalPlanBuilder::from(table_scan)
.filter(
cast_to_int64_expr(now_expr())
@@ -2209,6 +2243,8 @@ mod tests {
}
#[test]
+ #[ignore]
+ // https://github.com/apache/arrow-datafusion/issues/3587
fn simplify_not_between() {
let table_scan = test_table_scan();
let qual = Expr::Between {
@@ -2230,6 +2266,8 @@ mod tests {
}
#[test]
+ #[ignore]
+ // https://github.com/apache/arrow-datafusion/issues/3587
fn simplify_not_not_between() {
let table_scan = test_table_scan();
let qual = Expr::Between {
diff --git a/datafusion/optimizer/src/type_coercion.rs
b/datafusion/optimizer/src/type_coercion.rs
index 0f22c01d4..bf99d61d9 100644
--- a/datafusion/optimizer/src/type_coercion.rs
+++ b/datafusion/optimizer/src/type_coercion.rs
@@ -17,7 +17,6 @@
//! Optimizer rule for type validation and coercion
-use crate::simplify_expressions::ConstEvaluator;
use crate::{OptimizerConfig, OptimizerRule};
use arrow::datatypes::DataType;
use datafusion_common::{DFSchema, DFSchemaRef, DataFusionError, Result};
@@ -30,7 +29,6 @@ use datafusion_expr::{
LogicalPlan, Operator,
};
use datafusion_expr::{ExprSchemable, Signature};
-use datafusion_physical_expr::execution_props::ExecutionProps;
use std::sync::Arc;
#[derive(Default)]
@@ -69,14 +67,8 @@ impl OptimizerRule for TypeCoercion {
},
);
- let mut execution_props = ExecutionProps::new();
- execution_props.query_execution_start_time =
- optimizer_config.query_execution_start_time();
- let const_evaluator = ConstEvaluator::try_new(&execution_props)?;
-
let mut expr_rewrite = TypeCoercionRewriter {
schema: Arc::new(schema),
- const_evaluator,
};
let original_expr_names: Vec<Option<String>> = plan
@@ -110,12 +102,17 @@ impl OptimizerRule for TypeCoercion {
}
}
-struct TypeCoercionRewriter<'a> {
- schema: DFSchemaRef,
- const_evaluator: ConstEvaluator<'a>,
+pub(crate) struct TypeCoercionRewriter {
+ pub(crate) schema: DFSchemaRef,
+}
+
+impl TypeCoercionRewriter {
+ pub(crate) fn new(schema: DFSchemaRef) -> TypeCoercionRewriter {
+ TypeCoercionRewriter { schema }
+ }
}
-impl ExprRewriter for TypeCoercionRewriter<'_> {
+impl ExprRewriter for TypeCoercionRewriter {
fn pre_visit(&mut self, _expr: &Expr) -> Result<RewriteRecursion> {
Ok(RewriteRecursion::Continue)
}
@@ -124,20 +121,20 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
match expr {
Expr::IsTrue(expr) => {
let expr = is_true(get_casted_expr_for_bool_op(&expr,
&self.schema)?);
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
Expr::IsNotTrue(expr) => {
let expr = is_not_true(get_casted_expr_for_bool_op(&expr,
&self.schema)?);
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
Expr::IsFalse(expr) => {
let expr = is_false(get_casted_expr_for_bool_op(&expr,
&self.schema)?);
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
Expr::IsNotFalse(expr) => {
let expr =
is_not_false(get_casted_expr_for_bool_op(&expr,
&self.schema)?);
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
Expr::Like {
negated,
@@ -157,7 +154,7 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
pattern,
escape_char,
};
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
Expr::ILike {
negated,
@@ -177,7 +174,7 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
pattern,
escape_char,
};
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
Expr::IsUnknown(expr) => {
// will convert the
binary(expr,IsNotDistinctFrom,lit(Boolean(None));
@@ -186,7 +183,7 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
let coerced_type =
coerce_types(&left_type, &Operator::IsNotDistinctFrom,
&right_type)?;
let expr = is_unknown(expr.cast_to(&coerced_type,
&self.schema)?);
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
Expr::IsNotUnknown(expr) => {
// will convert the
binary(expr,IsDistinctFrom,lit(Boolean(None));
@@ -195,7 +192,7 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
let coerced_type =
coerce_types(&left_type, &Operator::IsDistinctFrom,
&right_type)?;
let expr = is_not_unknown(expr.cast_to(&coerced_type,
&self.schema)?);
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
Expr::BinaryExpr {
ref left,
@@ -223,7 +220,7 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
right.clone().cast_to(&coerced_type,
&self.schema)?,
),
};
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
}
}
@@ -264,7 +261,7 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
low: Box::new(low.cast_to(&coercion_type, &self.schema)?),
high: Box::new(high.cast_to(&coercion_type,
&self.schema)?),
};
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
Expr::ScalarUDF { fun, args } => {
let new_expr = coerce_arguments_for_signature(
@@ -276,7 +273,7 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
fun,
args: new_expr,
};
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
Expr::InList {
expr,
@@ -309,7 +306,7 @@ impl ExprRewriter for TypeCoercionRewriter<'_> {
list: cast_list_expr,
negated,
};
- expr.rewrite(&mut self.const_evaluator)
+ Ok(expr)
}
}
}
@@ -402,7 +399,7 @@ mod test {
let mut config = OptimizerConfig::default();
let plan = rule.optimize(&plan, &mut config)?;
assert_eq!(
- "Projection: #a < Float64(2)\n EmptyRelation",
+ "Projection: #a < CAST(UInt32(2) AS Float64)\n EmptyRelation",
&format!("{:?}", plan)
);
Ok(())
@@ -430,7 +427,7 @@ mod test {
let mut config = OptimizerConfig::default();
let plan = rule.optimize(&plan, &mut config)?;
assert_eq!(
- "Projection: #a < Float64(2) OR #a < Float64(2)\
+ "Projection: #a < CAST(UInt32(2) AS Float64) OR #a <
CAST(UInt32(2) AS Float64)\
\n EmptyRelation",
&format!("{:?}", plan)
);
@@ -461,7 +458,7 @@ mod test {
let mut config = OptimizerConfig::default();
let plan = rule.optimize(&plan, &mut config)?;
assert_eq!(
- "Projection: Utf8(\"a\")\n EmptyRelation",
+ "Projection: TestScalarUDF(CAST(Int32(123) AS Float32))\n
EmptyRelation",
&format!("{:?}", plan)
);
Ok(())
@@ -540,7 +537,7 @@ mod test {
let mut config = OptimizerConfig::default();
let plan = rule.optimize(&plan, &mut config)?;
assert_eq!(
- "Projection: #a IN ([Int64(1), Int64(4), Int64(8)])\n
EmptyRelation",
+ "Projection: #a IN ([CAST(Int32(1) AS Int64), CAST(Int8(4) AS
Int64), Int64(8)])\n EmptyRelation",
&format!("{:?}", plan)
);
// a in (1,4,8), a is decimal
@@ -558,7 +555,7 @@ mod test {
let plan = LogicalPlan::Projection(Projection::try_new(vec![expr],
empty, None)?);
let plan = rule.optimize(&plan, &mut config)?;
assert_eq!(
- "Projection: CAST(#a AS Decimal128(24, 4)) IN
([Decimal128(Some(10000),24,4), Decimal128(Some(40000),24,4),
Decimal128(Some(80000),24,4)])\n EmptyRelation",
+ "Projection: CAST(#a AS Decimal128(24, 4)) IN ([CAST(Int32(1) AS
Decimal128(24, 4)), CAST(Int8(4) AS Decimal128(24, 4)), CAST(Int64(8) AS
Decimal128(24, 4))])\n EmptyRelation",
&format!("{:?}", plan)
);
Ok(())
@@ -656,7 +653,7 @@ mod test {
let mut config = OptimizerConfig::default();
let plan = rule.optimize(&plan, &mut config).unwrap();
assert_eq!(
- "Projection: #a LIKE Utf8(NULL)\n EmptyRelation",
+ "Projection: #a LIKE CAST(NULL AS Utf8)\n EmptyRelation",
&format!("{:?}", plan)
);
diff --git a/datafusion/optimizer/tests/integration-test.rs
b/datafusion/optimizer/tests/integration-test.rs
index cb171780c..554e3cceb 100644
--- a/datafusion/optimizer/tests/integration-test.rs
+++ b/datafusion/optimizer/tests/integration-test.rs
@@ -79,12 +79,13 @@ fn intersect() -> Result<()> {
#[test]
fn between_date32_plus_interval() -> Result<()> {
+ // TODO: https://github.com/apache/arrow-datafusion/issues/3587
let sql = "SELECT count(1) FROM test \
WHERE col_date32 between '1998-03-18' AND cast('1998-03-18' as date) +
INTERVAL '90 days'";
let plan = test_sql(sql)?;
let expected =
"Projection: #COUNT(UInt8(1))\n Aggregate: groupBy=[[]],
aggr=[[COUNT(UInt8(1))]]\
- \n Filter: #test.col_date32 >= Date32(\"10303\") AND
#test.col_date32 <= Date32(\"10393\")\
+ \n Filter: #test.col_date32 BETWEEN Date32(\"10303\") AND
Date32(\"10393\")\
\n TableScan: test projection=[col_date32]";
assert_eq!(expected, format!("{:?}", plan));
Ok(())
@@ -92,18 +93,21 @@ fn between_date32_plus_interval() -> Result<()> {
#[test]
fn between_date64_plus_interval() -> Result<()> {
+ // TODO: https://github.com/apache/arrow-datafusion/issues/3587
let sql = "SELECT count(1) FROM test \
WHERE col_date64 between '1998-03-18T00:00:00' AND cast('1998-03-18' as
date) + INTERVAL '90 days'";
let plan = test_sql(sql)?;
let expected =
"Projection: #COUNT(UInt8(1))\n Aggregate: groupBy=[[]],
aggr=[[COUNT(UInt8(1))]]\
- \n Filter: #test.col_date64 >= Date64(\"890179200000\") AND
#test.col_date64 <= Date64(\"897955200000\")\
+ \n Filter: #test.col_date64 BETWEEN Date64(\"890179200000\") AND
Date64(\"897955200000\")\
\n TableScan: test projection=[col_date64]";
assert_eq!(expected, format!("{:?}", plan));
Ok(())
}
fn test_sql(sql: &str) -> Result<LogicalPlan> {
+ // TODO should make align with rules in the context
+ // https://github.com/apache/arrow-datafusion/issues/3524
let rules: Vec<Arc<dyn OptimizerRule + Sync + Send>> = vec![
// Simplify expressions first to maximize the chance
// of applying other optimizations
@@ -121,8 +125,10 @@ fn test_sql(sql: &str) -> Result<LogicalPlan> {
Arc::new(RewriteDisjunctivePredicate::new()),
Arc::new(FilterNullJoinKeys::default()),
Arc::new(ReduceOuterJoin::new()),
- Arc::new(FilterPushDown::new()),
Arc::new(TypeCoercion::new()),
+ // after the type coercion, can do simplify expression again
+ Arc::new(SimplifyExpressions::new()),
+ Arc::new(FilterPushDown::new()),
Arc::new(LimitPushDown::new()),
Arc::new(SingleDistinctToGroupBy::new()),
];
diff --git a/datafusion/physical-expr/src/expressions/binary.rs
b/datafusion/physical-expr/src/expressions/binary.rs
index b3704dc70..02bf0e5bd 100644
--- a/datafusion/physical-expr/src/expressions/binary.rs
+++ b/datafusion/physical-expr/src/expressions/binary.rs
@@ -73,12 +73,11 @@ use kernels_arrow::{
use arrow::datatypes::{DataType, Schema, TimeUnit};
use arrow::record_batch::RecordBatch;
-use crate::expressions::try_cast;
use crate::PhysicalExpr;
use datafusion_common::ScalarValue;
use datafusion_common::{DataFusionError, Result};
use datafusion_expr::binary_rule::binary_operator_data_type;
-use datafusion_expr::{binary_rule::coerce_types, ColumnarValue, Operator};
+use datafusion_expr::{ColumnarValue, Operator};
/// Binary expression
#[derive(Debug)]
@@ -912,25 +911,6 @@ impl BinaryExpr {
}
}
-/// return two physical expressions that are optionally coerced to a
-/// common type that the binary operator supports.
-fn binary_cast(
- lhs: Arc<dyn PhysicalExpr>,
- op: &Operator,
- rhs: Arc<dyn PhysicalExpr>,
- input_schema: &Schema,
-) -> Result<(Arc<dyn PhysicalExpr>, Arc<dyn PhysicalExpr>)> {
- let lhs_type = &lhs.data_type(input_schema)?;
- let rhs_type = &rhs.data_type(input_schema)?;
-
- let result_type = coerce_types(lhs_type, op, rhs_type)?;
-
- Ok((
- try_cast(lhs, input_schema, result_type.clone())?,
- try_cast(rhs, input_schema, result_type)?,
- ))
-}
-
/// Create a binary expression whose arguments are correctly coerced.
/// This function errors if it is not possible to coerce the arguments
/// to computational types supported by the operator.
@@ -940,17 +920,25 @@ pub fn binary(
rhs: Arc<dyn PhysicalExpr>,
input_schema: &Schema,
) -> Result<Arc<dyn PhysicalExpr>> {
- let (l, r) = binary_cast(lhs, &op, rhs, input_schema)?;
- Ok(Arc::new(BinaryExpr::new(l, op, r)))
+ let lhs_type = &lhs.data_type(input_schema)?;
+ let rhs_type = &rhs.data_type(input_schema)?;
+ if !lhs_type.eq(rhs_type) {
+ return Err(DataFusionError::Internal(format!(
+ "The type of {} {} {} of binary physical should be same",
+ lhs_type, op, rhs_type
+ )));
+ }
+ Ok(Arc::new(BinaryExpr::new(lhs, op, rhs)))
}
#[cfg(test)]
mod tests {
use super::*;
+ use crate::expressions::try_cast;
use crate::expressions::{col, lit};
use arrow::datatypes::{ArrowNumericType, Field, Int32Type, SchemaRef};
- use arrow::util::display::array_value_to_string;
use datafusion_common::Result;
+ use datafusion_expr::binary_rule::coerce_types;
// Create a binary expression without coercion. Used here when we do not
want to coerce the expressions
// to valid types. Usage can result in an execution (after plan) error.
@@ -1051,17 +1039,20 @@ mod tests {
// 4. verify that the resulting expression is of type C
// 5. verify that the results of evaluation are $VEC
macro_rules! test_coercion {
- ($A_ARRAY:ident, $A_TYPE:expr, $A_VEC:expr, $B_ARRAY:ident,
$B_TYPE:expr, $B_VEC:expr, $OP:expr, $C_ARRAY:ident, $C_TYPE:expr, $VEC:expr)
=> {{
+ ($A_ARRAY:ident, $A_TYPE:expr, $A_VEC:expr, $B_ARRAY:ident,
$B_TYPE:expr, $B_VEC:expr, $OP:expr, $C_ARRAY:ident, $C_TYPE:expr, $VEC:expr,)
=> {{
let schema = Schema::new(vec![
Field::new("a", $A_TYPE, false),
Field::new("b", $B_TYPE, false),
]);
let a = $A_ARRAY::from($A_VEC);
let b = $B_ARRAY::from($B_VEC);
+ let result_type = coerce_types(&$A_TYPE, &$OP, &$B_TYPE)?;
+
+ let left = try_cast(col("a", &schema)?, &schema,
result_type.clone())?;
+ let right = try_cast(col("b", &schema)?, &schema, result_type)?;
// verify that we can construct the expression
- let expression =
- binary(col("a", &schema)?, $OP, col("b", &schema)?, &schema)?;
+ let expression = binary(left, $OP, right, &schema)?;
let batch = RecordBatch::try_new(
Arc::new(schema.clone()),
vec![Arc::new(a), Arc::new(b)],
@@ -1100,7 +1091,7 @@ mod tests {
Operator::Plus,
Int32Array,
DataType::Int32,
- vec![2i32, 4i32]
+ vec![2i32, 4i32],
);
test_coercion!(
Int32Array,
@@ -1112,7 +1103,7 @@ mod tests {
Operator::Plus,
Int32Array,
DataType::Int32,
- vec![2i32]
+ vec![2i32],
);
test_coercion!(
Float32Array,
@@ -1124,7 +1115,7 @@ mod tests {
Operator::Plus,
Float32Array,
DataType::Float32,
- vec![2f32]
+ vec![2f32],
);
test_coercion!(
Float32Array,
@@ -1136,7 +1127,7 @@ mod tests {
Operator::Multiply,
Float32Array,
DataType::Float32,
- vec![2f32]
+ vec![2f32],
);
test_coercion!(
StringArray,
@@ -1148,7 +1139,7 @@ mod tests {
Operator::Like,
BooleanArray,
DataType::Boolean,
- vec![true, false]
+ vec![true, false],
);
test_coercion!(
StringArray,
@@ -1160,7 +1151,7 @@ mod tests {
Operator::Eq,
BooleanArray,
DataType::Boolean,
- vec![true, true]
+ vec![true, true],
);
test_coercion!(
StringArray,
@@ -1172,7 +1163,7 @@ mod tests {
Operator::Lt,
BooleanArray,
DataType::Boolean,
- vec![true, false]
+ vec![true, false],
);
test_coercion!(
StringArray,
@@ -1184,7 +1175,7 @@ mod tests {
Operator::Eq,
BooleanArray,
DataType::Boolean,
- vec![true, true]
+ vec![true, true],
);
test_coercion!(
StringArray,
@@ -1196,7 +1187,7 @@ mod tests {
Operator::Lt,
BooleanArray,
DataType::Boolean,
- vec![true, false]
+ vec![true, false],
);
test_coercion!(
StringArray,
@@ -1208,7 +1199,7 @@ mod tests {
Operator::RegexMatch,
BooleanArray,
DataType::Boolean,
- vec![true, false, true, false, false]
+ vec![true, false, true, false, false],
);
test_coercion!(
StringArray,
@@ -1220,7 +1211,7 @@ mod tests {
Operator::RegexIMatch,
BooleanArray,
DataType::Boolean,
- vec![true, true, true, true, false]
+ vec![true, true, true, true, false],
);
test_coercion!(
StringArray,
@@ -1232,7 +1223,7 @@ mod tests {
Operator::RegexNotMatch,
BooleanArray,
DataType::Boolean,
- vec![false, true, false, true, true]
+ vec![false, true, false, true, true],
);
test_coercion!(
StringArray,
@@ -1244,7 +1235,7 @@ mod tests {
Operator::RegexNotIMatch,
BooleanArray,
DataType::Boolean,
- vec![false, false, false, false, true]
+ vec![false, false, false, false, true],
);
test_coercion!(
LargeStringArray,
@@ -1256,7 +1247,7 @@ mod tests {
Operator::RegexMatch,
BooleanArray,
DataType::Boolean,
- vec![true, false, true, false, false]
+ vec![true, false, true, false, false],
);
test_coercion!(
LargeStringArray,
@@ -1268,7 +1259,7 @@ mod tests {
Operator::RegexIMatch,
BooleanArray,
DataType::Boolean,
- vec![true, true, true, true, false]
+ vec![true, true, true, true, false],
);
test_coercion!(
LargeStringArray,
@@ -1280,7 +1271,7 @@ mod tests {
Operator::RegexNotMatch,
BooleanArray,
DataType::Boolean,
- vec![false, true, false, true, true]
+ vec![false, true, false, true, true],
);
test_coercion!(
LargeStringArray,
@@ -1292,7 +1283,7 @@ mod tests {
Operator::RegexNotIMatch,
BooleanArray,
DataType::Boolean,
- vec![false, false, false, false, true]
+ vec![false, false, false, false, true],
);
test_coercion!(
Int16Array,
@@ -1304,7 +1295,7 @@ mod tests {
Operator::BitwiseAnd,
Int64Array,
DataType::Int64,
- vec![0i64, 0i64, 1i64]
+ vec![0i64, 0i64, 1i64],
);
test_coercion!(
Int16Array,
@@ -1316,7 +1307,7 @@ mod tests {
Operator::BitwiseOr,
Int64Array,
DataType::Int64,
- vec![11i64, 6i64, 7i64]
+ vec![11i64, 6i64, 7i64],
);
test_coercion!(
Int16Array,
@@ -1328,7 +1319,7 @@ mod tests {
Operator::BitwiseXor,
Int64Array,
DataType::Int64,
- vec![9i64, 4i64, 6i64]
+ vec![9i64, 4i64, 6i64],
);
Ok(())
}
@@ -1352,72 +1343,36 @@ mod tests {
dict_builder.append_null();
dict_builder.append("three")?;
dict_builder.append("four")?;
- let dict_array = dict_builder.finish();
+ let dict_array = Arc::new(dict_builder.finish()) as ArrayRef;
- let str_array =
- StringArray::from(vec![Some("not one"), Some("two"), None,
Some("four")]);
+ let str_array = Arc::new(StringArray::from(vec![
+ Some("not one"),
+ Some("two"),
+ None,
+ Some("four"),
+ ])) as ArrayRef;
let schema = Arc::new(Schema::new(vec![
- Field::new("dict", dict_type, true),
- Field::new("str", string_type, true),
+ Field::new("a", dict_type.clone(), true),
+ Field::new("b", string_type.clone(), true),
]));
- let batch = RecordBatch::try_new(
- schema.clone(),
- vec![Arc::new(dict_array), Arc::new(str_array)],
- )?;
-
- let expected = "false\n\n\ntrue";
-
- // Test 1: dict = str
-
- // verify that we can construct the expression
- let expression = binary(
- col("dict", &schema)?,
- Operator::Eq,
- col("str", &schema)?,
- &schema,
- )?;
- assert_eq!(expression.data_type(&schema)?, DataType::Boolean);
-
- // evaluate and verify the result type matched
- let result = expression.evaluate(&batch)?.into_array(batch.num_rows());
- assert_eq!(result.data_type(), &DataType::Boolean);
-
- // verify that the result itself is correct
- assert_eq!(expected, array_to_string(&result)?);
+ // Test 1: a = b
+ let result = BooleanArray::from(vec![Some(false), None, None,
Some(true)]);
+ apply_logic_op(&schema, &dict_array, &str_array, Operator::Eq,
result)?;
// Test 2: now test the other direction
- // str = dict
-
- // verify that we can construct the expression
- let expression = binary(
- col("str", &schema)?,
- Operator::Eq,
- col("dict", &schema)?,
- &schema,
- )?;
- assert_eq!(expression.data_type(&schema)?, DataType::Boolean);
-
- // evaluate and verify the result type matched
- let result = expression.evaluate(&batch)?.into_array(batch.num_rows());
- assert_eq!(result.data_type(), &DataType::Boolean);
-
- // verify that the result itself is correct
- assert_eq!(expected, array_to_string(&result)?);
+ // b = a
+ let schema = Arc::new(Schema::new(vec![
+ Field::new("a", string_type, true),
+ Field::new("b", dict_type, true),
+ ]));
+ let result = BooleanArray::from(vec![Some(false), None, None,
Some(true)]);
+ apply_logic_op(&schema, &str_array, &dict_array, Operator::Eq,
result)?;
Ok(())
}
- // Convert the array to a newline delimited string of pretty printed values
- fn array_to_string(array: &ArrayRef) -> Result<String> {
- let s = (0..array.len())
- .map(|i| array_value_to_string(array, i))
- .collect::<std::result::Result<Vec<_>,
arrow::error::ArrowError>>()?
- .join("\n");
- Ok(s)
- }
-
#[test]
fn plus_op() -> Result<()> {
let schema = Schema::new(vec![
@@ -1543,8 +1498,13 @@ mod tests {
op: Operator,
expected: BooleanArray,
) -> Result<()> {
- let arithmetic_op =
- binary_simple(col("a", schema)?, op, col("b", schema)?, schema);
+ let left_type = left.data_type();
+ let right_type = right.data_type();
+ let result_type = coerce_types(left_type, &op, right_type)?;
+
+ let left_expr = try_cast(col("a", schema)?, schema,
result_type.clone())?;
+ let right_expr = try_cast(col("b", schema)?, schema, result_type)?;
+ let arithmetic_op = binary_simple(left_expr, op, right_expr, schema);
let data: Vec<ArrayRef> = vec![left.clone(), right.clone()];
let batch = RecordBatch::try_new(schema.clone(), data)?;
let result =
arithmetic_op.evaluate(&batch)?.into_array(batch.num_rows());
@@ -1562,8 +1522,19 @@ mod tests {
expected: &BooleanArray,
) -> Result<()> {
let scalar = lit(scalar.clone());
+ let op_type = coerce_types(&scalar.data_type(schema)?, &op,
arr.data_type())?;
+ let left_expr = if op_type.eq(&scalar.data_type(schema)?) {
+ scalar
+ } else {
+ try_cast(scalar, schema, op_type.clone())?
+ };
+ let right_expr = if op_type.eq(arr.data_type()) {
+ col("a", schema)?
+ } else {
+ try_cast(col("a", schema)?, schema, op_type)?
+ };
- let arithmetic_op = binary_simple(scalar, op, col("a", schema)?,
schema);
+ let arithmetic_op = binary_simple(left_expr, op, right_expr, schema);
let batch = RecordBatch::try_new(Arc::clone(schema),
vec![Arc::clone(arr)])?;
let result =
arithmetic_op.evaluate(&batch)?.into_array(batch.num_rows());
assert_eq!(result.as_ref(), expected);
@@ -1580,8 +1551,19 @@ mod tests {
expected: &BooleanArray,
) -> Result<()> {
let scalar = lit(scalar.clone());
+ let op_type = coerce_types(arr.data_type(), &op,
&scalar.data_type(schema)?)?;
+ let right_expr = if op_type.eq(&scalar.data_type(schema)?) {
+ scalar
+ } else {
+ try_cast(scalar, schema, op_type.clone())?
+ };
+ let left_expr = if op_type.eq(arr.data_type()) {
+ col("a", schema)?
+ } else {
+ try_cast(col("a", schema)?, schema, op_type)?
+ };
- let arithmetic_op = binary_simple(col("a", schema)?, op, scalar,
schema);
+ let arithmetic_op = binary_simple(left_expr, op, right_expr, schema);
let batch = RecordBatch::try_new(Arc::clone(schema),
vec![Arc::clone(arr)])?;
let result =
arithmetic_op.evaluate(&batch)?.into_array(batch.num_rows());
assert_eq!(result.as_ref(), expected);
@@ -2405,8 +2387,19 @@ mod tests {
op: Operator,
expected: ArrayRef,
) -> Result<()> {
- let arithmetic_op =
- binary_simple(col("a", schema)?, op, col("b", schema)?, schema);
+ let op_type = coerce_types(left.data_type(), &op, right.data_type())?;
+ let left_expr = if left.data_type().eq(&op_type) {
+ col("a", schema)?
+ } else {
+ try_cast(col("a", schema)?, schema, op_type.clone())?
+ };
+
+ let right_expr = if right.data_type().eq(&op_type) {
+ col("b", schema)?
+ } else {
+ try_cast(col("b", schema)?, schema, op_type)?
+ };
+ let arithmetic_op = binary_simple(left_expr, op, right_expr, schema);
let data: Vec<ArrayRef> = vec![left.clone(), right.clone()];
let batch = RecordBatch::try_new(schema.clone(), data)?;
let result =
arithmetic_op.evaluate(&batch)?.into_array(batch.num_rows());
@@ -2487,6 +2480,7 @@ mod tests {
expect,
)
.unwrap();
+
// divide: int32 array divide decimal array
let schema = Arc::new(Schema::new(vec![
Field::new("a", DataType::Int32, true),
@@ -2510,6 +2504,7 @@ mod tests {
expect,
)
.unwrap();
+
// modulus: int32 array modulus decimal array
let schema = Arc::new(Schema::new(vec![
Field::new("a", DataType::Int32, true),