@@ -91,42 +91,38 @@ fn create_to_char_udf() -> ScalarUDF {
9191
9292/// Format timestamps according to PostgreSQL format patterns
9393fn format_timestamps ( timestamp_array : & ArrayRef , format_str : & str ) -> datafusion:: error:: Result < ArrayRef > {
94- // Try to handle both microsecond and nanosecond timestamps
94+ let chrono_format = postgres_to_chrono_format ( format_str ) ;
9595 let mut builder = StringBuilder :: new ( ) ;
9696
97- if let Some ( timestamps) = timestamp_array. as_any ( ) . downcast_ref :: < TimestampMicrosecondArray > ( ) {
98- for i in 0 ..timestamps. len ( ) {
99- if timestamps. is_null ( i) {
100- builder. append_null ( ) ;
101- } else {
102- let timestamp_us = timestamps. value ( i) ;
103- let datetime =
104- DateTime :: < Utc > :: from_timestamp_micros ( timestamp_us) . ok_or_else ( || DataFusionError :: Execution ( "Invalid timestamp" . to_string ( ) ) ) ?;
105-
106- // Convert PostgreSQL format to chrono format
107- let chrono_format = postgres_to_chrono_format ( format_str) ;
108- let formatted = datetime. format ( & chrono_format) . to_string ( ) ;
97+ let format_fn = |timestamp_us : i64 | -> datafusion:: error:: Result < String > {
98+ DateTime :: < Utc > :: from_timestamp_micros ( timestamp_us)
99+ . ok_or_else ( || DataFusionError :: Execution ( "Invalid timestamp" . to_string ( ) ) )
100+ . map ( |dt| dt. format ( & chrono_format) . to_string ( ) )
101+ } ;
109102
110- builder. append_value ( & formatted) ;
103+ match timestamp_array. as_any ( ) . downcast_ref :: < TimestampMicrosecondArray > ( ) {
104+ Some ( timestamps) => {
105+ for i in 0 ..timestamps. len ( ) {
106+ if timestamps. is_null ( i) {
107+ builder. append_null ( ) ;
108+ } else {
109+ builder. append_value ( & format_fn ( timestamps. value ( i) ) ?) ;
110+ }
111111 }
112112 }
113- } else if let Some ( timestamps) = timestamp_array. as_any ( ) . downcast_ref :: < TimestampNanosecondArray > ( ) {
114- for i in 0 ..timestamps. len ( ) {
115- if timestamps. is_null ( i) {
116- builder. append_null ( ) ;
117- } else {
118- let timestamp_ns = timestamps. value ( i) ;
119- let datetime = DateTime :: < Utc > :: from_timestamp_nanos ( timestamp_ns) ;
120-
121- // Convert PostgreSQL format to chrono format
122- let chrono_format = postgres_to_chrono_format ( format_str) ;
123- let formatted = datetime. format ( & chrono_format) . to_string ( ) ;
124-
125- builder. append_value ( & formatted) ;
113+ None => match timestamp_array. as_any ( ) . downcast_ref :: < TimestampNanosecondArray > ( ) {
114+ Some ( timestamps) => {
115+ for i in 0 ..timestamps. len ( ) {
116+ if timestamps. is_null ( i) {
117+ builder. append_null ( ) ;
118+ } else {
119+ let timestamp_us = timestamps. value ( i) / 1000 ; // Convert nanos to micros
120+ builder. append_value ( & format_fn ( timestamp_us) ?) ;
121+ }
122+ }
126123 }
124+ None => return Err ( DataFusionError :: Execution ( "First argument must be a timestamp" . to_string ( ) ) ) ,
127125 }
128- } else {
129- return Err ( DataFusionError :: Execution ( "First argument must be a timestamp" . to_string ( ) ) ) ;
130126 }
131127
132128 Ok ( Arc :: new ( builder. finish ( ) ) )
@@ -610,50 +606,41 @@ fn create_time_bucket_udf() -> ScalarUDF {
610606/// Parse interval string to microseconds
611607fn parse_interval_to_micros ( interval_str : & str ) -> datafusion:: error:: Result < i64 > {
612608 let trimmed = interval_str. trim ( ) ;
613-
614- // Try to parse with whitespace first (e.g., "30 minutes")
615609 let parts: Vec < & str > = trimmed. split_whitespace ( ) . collect ( ) ;
616610
617- let ( value, unit) = if parts. len ( ) == 2 {
618- // Format: "30 minutes"
619- let value = parts[ 0 ] . parse :: < i64 > ( )
620- . map_err ( |_| DataFusionError :: Execution ( "Invalid interval value" . to_string ( ) ) ) ?;
621- ( value, parts[ 1 ] . to_lowercase ( ) )
622- } else if parts. len ( ) == 1 {
623- // Try to parse format without space (e.g., "30m")
624- let part = parts[ 0 ] ;
625-
626- // Find where the number ends and the unit begins
627- let split_pos = part. chars ( )
628- . position ( |c| c. is_alphabetic ( ) )
629- . ok_or_else ( || DataFusionError :: Execution (
630- "Invalid interval format. Expected format: 'N unit' (e.g., '5 minutes' or '5m')" . to_string ( )
631- ) ) ?;
632-
633- let ( num_str, unit_str) = part. split_at ( split_pos) ;
634-
635- let value = num_str. parse :: < i64 > ( )
636- . map_err ( |_| DataFusionError :: Execution ( "Invalid interval value" . to_string ( ) ) ) ?;
611+ let ( value, unit) = match parts. as_slice ( ) {
612+ [ value_str, unit_str] => {
613+ let value = value_str. parse :: < i64 > ( )
614+ . map_err ( |_| DataFusionError :: Execution ( "Invalid interval value" . to_string ( ) ) ) ?;
615+ ( value, unit_str. to_lowercase ( ) )
616+ }
617+ [ combined] => {
618+ let split_pos = combined. chars ( )
619+ . position ( |c| c. is_alphabetic ( ) )
620+ . ok_or_else ( || DataFusionError :: Execution (
621+ "Invalid interval format. Expected format: 'N unit' (e.g., '5 minutes' or '5m')" . to_string ( )
622+ ) ) ?;
637623
638- ( value, unit_str. to_lowercase ( ) )
639- } else {
640- return Err ( DataFusionError :: Execution (
624+ let ( num_str, unit_str) = combined. split_at ( split_pos) ;
625+ let value = num_str. parse :: < i64 > ( )
626+ . map_err ( |_| DataFusionError :: Execution ( "Invalid interval value" . to_string ( ) ) ) ?;
627+ ( value, unit_str. to_lowercase ( ) )
628+ }
629+ _ => return Err ( DataFusionError :: Execution (
641630 "Invalid interval format. Expected format: 'N unit' (e.g., '5 minutes' or '5m')" . to_string ( ) ,
642- ) ) ;
631+ ) ) ,
643632 } ;
644633
645634 let micros_per_unit = match unit. as_str ( ) {
646635 "second" | "seconds" | "sec" | "secs" | "s" => 1_000_000 ,
647- "minute" | "minutes" | "min" | "mins" | "m" => 60 * 1_000_000 ,
648- "hour" | "hours" | "hr" | "hrs" | "h" => 3600 * 1_000_000 ,
649- "day" | "days" | "d" => 86400 * 1_000_000 ,
650- "week" | "weeks" | "w" => 7 * 86400 * 1_000_000 ,
651- _ => {
652- return Err ( DataFusionError :: Execution ( format ! (
653- "Unsupported time unit: {}. Supported units: second(s), minute(s), hour(s), day(s), week(s)" ,
654- unit
655- ) ) ) ;
656- }
636+ "minute" | "minutes" | "min" | "mins" | "m" => 60_000_000 ,
637+ "hour" | "hours" | "hr" | "hrs" | "h" => 3_600_000_000 ,
638+ "day" | "days" | "d" => 86_400_000_000 ,
639+ "week" | "weeks" | "w" => 604_800_000_000 ,
640+ _ => return Err ( DataFusionError :: Execution ( format ! (
641+ "Unsupported time unit: {}. Supported units: second(s), minute(s), hour(s), day(s), week(s)" ,
642+ unit
643+ ) ) ) ,
657644 } ;
658645
659646 Ok ( value * micros_per_unit)
@@ -819,7 +806,7 @@ impl Accumulator for PercentileAccumulator {
819806 if !binary_array. is_null ( i) {
820807 let bytes = binary_array. value ( i) ;
821808 let other_digest = TDigestWrapper :: from_bytes ( bytes)
822- . map_err ( |e| DataFusionError :: Execution ( e ) ) ?;
809+ . map_err ( DataFusionError :: Execution ) ?;
823810
824811 self . digest . merge ( & other_digest) ;
825812 }
@@ -913,15 +900,15 @@ impl ScalarUDFImpl for ApproxPercentileUDF {
913900 let percentile = percentile_values. value ( i) ;
914901
915902 // Validate percentile is between 0 and 1
916- if percentile < 0.0 || percentile > 1.0 {
903+ if ! ( 0.0 ..= 1.0 ) . contains ( & percentile ) {
917904 return Err ( DataFusionError :: Execution (
918905 format ! ( "Percentile must be between 0 and 1, got {}" , percentile) ,
919906 ) ) ;
920907 }
921908
922909 let digest_bytes = digest_values. value ( i) ;
923910 let wrapper = TDigestWrapper :: from_bytes ( digest_bytes)
924- . map_err ( |e| DataFusionError :: Execution ( e ) ) ?;
911+ . map_err ( DataFusionError :: Execution ) ?;
925912
926913 match wrapper. to_digest ( ) {
927914 Some ( digest) => {
0 commit comments