From 538507f19f9923782c57bbde1ee0c1af0051727d Mon Sep 17 00:00:00 2001 From: yuyawk Date: Wed, 6 Oct 2021 21:56:26 +0900 Subject: [PATCH 1/3] fix PgInterval convert failure for negative chrono::Duration --- sqlx-core/src/postgres/types/interval.rs | 25 +++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/sqlx-core/src/postgres/types/interval.rs b/sqlx-core/src/postgres/types/interval.rs index 8a5307adac..82e8d03a12 100644 --- a/sqlx-core/src/postgres/types/interval.rs +++ b/sqlx-core/src/postgres/types/interval.rs @@ -150,7 +150,30 @@ impl TryFrom for PgInterval { /// This returns an error if there is a loss of precision using nanoseconds or if there is a /// microsecond or nanosecond overflow. fn try_from(value: chrono::Duration) -> Result { - value.to_std()?.try_into() + value + .num_nanoseconds() + .map_or::, _>( + Err("Overflow has occurred for PostgreSQL `INTERVAL`".into()), + |nanoseconds| { + if nanoseconds % 1000 != 0 { + return Err( + "PostgreSQL `INTERVAL` does not support nanoseconds precision".into(), + ); + } + Ok(()) + }, + )?; + + value.num_microseconds().map_or( + Err("Overflow has occurred for PostgreSQL `INTERVAL`".into()), + |microseconds| { + Ok(Self { + months: 0, + days: 0, + microseconds: microseconds, + }) + }, + ) } } From 74b3950349fe0f9d7dd22ea613c881e41d2db3a7 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Wed, 6 Oct 2021 22:23:55 +0900 Subject: [PATCH 2/3] add unit tests for PgInterval --- sqlx-core/src/postgres/types/interval.rs | 51 ++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/sqlx-core/src/postgres/types/interval.rs b/sqlx-core/src/postgres/types/interval.rs index 82e8d03a12..f443ed1806 100644 --- a/sqlx-core/src/postgres/types/interval.rs +++ b/sqlx-core/src/postgres/types/interval.rs @@ -306,6 +306,7 @@ fn test_encode_interval() { #[test] fn test_pginterval_std() { + // Case for positive duration let interval = PgInterval { days: 0, months: 0, @@ -315,11 +316,18 @@ fn test_pginterval_std() { &PgInterval::try_from(std::time::Duration::from_micros(27_000)).unwrap(), &interval ); + + // Case when precision loss occurs + assert!(PgInterval::try_from(std::time::Duration::from_nanos(27_000_001)).is_err()); + + // Case when microsecond overflow occurs + assert!(PgInterval::try_from(std::time::Duration::from_secs(20_000_000_000_000)).is_err()); } #[test] #[cfg(feature = "chrono")] fn test_pginterval_chrono() { + // Case for positive duration let interval = PgInterval { days: 0, months: 0, @@ -329,11 +337,35 @@ fn test_pginterval_chrono() { &PgInterval::try_from(chrono::Duration::microseconds(27_000)).unwrap(), &interval ); + + // Case for negative duration + let interval = PgInterval { + days: 0, + months: 0, + microseconds: -27_000, + }; + assert_eq!( + &PgInterval::try_from(chrono::Duration::microseconds(-27_000)).unwrap(), + &interval + ); + + // Case when precision loss occurs + assert!(PgInterval::try_from(chrono::Duration::nanoseconds(27_000_001)).is_err()); + assert!(PgInterval::try_from(chrono::Duration::nanoseconds(-27_000_001)).is_err()); + + // Case when microsecond overflow occurs + assert!(PgInterval::try_from(chrono::Duration::seconds(10_000_000_000_000)).is_err()); + assert!(PgInterval::try_from(chrono::Duration::seconds(-10_000_000_000_000)).is_err()); + + // Case when nanosecond overflow occurs + assert!(PgInterval::try_from(chrono::Duration::seconds(10_000_000_000)).is_err()); + assert!(PgInterval::try_from(chrono::Duration::seconds(-10_000_000_000)).is_err()); } #[test] #[cfg(feature = "time")] fn test_pginterval_time() { + // Case for positive duration let interval = PgInterval { days: 0, months: 0, @@ -343,4 +375,23 @@ fn test_pginterval_time() { &PgInterval::try_from(time::Duration::microseconds(27_000)).unwrap(), &interval ); + + // Case for negative duration + let interval = PgInterval { + days: 0, + months: 0, + microseconds: -27_000, + }; + assert_eq!( + &PgInterval::try_from(time::Duration::microseconds(-27_000)).unwrap(), + &interval + ); + + // Case when precision loss occurs + assert!(PgInterval::try_from(time::Duration::nanoseconds(27_000_001)).is_err()); + assert!(PgInterval::try_from(time::Duration::nanoseconds(-27_000_001)).is_err()); + + // Case when microsecond overflow occurs + assert!(PgInterval::try_from(time::Duration::seconds(10_000_000_000_000)).is_err()); + assert!(PgInterval::try_from(time::Duration::seconds(-10_000_000_000_000)).is_err()); } From 032092b2a802e73c66a8334a49904dcd0bea4fe7 Mon Sep 17 00:00:00 2001 From: yuyawk Date: Fri, 8 Oct 2021 00:41:31 +0900 Subject: [PATCH 3/3] Fix: remove redundancy because nanosecond overflow implies microsecond overflow --- sqlx-core/src/postgres/types/interval.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sqlx-core/src/postgres/types/interval.rs b/sqlx-core/src/postgres/types/interval.rs index f443ed1806..42805a56b5 100644 --- a/sqlx-core/src/postgres/types/interval.rs +++ b/sqlx-core/src/postgres/types/interval.rs @@ -148,7 +148,7 @@ impl TryFrom for PgInterval { /// Convert a `chrono::Duration` to a `PgInterval`. /// /// This returns an error if there is a loss of precision using nanoseconds or if there is a - /// microsecond or nanosecond overflow. + /// nanosecond overflow. fn try_from(value: chrono::Duration) -> Result { value .num_nanoseconds() @@ -353,10 +353,6 @@ fn test_pginterval_chrono() { assert!(PgInterval::try_from(chrono::Duration::nanoseconds(27_000_001)).is_err()); assert!(PgInterval::try_from(chrono::Duration::nanoseconds(-27_000_001)).is_err()); - // Case when microsecond overflow occurs - assert!(PgInterval::try_from(chrono::Duration::seconds(10_000_000_000_000)).is_err()); - assert!(PgInterval::try_from(chrono::Duration::seconds(-10_000_000_000_000)).is_err()); - // Case when nanosecond overflow occurs assert!(PgInterval::try_from(chrono::Duration::seconds(10_000_000_000)).is_err()); assert!(PgInterval::try_from(chrono::Duration::seconds(-10_000_000_000)).is_err());