@@ -447,7 +447,7 @@ impl FromCell for NaiveDateTime {
447447
448448 fn build_plan ( column : & Column ) -> Result < Self :: Plan > {
449449 match column. ty ( ) {
450- ColumnType :: TimestampNtz { scale } => TimestampPlan :: new ( i64 :: from ( scale. unwrap_or ( 9 ) ) )
450+ ColumnType :: TimestampNtz { scale } => TimestampPlan :: from_metadata_scale ( * scale)
451451 . map_err ( |detail| incompatible_column :: < Self > ( column, Some ( detail) ) ) ,
452452 _ => Err ( incompatible_column :: < Self > ( column, None ) ) ,
453453 }
@@ -474,10 +474,10 @@ impl FromCell for DateTime<Utc> {
474474
475475 fn build_plan ( column : & Column ) -> Result < Self :: Plan > {
476476 match column. ty ( ) {
477- ColumnType :: TimestampLtz { scale } => TimestampPlan :: new ( i64 :: from ( scale. unwrap_or ( 9 ) ) )
477+ ColumnType :: TimestampLtz { scale } => TimestampPlan :: from_metadata_scale ( * scale)
478478 . map ( UtcTimestampPlan :: Ltz )
479479 . map_err ( |detail| incompatible_column :: < Self > ( column, Some ( detail) ) ) ,
480- ColumnType :: TimestampTz { scale } => TimestampPlan :: new ( i64 :: from ( scale. unwrap_or ( 9 ) ) )
480+ ColumnType :: TimestampTz { scale } => TimestampPlan :: from_metadata_scale ( * scale)
481481 . map ( UtcTimestampPlan :: Tz )
482482 . map_err ( |detail| incompatible_column :: < Self > ( column, Some ( detail) ) ) ,
483483 _ => Err ( incompatible_column :: < Self > ( column, None ) ) ,
@@ -499,7 +499,7 @@ impl FromCell for DateTime<FixedOffset> {
499499
500500 fn build_plan ( column : & Column ) -> Result < Self :: Plan > {
501501 match column. ty ( ) {
502- ColumnType :: TimestampTz { scale } => TimestampPlan :: new ( i64 :: from ( scale. unwrap_or ( 9 ) ) )
502+ ColumnType :: TimestampTz { scale } => TimestampPlan :: from_metadata_scale ( * scale)
503503 . map_err ( |detail| incompatible_column :: < Self > ( column, Some ( detail) ) ) ,
504504 _ => Err ( incompatible_column :: < Self > ( column, None ) ) ,
505505 }
@@ -568,7 +568,7 @@ impl TimePlan {
568568/// Precomputed scale factors for a timestamp column, shared by the `NTZ` / `LTZ` / `TZ` decoders.
569569#[ derive( Clone , Copy ) ]
570570pub struct TimestampPlan {
571- scale : i64 ,
571+ scale : usize ,
572572 /// `10^scale`, converting between the scaled integer wire value and whole seconds.
573573 scale_factor : i128 ,
574574 /// `10^(9 - scale)`, scaling the fractional remainder up to nanoseconds.
@@ -582,7 +582,11 @@ impl TimestampPlan {
582582 nanos_multiplier : 1_000_000_000 ,
583583 } ;
584584
585- fn new ( scale : i64 ) -> StdResult < Self , String > {
585+ fn from_metadata_scale ( scale : Option < u8 > ) -> StdResult < Self , String > {
586+ Self :: new ( scale. unwrap_or ( 9 ) )
587+ }
588+
589+ fn new ( scale : u8 ) -> StdResult < Self , String > {
586590 let scale = validate_ts_scale ( scale) ?;
587591 // `scale` is `0..=9`, so neither power can overflow.
588592 Ok ( Self {
@@ -593,11 +597,11 @@ impl TimestampPlan {
593597 }
594598}
595599
596- fn validate_ts_scale ( scale : i64 ) -> StdResult < i64 , String > {
597- if ! ( 0 ..= 9 ) . contains ( & scale) {
600+ fn validate_ts_scale ( scale : u8 ) -> StdResult < usize , String > {
601+ if scale > 9 {
598602 return Err ( format ! ( "invalid timestamp scale: {scale} (expected 0..=9)" ) ) ;
599603 }
600- Ok ( scale)
604+ Ok ( usize :: from ( scale) )
601605}
602606
603607fn parse_scaled_decimal_i128 (
@@ -611,52 +615,71 @@ fn parse_scaled_decimal_i128(
611615 return Err ( format ! ( "Could not decode {kind}: {s}" ) ) ;
612616 }
613617
614- let ( negative, digits) = if let Some ( rest) = s. strip_prefix ( '-' ) {
615- ( true , rest)
616- } else if let Some ( rest) = s. strip_prefix ( '+' ) {
617- ( false , rest)
618- } else {
619- ( false , s)
618+ let bytes = s. as_bytes ( ) ;
619+ let mut i = 0usize ;
620+ let negative = match bytes[ 0 ] {
621+ b'-' => {
622+ i = 1 ;
623+ true
624+ }
625+ b'+' => {
626+ i = 1 ;
627+ false
628+ }
629+ _ => false ,
620630 } ;
621- if digits . is_empty ( ) {
631+ if i == bytes . len ( ) {
622632 return Err ( format ! ( "Could not decode {kind}: {s}" ) ) ;
623633 }
624634
625- let ( whole_str, frac_str) = match digits. split_once ( '.' ) {
626- Some ( ( whole, frac) ) if !frac. is_empty ( ) => ( whole, Some ( frac) ) ,
627- Some ( _) => return Err ( format ! ( "Could not decode {kind}: {s}" ) ) ,
628- None => ( digits, None ) ,
629- } ;
630- if whole_str. is_empty ( ) || !whole_str. as_bytes ( ) . iter ( ) . all ( |b| b. is_ascii_digit ( ) ) {
635+ let mut whole = 0i128 ;
636+ let whole_start = i;
637+ while i < bytes. len ( ) {
638+ let b = bytes[ i] ;
639+ if !b. is_ascii_digit ( ) {
640+ break ;
641+ }
642+ whole = whole
643+ . checked_mul ( 10 )
644+ . and_then ( |value| value. checked_add ( i128:: from ( b - b'0' ) ) )
645+ . ok_or_else ( || format ! ( "Could not decode {kind}: {s}" ) ) ?;
646+ i += 1 ;
647+ }
648+ if i == whole_start {
631649 return Err ( format ! ( "Could not decode {kind}: {s}" ) ) ;
632650 }
633651
634- let whole = whole_str
635- . parse :: < i128 > ( )
636- . map_err ( |_| format ! ( "Could not decode {kind}: {s}" ) ) ?;
637652 let mut scaled = whole
638653 . checked_mul ( plan. scale_factor )
639654 . ok_or_else ( || format ! ( "Could not decode {kind}: {s}" ) ) ?;
640655
641- if let Some ( frac_str ) = frac_str {
642- if scale == 0 {
656+ if i < bytes . len ( ) {
657+ if bytes [ i ] != b'.' {
643658 return Err ( format ! ( "Could not decode {kind}: {s}" ) ) ;
644659 }
645- if !frac_str. as_bytes ( ) . iter ( ) . all ( |b| b. is_ascii_digit ( ) ) {
660+ i += 1 ;
661+ if i == bytes. len ( ) {
646662 return Err ( format ! ( "Could not decode {kind}: {s}" ) ) ;
647663 }
648- if frac_str . len ( ) > scale as usize {
664+ if scale == 0 {
649665 return Err ( format ! ( "Could not decode {kind}: {s}" ) ) ;
650666 }
651667
652668 let mut frac = 0i128 ;
653- for b in frac_str. bytes ( ) {
669+ let mut frac_len = 0usize ;
670+ while i < bytes. len ( ) {
671+ let b = bytes[ i] ;
672+ if !b. is_ascii_digit ( ) || frac_len >= scale {
673+ return Err ( format ! ( "Could not decode {kind}: {s}" ) ) ;
674+ }
654675 frac = frac
655676 . checked_mul ( 10 )
656677 . and_then ( |value| value. checked_add ( i128:: from ( b - b'0' ) ) )
657678 . ok_or_else ( || format ! ( "Could not decode {kind}: {s}" ) ) ?;
679+ frac_len += 1 ;
680+ i += 1 ;
658681 }
659- for _ in frac_str . len ( ) .. scale as usize {
682+ for _ in frac_len.. scale {
660683 frac = frac
661684 . checked_mul ( 10 )
662685 . ok_or_else ( || format ! ( "Could not decode {kind}: {s}" ) ) ?;
@@ -706,7 +729,7 @@ pub(crate) fn parse_timestamp_epoch_with(
706729 parse_timestamp_epoch_scaled ( scaled, plan, s, "timestamp" )
707730}
708731
709- pub ( crate ) fn parse_timestamp_epoch ( s : & str , scale : i64 ) -> StdResult < DateTime < Utc > , String > {
732+ pub ( crate ) fn parse_timestamp_epoch ( s : & str , scale : u8 ) -> StdResult < DateTime < Utc > , String > {
710733 parse_timestamp_epoch_with ( s, & TimestampPlan :: new ( scale) ?)
711734}
712735
@@ -794,7 +817,7 @@ pub(crate) fn parse_timestamp_tz_with_offset_with(
794817
795818pub ( crate ) fn parse_timestamp_tz_with_offset (
796819 s : & str ,
797- scale : i64 ,
820+ scale : u8 ,
798821) -> StdResult < DateTime < FixedOffset > , String > {
799822 parse_timestamp_tz_with_offset_with ( s, & TimestampPlan :: new ( scale) ?)
800823}
@@ -930,16 +953,16 @@ mod tests {
930953 rowset:: parser:: parse_inline_result_table,
931954 } ;
932955
933- fn timestamp_scale ( ty : & ColumnType ) -> Option < i64 > {
956+ fn timestamp_scale ( ty : & ColumnType ) -> Option < u8 > {
934957 match ty {
935958 ColumnType :: TimestampNtz { scale }
936959 | ColumnType :: TimestampLtz { scale }
937- | ColumnType :: TimestampTz { scale } => scale. map ( i64 :: from ) ,
960+ | ColumnType :: TimestampTz { scale } => * scale,
938961 _ => None ,
939962 }
940963 }
941964
942- fn parse_timestamp_tz_as_utc ( s : & str , scale : i64 ) -> StdResult < DateTime < Utc > , String > {
965+ fn parse_timestamp_tz_as_utc ( s : & str , scale : u8 ) -> StdResult < DateTime < Utc > , String > {
943966 parse_timestamp_tz_as_utc_with ( s, & TimestampPlan :: new ( scale) ?)
944967 }
945968
@@ -1012,7 +1035,7 @@ mod tests {
10121035 /// Snowflake documents timestamp scales in the `0..=9` range.
10131036 #[ test]
10141037 fn parse_timestamp_epoch_rejects_out_of_range_scale ( ) {
1015- for scale in [ - 1 , 10 ] {
1038+ for scale in [ 10 , u8 :: MAX ] {
10161039 let err = parse_timestamp_epoch ( "0.1" , scale) . unwrap_err ( ) ;
10171040 assert ! ( err. contains( "invalid timestamp scale" ) , "actual: {err}" ) ;
10181041 }
0 commit comments