@@ -3,19 +3,16 @@ use std::borrow::Cow;
33use std:: collections:: HashMap ;
44use std:: sync:: Arc ;
55
6- use chrono:: { FixedOffset , NaiveDateTime , Offset , TimeDelta , TimeZone , Utc } ;
76use itertools:: Itertools ;
87use tracing:: warn;
98
10- use crate :: arrow:: array:: timezone:: Tz ;
119use crate :: arrow:: array:: types:: * ;
1210use crate :: arrow:: array:: {
1311 self as arrow_array, make_array, new_null_array, Array , ArrayBuilder , ArrayData , ArrayRef ,
1412 AsArray , BooleanArray , Datum , ListArray , MapArray , MutableArrayData , NullBufferBuilder ,
1513 RecordBatch , StringArray , StructArray ,
1614} ;
1715use crate :: arrow:: buffer:: { NullBuffer , OffsetBuffer } ;
18- use crate :: arrow:: compute:: kernels:: cast_utils:: { string_to_datetime, Parser } ;
1916use crate :: arrow:: compute:: kernels:: cmp:: { distinct, eq, gt, gt_eq, lt, lt_eq, neq, not_distinct} ;
2017use crate :: arrow:: compute:: kernels:: comparison:: in_list_utf8;
2118use crate :: arrow:: compute:: kernels:: numeric:: { add, div, mul, sub} ;
@@ -44,6 +41,7 @@ use crate::expressions::{
4441 UnaryPredicateOp , VariadicExpression , VariadicExpressionOp ,
4542} ;
4643use crate :: schema:: { DataType , PrimitiveType , StructField , StructType } ;
44+ use crate :: timestamp_timezone:: { parse_partition_scalar, TimestampTimezone } ;
4745
4846#[ internal_api]
4947pub ( crate ) trait ProvidesColumnByName {
@@ -414,95 +412,6 @@ pub fn evaluate_expression(
414412 }
415413}
416414
417- #[ derive( Clone , Copy ) ]
418- enum TimestampTimezone {
419- Arrow ( Tz ) ,
420- Fixed ( FixedOffset ) ,
421- }
422-
423- impl TimestampTimezone {
424- fn parse ( value : & str ) -> DeltaResult < Self > {
425- if value. starts_with ( '+' ) || value. starts_with ( '-' ) {
426- return parse_fixed_offset ( value)
427- . map ( Self :: Fixed )
428- . ok_or_else ( || Error :: generic ( format ! ( "Invalid timestamp timezone: {value}" ) ) ) ;
429- }
430- value
431- . parse :: < Tz > ( )
432- . map ( Self :: Arrow )
433- . map_err ( |_| Error :: generic ( format ! ( "Invalid timestamp timezone: {value}" ) ) )
434- }
435-
436- fn parse_timestamp ( self , raw : & str ) -> Result < i64 , ArrowError > {
437- match self {
438- Self :: Arrow ( timezone) => parse_timestamp_with_timezone ( timezone, raw) ,
439- Self :: Fixed ( timezone) => parse_timestamp_with_timezone ( timezone, raw) ,
440- }
441- }
442- }
443-
444- fn parse_fixed_offset ( value : & str ) -> Option < FixedOffset > {
445- let ( sign, value) = match value. as_bytes ( ) . first ( ) ? {
446- b'+' => ( 1 , & value[ 1 ..] ) ,
447- b'-' => ( -1 , & value[ 1 ..] ) ,
448- _ => return None ,
449- } ;
450- let mut parts = value. split ( ':' ) ;
451- let parse_component = |part : & str | {
452- ( !part. is_empty ( ) && part. bytes ( ) . all ( |byte| byte. is_ascii_digit ( ) ) )
453- . then ( || part. parse :: < i32 > ( ) . ok ( ) )
454- . flatten ( )
455- } ;
456- let hours = parse_component ( parts. next ( ) ?) ?;
457- let minutes = parts. next ( ) . map_or ( Some ( 0 ) , parse_component) ?;
458- let seconds = parts. next ( ) . map_or ( Some ( 0 ) , parse_component) ?;
459- if parts. next ( ) . is_some ( )
460- || value. is_empty ( )
461- || hours > 18
462- || minutes > 59
463- || seconds > 59
464- || ( hours == 18 && ( minutes != 0 || seconds != 0 ) )
465- {
466- return None ;
467- }
468- FixedOffset :: east_opt ( sign * ( hours * 3_600 + minutes * 60 + seconds) )
469- }
470-
471- fn parse_timestamp_with_timezone < T : TimeZone + Copy > (
472- timezone : T ,
473- raw : & str ,
474- ) -> Result < i64 , ArrowError > {
475- if let Ok ( timestamp) = string_to_datetime ( & timezone, raw) {
476- return Ok ( timestamp. timestamp_micros ( ) ) ;
477- }
478- let local_datetime = string_to_datetime ( & Utc , raw) ?. naive_utc ( ) ;
479- resolve_local_timestamp ( local_datetime, timezone) . ok_or_else ( || {
480- ArrowError :: ParseError ( format ! ( "Error resolving local timestamp from '{raw}'" ) )
481- } )
482- }
483-
484- fn resolve_local_timestamp < T : TimeZone + Copy > (
485- local_datetime : NaiveDateTime ,
486- timezone : T ,
487- ) -> Option < i64 > {
488- if let Some ( timestamp) = timezone. from_local_datetime ( & local_datetime) . earliest ( ) {
489- return Some ( timestamp. timestamp_micros ( ) ) ;
490- }
491-
492- // A forward transition can skip more than one hour, so walk backward until reaching the
493- // pre-transition side of the gap.
494- let offset = ( 1 ..=48 ) . find_map ( |hours| {
495- let before_transition = local_datetime. checked_sub_signed ( TimeDelta :: hours ( hours) ) ?;
496- timezone
497- . from_local_datetime ( & before_transition)
498- . earliest ( )
499- . map ( |timestamp| timestamp. offset ( ) . fix ( ) )
500- } ) ?;
501- local_datetime
502- . checked_sub_signed ( TimeDelta :: seconds ( i64:: from ( offset. local_minus_utc ( ) ) ) )
503- . map ( |utc_datetime| utc_datetime. and_utc ( ) . timestamp_micros ( ) )
504- }
505-
506415/// Evaluate an `ARRAY(e0, e1, ..., eN-1)` constructor expression into an Arrow `ListArray`.
507416///
508417/// Each input expression produces one column of length M (rows in the batch); the output
@@ -1032,44 +941,6 @@ pub fn coalesce_arrays(
1032941 Ok ( make_array ( mutable. freeze ( ) ) )
1033942}
1034943
1035- /// Parses one raw partition-value string into its target [`Scalar`], or `None` for a null value.
1036- ///
1037- /// An empty string casts via [`PrimitiveType::empty_string_partition_cast`].
1038- ///
1039- /// Uses map-to-struct's date and timestamp semantics.
1040- fn parse_partition_scalar (
1041- prim : & PrimitiveType ,
1042- raw : & str ,
1043- timestamp_timezone : TimestampTimezone ,
1044- ) -> DeltaResult < Option < Scalar > > {
1045- if raw. is_empty ( ) {
1046- return Ok ( prim. empty_string_partition_cast ( ) ) ;
1047- }
1048- match prim {
1049- PrimitiveType :: Date => {
1050- let days = Date32Type :: parse ( raw) . ok_or_else ( || {
1051- Error :: ParseError ( raw. to_string ( ) , DataType :: Primitive ( prim. clone ( ) ) )
1052- } ) ?;
1053- return Ok ( Some ( Scalar :: Date ( days) ) ) ;
1054- }
1055- PrimitiveType :: Timestamp => {
1056- let micros = timestamp_timezone. parse_timestamp ( raw) . map_err ( |_| {
1057- Error :: ParseError ( raw. to_string ( ) , DataType :: Primitive ( prim. clone ( ) ) )
1058- } ) ?;
1059- return Ok ( Some ( Scalar :: Timestamp ( micros) ) ) ;
1060- }
1061- PrimitiveType :: TimestampNtz => {
1062- let micros = string_to_datetime ( & Utc , raw)
1063- . map_err ( |_| Error :: ParseError ( raw. to_string ( ) , DataType :: Primitive ( prim. clone ( ) ) ) ) ?
1064- . timestamp_micros ( ) ;
1065- return Ok ( Some ( Scalar :: TimestampNtz ( micros) ) ) ;
1066- }
1067- _ => { }
1068- }
1069- let scalar = prim. parse_scalar ( raw) ?;
1070- Ok ( ( !matches ! ( scalar, Scalar :: Null ( _) ) ) . then_some ( scalar) )
1071- }
1072-
1073944/// Evaluates `MAP_TO_STRUCT(map_col, output_schema)`: extracts keys from a `Map<String, String>`
1074945/// and parses each value into its target type, producing a `StructArray`. An empty-string value
1075946/// casts via [`PrimitiveType::empty_string_partition_cast`].
@@ -3008,35 +2879,6 @@ mod tests {
30082879 ) ;
30092880 }
30102881
3011- #[ rstest]
3012- #[ case:: zero( "+0" , 0 ) ]
3013- #[ case:: hours( "+5" , 18_000 ) ]
3014- #[ case:: hours_minutes( "-05:30" , -19_800 ) ]
3015- #[ case:: hours_minutes_seconds( "+12:45:30" , 45_930 ) ]
3016- #[ case:: positive_limit( "+18:00:00" , 64_800 ) ]
3017- #[ case:: negative_limit( "-18" , -64_800 ) ]
3018- fn test_parse_fixed_offset_accepts_supported_forms (
3019- #[ case] timezone : & str ,
3020- #[ case] expected_seconds : i32 ,
3021- ) {
3022- assert_eq ! (
3023- parse_fixed_offset( timezone) . map( |offset| offset. local_minus_utc( ) ) ,
3024- Some ( expected_seconds)
3025- ) ;
3026- }
3027-
3028- #[ rstest]
3029- #[ case:: empty( "" ) ]
3030- #[ case:: bare_positive_sign( "+" ) ]
3031- #[ case:: over_max_second( "+18:00:01" ) ]
3032- #[ case:: over_max_hour( "+19" ) ]
3033- #[ case:: minutes_out_of_range( "+00:60" ) ]
3034- #[ case:: seconds_out_of_range( "+00:00:60" ) ]
3035- #[ case:: extra_component( "+05:30:00:00" ) ]
3036- fn test_parse_fixed_offset_rejects_invalid_forms ( #[ case] timezone : & str ) {
3037- assert_eq ! ( parse_fixed_offset( timezone) , None ) ;
3038- }
3039-
30402882 #[ test]
30412883 fn test_map_to_struct_timestamp_timezone_does_not_affect_timestamp_ntz ( ) {
30422884 let raw = "2024-01-15 12:30:45.123456" ;
0 commit comments