diff --git a/datafusion/core/tests/sql/timestamp.rs b/datafusion/core/tests/sql/timestamp.rs index 18cc46ae440d..fc9b99f14dd0 100644 --- a/datafusion/core/tests/sql/timestamp.rs +++ b/datafusion/core/tests/sql/timestamp.rs @@ -1572,7 +1572,6 @@ async fn test_current_time() -> Result<()> { #[tokio::test] async fn test_ts_dt_binary_ops() -> Result<()> { let ctx = SessionContext::new(); - // test cast in where clause let sql = "select count(1) result from (select now() as n) a where n = '2000-01-01'::date"; @@ -1631,5 +1630,55 @@ async fn test_ts_dt_binary_ops() -> Result<()> { assert_batches_eq!(expected, &results); + //test cast path timestamp date using literals + let sql = "select '2000-01-01'::timestamp >= '2000-01-01'::date"; + let df = ctx.sql(sql).await.unwrap(); + + let plan = df.explain(true, false)?.collect().await?; + let batch = &plan[0]; + let mut res: Option = None; + for row in 0..batch.num_rows() { + if &array_value_to_string(batch.column(0), row)? + == "logical_plan after type_coercion" + { + res = Some(array_value_to_string(batch.column(1), row)?); + break; + } + } + assert_eq!(res, Some("Projection: CAST(Utf8(\"2000-01-01\") AS Timestamp(Nanosecond, None)) >= CAST(CAST(Utf8(\"2000-01-01\") AS Date32) AS Timestamp(Nanosecond, None))\n EmptyRelation".to_string())); + + //test cast path timestamp date using function + let sql = "select now() >= '2000-01-01'::date"; + let df = ctx.sql(sql).await.unwrap(); + + let plan = df.explain(true, false)?.collect().await?; + let batch = &plan[0]; + let mut res: Option = None; + for row in 0..batch.num_rows() { + if &array_value_to_string(batch.column(0), row)? + == "logical_plan after type_coercion" + { + res = Some(array_value_to_string(batch.column(1), row)?); + break; + } + } + assert_eq!(res, Some("Projection: CAST(now() AS Timestamp(Nanosecond, None)) >= CAST(CAST(Utf8(\"2000-01-01\") AS Date32) AS Timestamp(Nanosecond, None))\n EmptyRelation".to_string())); + + let sql = "select now() = current_date()"; + let df = ctx.sql(sql).await.unwrap(); + + let plan = df.explain(true, false)?.collect().await?; + let batch = &plan[0]; + let mut res: Option = None; + for row in 0..batch.num_rows() { + if &array_value_to_string(batch.column(0), row)? + == "logical_plan after type_coercion" + { + res = Some(array_value_to_string(batch.column(1), row)?); + break; + } + } + assert_eq!(res, Some("Projection: CAST(now() AS Timestamp(Nanosecond, None)) = CAST(currentdate() AS Timestamp(Nanosecond, None))\n EmptyRelation".to_string())); + Ok(()) } diff --git a/datafusion/expr/src/type_coercion/binary.rs b/datafusion/expr/src/type_coercion/binary.rs index d2923c8dbfae..d942d1cf1cbe 100644 --- a/datafusion/expr/src/type_coercion/binary.rs +++ b/datafusion/expr/src/type_coercion/binary.rs @@ -541,33 +541,30 @@ fn is_time_with_valid_unit(datatype: DataType) -> bool { fn temporal_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option { use arrow::datatypes::DataType::*; match (lhs_type, rhs_type) { - (Date64, Date32) => Some(Date64), - (Date32, Date64) => Some(Date64), - (Utf8, Date32) => Some(Date32), - (Date32, Utf8) => Some(Date32), - (Utf8, Date64) => Some(Date64), - (Date64, Utf8) => Some(Date64), - (Utf8, Time32(unit)) => match is_time_with_valid_unit(Time32(unit.clone())) { - false => None, - true => Some(Time32(unit.clone())), - }, - (Time32(unit), Utf8) => match is_time_with_valid_unit(Time32(unit.clone())) { - false => None, - true => Some(Time32(unit.clone())), - }, - (Utf8, Time64(unit)) => match is_time_with_valid_unit(Time64(unit.clone())) { - false => None, - true => Some(Time64(unit.clone())), - }, - (Time64(unit), Utf8) => match is_time_with_valid_unit(Time64(unit.clone())) { - false => None, - true => Some(Time64(unit.clone())), - }, - (Timestamp(_, tz), Utf8) => Some(Timestamp(TimeUnit::Nanosecond, tz.clone())), - (Utf8, Timestamp(_, tz)) => Some(Timestamp(TimeUnit::Nanosecond, tz.clone())), - // TODO: need to investigate the result type for the comparison between timestamp and date - (Timestamp(_, _), Date32) => Some(Date32), - (Timestamp(_, _), Date64) => Some(Date64), + (Date64, Date32) | (Date32, Date64) => Some(Date64), + (Utf8, Date32) | (Date32, Utf8) => Some(Date32), + (Utf8, Date64) | (Date64, Utf8) => Some(Date64), + (Utf8, Time32(unit)) | (Time32(unit), Utf8) => { + match is_time_with_valid_unit(Time32(unit.clone())) { + false => None, + true => Some(Time32(unit.clone())), + } + } + (Utf8, Time64(unit)) | (Time64(unit), Utf8) => { + match is_time_with_valid_unit(Time64(unit.clone())) { + false => None, + true => Some(Time64(unit.clone())), + } + } + (Timestamp(_, tz), Utf8) | (Utf8, Timestamp(_, tz)) => { + Some(Timestamp(TimeUnit::Nanosecond, tz.clone())) + } + (Timestamp(_, None), Date32) | (Date32, Timestamp(_, None)) => { + Some(Timestamp(TimeUnit::Nanosecond, None)) + } + (Timestamp(_, _tz), Date32) | (Date32, Timestamp(_, _tz)) => { + Some(Timestamp(TimeUnit::Nanosecond, None)) + } (Timestamp(lhs_unit, lhs_tz), Timestamp(rhs_unit, rhs_tz)) => { let tz = match (lhs_tz, rhs_tz) { // can't cast across timezones diff --git a/datafusion/expr/src/type_coercion/functions.rs b/datafusion/expr/src/type_coercion/functions.rs index f58050eb9415..a732c552bee7 100644 --- a/datafusion/expr/src/type_coercion/functions.rs +++ b/datafusion/expr/src/type_coercion/functions.rs @@ -174,8 +174,8 @@ pub fn can_coerce_from(type_into: &DataType, type_from: &DataType) -> bool { | Float64 | Decimal128(_, _) ), - Timestamp(TimeUnit::Nanosecond, None) => { - matches!(type_from, Null | Timestamp(_, None)) + Timestamp(TimeUnit::Nanosecond, _) => { + matches!(type_from, Null | Timestamp(_, _) | Date32) } Utf8 | LargeUtf8 => true, Null => can_cast_types(type_from, type_into), diff --git a/datafusion/optimizer/src/type_coercion.rs b/datafusion/optimizer/src/type_coercion.rs index e335c9ff5f85..96dacab512ef 100644 --- a/datafusion/optimizer/src/type_coercion.rs +++ b/datafusion/optimizer/src/type_coercion.rs @@ -1038,4 +1038,23 @@ mod test { Ok(()) // TODO add more test for this } + + #[test] + fn binary_op_date32_eq_ts() -> Result<()> { + let expr = cast( + lit("1998-03-18"), + DataType::Timestamp(arrow::datatypes::TimeUnit::Nanosecond, None), + ) + .eq(cast(lit("1998-03-18"), DataType::Date32)); + let empty = Arc::new(LogicalPlan::EmptyRelation(EmptyRelation { + produce_one_row: false, + schema: Arc::new(DFSchema::empty()), + })); + let plan = LogicalPlan::Projection(Projection::try_new(vec![expr], empty)?); + dbg!(&plan); + let expected = + "Projection: CAST(Utf8(\"1998-03-18\") AS Timestamp(Nanosecond, None)) = CAST(CAST(Utf8(\"1998-03-18\") AS Date32) AS Timestamp(Nanosecond, None))\n EmptyRelation"; + assert_optimized_plan_eq(&plan, expected)?; + Ok(()) + } }