@@ -37,8 +37,7 @@ use arrow::datatypes::{DataType, Schema};
3737use datafusion_common:: { Result , ScalarValue , tree_node:: Transformed } ;
3838use datafusion_expr:: Operator ;
3939use datafusion_expr_common:: casts:: {
40- is_date_narrowing_cast, is_timestamp_precision_narrowing_cast,
41- try_cast_literal_to_type,
40+ changes_timestamp_semantics, is_date_narrowing_cast, try_cast_literal_to_type,
4241} ;
4342
4443use crate :: PhysicalExpr ;
@@ -130,7 +129,7 @@ fn try_unwrap_cast_comparison(
130129 // Get the data type of the inner expression
131130 let inner_type = inner_expr. data_type ( schema) ?;
132131
133- if is_timestamp_precision_narrowing_cast ( & inner_type, cast_type)
132+ if changes_timestamp_semantics ( & inner_type, cast_type)
134133 || is_date_narrowing_cast ( & inner_type, cast_type)
135134 {
136135 return Ok ( None ) ;
@@ -251,6 +250,101 @@ mod tests {
251250 assert ! ( !result. transformed) ;
252251 }
253252
253+ #[ test]
254+ fn test_no_unwrap_timestamp_timezone_or_unit_change ( ) {
255+ let source_type = DataType :: Timestamp ( TimeUnit :: Microsecond , None ) ;
256+ let schema = Schema :: new ( vec ! [ Field :: new( "ts" , source_type. clone( ) , false ) ] ) ;
257+ for target_type in [
258+ DataType :: Timestamp ( TimeUnit :: Microsecond , Some ( "+01:00" . into ( ) ) ) ,
259+ DataType :: Timestamp ( TimeUnit :: Nanosecond , None ) ,
260+ ] {
261+ let target_literal = ScalarValue :: TimestampMicrosecond ( Some ( 0 ) , None )
262+ . cast_to ( & target_type)
263+ . unwrap ( ) ;
264+ let column = col ( "ts" , & schema) . unwrap ( ) ;
265+ for cast in [
266+ Arc :: new ( CastExpr :: new (
267+ Arc :: clone ( & column) ,
268+ target_type. clone ( ) ,
269+ None ,
270+ ) ) as Arc < dyn PhysicalExpr > ,
271+ Arc :: new ( TryCastExpr :: new ( Arc :: clone ( & column) , target_type. clone ( ) ) ) ,
272+ ] {
273+ let literal = lit ( target_literal. clone ( ) ) ;
274+ for comparison in [
275+ Arc :: new ( BinaryExpr :: new (
276+ Arc :: clone ( & cast) ,
277+ Operator :: Eq ,
278+ Arc :: clone ( & literal) ,
279+ ) ) ,
280+ Arc :: new ( BinaryExpr :: new ( Arc :: clone ( & literal) , Operator :: Eq , cast) ) ,
281+ ] {
282+ let result = unwrap_cast_in_comparison ( comparison, & schema) . unwrap ( ) ;
283+ assert ! ( !result. transformed, "cast to {target_type:?} was unwrapped" ) ;
284+ }
285+ }
286+ }
287+ }
288+
289+ #[ test]
290+ fn test_unwrap_identity_timestamp_cast ( ) {
291+ let ts_type = DataType :: Timestamp ( TimeUnit :: Microsecond , None ) ;
292+ let schema = Schema :: new ( vec ! [ Field :: new( "ts" , ts_type. clone( ) , false ) ] ) ;
293+ let column = col ( "ts" , & schema) . unwrap ( ) ;
294+ let comparison = Arc :: new ( BinaryExpr :: new (
295+ Arc :: new ( CastExpr :: new ( column, ts_type, None ) ) ,
296+ Operator :: Eq ,
297+ lit ( ScalarValue :: TimestampMicrosecond ( Some ( 0 ) , None ) ) ,
298+ ) ) ;
299+ let result = unwrap_cast_in_comparison ( comparison, & schema) . unwrap ( ) ;
300+ assert ! ( result. transformed) ;
301+ }
302+
303+ #[ test]
304+ fn test_unwrap_timestamp_timezone_removal_or_replacement ( ) {
305+ let source_type =
306+ DataType :: Timestamp ( TimeUnit :: Microsecond , Some ( "+01:00" . into ( ) ) ) ;
307+ let schema = Schema :: new ( vec ! [ Field :: new( "ts" , source_type, false ) ] ) ;
308+ for target_type in [
309+ DataType :: Timestamp ( TimeUnit :: Microsecond , None ) ,
310+ DataType :: Timestamp ( TimeUnit :: Microsecond , Some ( "+02:00" . into ( ) ) ) ,
311+ ] {
312+ let column = col ( "ts" , & schema) . unwrap ( ) ;
313+ for cast in [
314+ Arc :: new ( CastExpr :: new (
315+ Arc :: clone ( & column) ,
316+ target_type. clone ( ) ,
317+ None ,
318+ ) ) as Arc < dyn PhysicalExpr > ,
319+ Arc :: new ( TryCastExpr :: new ( Arc :: clone ( & column) , target_type. clone ( ) ) ) ,
320+ ] {
321+ let comparison = Arc :: new ( BinaryExpr :: new (
322+ cast,
323+ Operator :: Eq ,
324+ lit ( ScalarValue :: TimestampMicrosecond (
325+ Some ( 42 ) ,
326+ match & target_type {
327+ DataType :: Timestamp ( _, tz) => tz. clone ( ) ,
328+ _ => unreachable ! ( ) ,
329+ } ,
330+ ) ) ,
331+ ) ) ;
332+ let result = unwrap_cast_in_comparison ( comparison, & schema) . unwrap ( ) ;
333+ assert ! (
334+ result. transformed,
335+ "cast to {target_type:?} was not unwrapped"
336+ ) ;
337+ let optimized = result. data ;
338+ let binary = optimized. downcast_ref :: < BinaryExpr > ( ) . unwrap ( ) ;
339+ assert ! ( !is_cast_expr( binary. left( ) ) ) ;
340+ assert_eq ! (
341+ binary. right( ) . downcast_ref:: <Literal >( ) . unwrap( ) . value( ) ,
342+ & ScalarValue :: TimestampMicrosecond ( Some ( 42 ) , Some ( "+01:00" . into( ) ) )
343+ ) ;
344+ }
345+ }
346+ }
347+
254348 #[ test]
255349 fn test_no_unwrap_when_types_unsupported ( ) {
256350 let schema = Schema :: new ( vec ! [ Field :: new( "f1" , DataType :: Float32 , false ) ] ) ;
@@ -601,7 +695,7 @@ mod tests {
601695 }
602696
603697 #[ test]
604- fn test_unwrap_timestamp_precision_widening ( ) {
698+ fn test_no_unwrap_timestamp_precision_widening ( ) {
605699 let schema = Schema :: new ( vec ! [ Field :: new(
606700 "ts" ,
607701 DataType :: Timestamp ( TimeUnit :: Millisecond , None ) ,
@@ -620,14 +714,8 @@ mod tests {
620714
621715 let result = unwrap_cast_in_comparison ( binary_expr, & schema) . unwrap ( ) ;
622716
623- assert ! ( result. transformed) ;
624- let optimized_binary = result. data . downcast_ref :: < BinaryExpr > ( ) . unwrap ( ) ;
625- assert ! ( !is_cast_expr( optimized_binary. left( ) ) ) ;
626- let right_literal = optimized_binary. right ( ) . downcast_ref :: < Literal > ( ) . unwrap ( ) ;
627- assert_eq ! (
628- right_literal. value( ) ,
629- & ScalarValue :: TimestampMillisecond ( Some ( 1 ) , None )
630- ) ;
717+ // The literal fits, but other column values may overflow when widened.
718+ assert ! ( !result. transformed) ;
631719 }
632720
633721 #[ test]
0 commit comments