diff --git a/src/expr/src/scalar/func/impls/interval.rs b/src/expr/src/scalar/func/impls/interval.rs index 3bf71a9f2dfc..fb477f95028c 100644 --- a/src/expr/src/scalar/func/impls/interval.rs +++ b/src/expr/src/scalar/func/impls/interval.rs @@ -30,7 +30,7 @@ sqlfunc!( i = Interval::new(0, 86400, 0) .unwrap() .checked_add( - &Interval::new(0, i.dur_as_secs() % (24 * 60 * 60), i.nanoseconds() as i64) + &Interval::new(0, i.dur_as_secs() % (24 * 60 * 60), i.nanoseconds().into()) .unwrap(), ) .unwrap(); diff --git a/src/expr/src/scalar/func/impls/time.rs b/src/expr/src/scalar/func/impls/time.rs index c0d20745979c..2444494c04bd 100644 --- a/src/expr/src/scalar/func/impls/time.rs +++ b/src/expr/src/scalar/func/impls/time.rs @@ -31,7 +31,7 @@ sqlfunc!( Interval::new( 0, t.num_seconds_from_midnight() as i64, - t.nanosecond() as i64, + t.nanosecond().into(), ) .map_err(|_| EvalError::IntervalOutOfRange) } diff --git a/src/pgrepr/src/value/interval.rs b/src/pgrepr/src/value/interval.rs index b55b7ac4153b..130ed4f2c990 100644 --- a/src/pgrepr/src/value/interval.rs +++ b/src/pgrepr/src/value/interval.rs @@ -46,10 +46,18 @@ impl ToSql for Interval { // Our intervals are guaranteed to fit within SQL's min/max intervals, // so this is compression is guaranteed to be lossless. For details, see // `repr::scalar::datetime::compute_interval`. - let days = std::cmp::min(self.0.days() as i128, i32::MAX as i128); + let days: i128 = std::cmp::max( + std::cmp::min(self.0.days().into(), i32::MAX.into()), + i32::MIN.into(), + ); let ns = self.0.duration - days * 24 * 60 * 60 * 1_000_000_000; - out.put_i64((ns / 1000) as i64); - out.put_i32(days as i32); + out.put_i64((ns / 1000).try_into().expect( + "bounds checking when creating Intervals should prevent this field from overflowing", + )); + out.put_i32( + days.try_into() + .expect("days is bound between i32::MAX and i32::MIN above"), + ); out.put_i32(self.0.months); Ok(IsNull::No) } @@ -67,7 +75,12 @@ impl<'a> FromSql<'a> for Interval { let days = raw.read_i32::()?; let months = raw.read_i32::()?; Ok(Interval( - ReprInterval::new(months, days as i64 * 24 * 60 * 60, micros * 1000).unwrap(), + ReprInterval::new( + months, + i64::from(days) * 24 * 60 * 60, + i128::from(micros) * 1000, + ) + .unwrap(), )) } diff --git a/src/repr/src/adt/datetime.rs b/src/repr/src/adt/datetime.rs index f1caf3d91dac..01b976ee6124 100644 --- a/src/repr/src/adt/datetime.rs +++ b/src/repr/src/adt/datetime.rs @@ -491,7 +491,7 @@ impl ParsedDateTime { } }; - match Interval::new(months, seconds, nanos) { + match Interval::new(months, seconds, nanos.into()) { Ok(i) => Ok(i), Err(e) => Err(e.to_string()), } diff --git a/src/repr/src/adt/interval.rs b/src/repr/src/adt/interval.rs index b09bf6466578..120a2a9a74a9 100644 --- a/src/repr/src/adt/interval.rs +++ b/src/repr/src/adt/interval.rs @@ -55,10 +55,10 @@ impl Interval { /// Constructs a new `Interval` with the specified units of time. /// /// `nanos` in excess of `999_999_999` are carried over into seconds. - pub fn new(months: i32, seconds: i64, nanos: i64) -> Result { + pub fn new(months: i32, seconds: i64, nanos: i128) -> Result { let i = Interval { months, - duration: i128::from(seconds) * 1_000_000_000 + i128::from(nanos), + duration: i128::from(seconds) * 1_000_000_000 + nanos, }; // Don't let our duration exceed Postgres' min/max for those same fields, // equivalent to: @@ -92,7 +92,7 @@ impl Interval { match Self::new( months, seconds, - i64::from(self.nanoseconds() + other.nanoseconds()), + (self.nanoseconds() + other.nanoseconds()).into(), ) { Ok(i) => Some(i), Err(_) => None, @@ -116,7 +116,7 @@ impl Interval { return None; } - Self::new(months as i32, seconds as i64, nanos as i64).ok() + Self::new(months as i32, seconds as i64, nanos as i128).ok() } pub fn checked_div(&self, other: f64) -> Option { @@ -136,7 +136,7 @@ impl Interval { return None; } - Self::new(months as i32, seconds as i64, nanos as i64).ok() + Self::new(months as i32, seconds as i64, nanos as i128).ok() } /// Returns the total number of whole seconds in the `Interval`'s duration. diff --git a/src/repr/src/scalar.rs b/src/repr/src/scalar.rs index dc77ad883aaa..4b5e8bb977f0 100644 --- a/src/repr/src/scalar.rs +++ b/src/repr/src/scalar.rs @@ -678,7 +678,7 @@ impl<'a> From for Datum<'a> { Interval::new( 0, duration.num_seconds(), - duration.num_nanoseconds().unwrap_or(0) % 1_000_000_000, + i128::from(duration.num_nanoseconds().unwrap_or(0)) % 1_000_000_000, ) .unwrap(), ) diff --git a/test/pgtest/binary.pt b/test/pgtest/binary.pt index 64e7bc3c42e5..736eeb9a8ea2 100644 --- a/test/pgtest/binary.pt +++ b/test/pgtest/binary.pt @@ -15,3 +15,67 @@ BindComplete DataRow {"fields":["[107, 73, 209, 255, 255, 255, 255, 255, 127, 255, 255, 255, 0, 0, 0, 0]"]} CommandComplete {"tag":"SELECT 1"} ReadyForQuery {"status":"I"} + +send +Parse {"query": "SELECT INTERVAL '2147483647 months 2147483647 days 2147483647 hours 59 minutes 59.999999 seconds'"} +Bind {"result_formats": [1]} +Execute +Sync +---- + +until +ReadyForQuery +---- +ParseComplete +BindComplete +DataRow {"fields":["[107, 73, 209, 255, 255, 255, 255, 255, 127, 255, 255, 255, 127, 255, 255, 255]"]} +CommandComplete {"tag":"SELECT 1"} +ReadyForQuery {"status":"I"} + +send +Parse {"query": "SELECT INTERVAL '-2147483648 days -48 hrs';"} +Bind {"result_formats": [1]} +Execute +Sync +---- + +until +ReadyForQuery +---- +ParseComplete +BindComplete +DataRow {"fields":["[255, 255, 255, 215, 196, 81, 64, 0, 128, 0, 0, 0, 0, 0, 0, 0]"]} +CommandComplete {"tag":"SELECT 1"} +ReadyForQuery {"status":"I"} + +send +Parse {"query": "SELECT INTERVAL '-2147483648 days -2147483648 hours -59 minutes -59.999999 seconds'"} +Bind {"result_formats": [1]} +Execute +Sync +---- + +until +ReadyForQuery +---- +ParseComplete +BindComplete +DataRow {"fields":["[148, 182, 45, 255, 41, 108, 92, 1, 128, 0, 0, 0, 0, 0, 0, 0]"]} +CommandComplete {"tag":"SELECT 1"} +ReadyForQuery {"status":"I"} + +send +Parse {"query": "SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hours -59 minutes -59.999999 seconds'"} +Bind {"result_formats": [1]} +Execute +Sync +---- + +until +ReadyForQuery +---- +ParseComplete +BindComplete +DataRow {"fields":["[148, 182, 45, 255, 41, 108, 92, 1, 128, 0, 0, 0, 128, 0, 0, 0]"]} +CommandComplete {"tag":"SELECT 1"} +ReadyForQuery {"status":"I"} diff --git a/test/sqllogictest/interval.slt b/test/sqllogictest/interval.slt index 9e5e96526e63..fd06c3c37258 100644 --- a/test/sqllogictest/interval.slt +++ b/test/sqllogictest/interval.slt @@ -701,21 +701,21 @@ statement error division by zero SELECT INTERVAL '1' YEAR / 0 ## Largest values -# See #4926 -#query T -#SELECT INTERVAL '2147483647 days 2147483647 hours 59 minutes 59.999999 seconds' -#---- -#2236962132 days 07:59:59.999999 -# -#query T -#SELECT INTERVAL '-2147483647 days -2147483647 hours -59 minutes -59.999999 seconds' -#---- -#-2236962132 days -07:59:59.999999 -# -#query T -#SELECT INTERVAL '-2147483648 days -2147483648 hours -59 minutes -59.999999 seconds' -#---- -#-2236962133 days -08:59:59.999999 + +query T +SELECT INTERVAL '2147483647 days 2147483647 hours 59 minutes 59.999999 seconds' +---- +2236962132 days 07:59:59.999999 + +query T +SELECT INTERVAL '-2147483647 days -2147483647 hours -59 minutes -59.999999 seconds' +---- +-2236962132 days -07:59:59.999999 + +query T +SELECT INTERVAL '-2147483648 days -2147483648 hours -59 minutes -59.999999 seconds' +---- +-2236962133 days -08:59:59.999999 statement error invalid input syntax for type interval: exceeds min/max interval duration SELECT INTERVAL '2147483647 days 2147483648 hours'