From 71cb68b2f47cbf9b99724616dcc4d9e205399450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Carr?= Date: Sat, 27 Jun 2020 17:36:06 -0700 Subject: [PATCH] feat(postgres) Create bindings for PgInterval --- sqlx-core/src/postgres/type_info.rs | 19 ++- sqlx-core/src/postgres/types/interval.rs | 147 +++++++++++++++++++++++ sqlx-core/src/postgres/types/mod.rs | 3 + sqlx-macros/src/database/postgres.rs | 2 + tests/postgres/types.rs | 30 ++++- 5 files changed, 194 insertions(+), 7 deletions(-) create mode 100644 sqlx-core/src/postgres/types/interval.rs diff --git a/sqlx-core/src/postgres/type_info.rs b/sqlx-core/src/postgres/type_info.rs index 303fd264..88afdc91 100644 --- a/sqlx-core/src/postgres/type_info.rs +++ b/sqlx-core/src/postgres/type_info.rs @@ -83,6 +83,8 @@ pub enum PgType { TimeArray, Timestamptz, TimestamptzArray, + Interval, + IntervalArray, NumericArray, Timetz, TimetzArray, @@ -92,7 +94,6 @@ pub enum PgType { VarbitArray, Numeric, Record, - Interval, RecordArray, Uuid, UuidArray, @@ -285,6 +286,8 @@ impl PgType { 1183 => PgType::TimeArray, 1184 => PgType::Timestamptz, 1185 => PgType::TimestamptzArray, + 1186 => PgType::Interval, + 1187 => PgType::IntervalArray, 1231 => PgType::NumericArray, 1266 => PgType::Timetz, 1270 => PgType::TimetzArray, @@ -294,7 +297,6 @@ impl PgType { 1563 => PgType::VarbitArray, 1700 => PgType::Numeric, 2249 => PgType::Record, - 2281 => PgType::Interval, 2287 => PgType::RecordArray, 2950 => PgType::Uuid, 2951 => PgType::UuidArray, @@ -389,6 +391,8 @@ impl PgType { PgType::TimeArray => 1183, PgType::Timestamptz => 1184, PgType::TimestamptzArray => 1185, + PgType::Interval => 1186, + PgType::IntervalArray => 1187, PgType::NumericArray => 1231, PgType::Timetz => 1266, PgType::TimetzArray => 1270, @@ -398,7 +402,6 @@ impl PgType { PgType::VarbitArray => 1563, PgType::Numeric => 1700, PgType::Record => 2249, - PgType::Interval => 2281, PgType::RecordArray => 2287, PgType::Uuid => 2950, PgType::UuidArray => 2951, @@ -488,6 +491,8 @@ impl PgType { PgType::TimeArray => "TIME[]", PgType::Timestamptz => "TIMESTAMPTZ", PgType::TimestamptzArray => "TIMESTAMPTZ[]", + PgType::Interval => "INTERVAL", + PgType::IntervalArray => "INTERVAL[]", PgType::NumericArray => "NUMERIC[]", PgType::Timetz => "TIMETZ", PgType::TimetzArray => "TIMETZ[]", @@ -497,7 +502,6 @@ impl PgType { PgType::VarbitArray => "VARBIT[]", PgType::Numeric => "NUMERIC", PgType::Record => "RECORD", - PgType::Interval => "INTERVAL", PgType::RecordArray => "RECORD[]", PgType::Uuid => "UUID", PgType::UuidArray => "UUID[]", @@ -584,6 +588,8 @@ impl PgType { PgType::TimeArray => "_time", PgType::Timestamptz => "timestamptz", PgType::TimestamptzArray => "_timestamptz", + PgType::Interval => "interval", + PgType::IntervalArray => "_interval", PgType::NumericArray => "_numeric", PgType::Timetz => "timetz", PgType::TimetzArray => "_timetz", @@ -593,7 +599,6 @@ impl PgType { PgType::VarbitArray => "_varbit", PgType::Numeric => "numeric", PgType::Record => "record", - PgType::Interval => "interval", PgType::RecordArray => "_record", PgType::Uuid => "uuid", PgType::UuidArray => "_uuid", @@ -680,6 +685,8 @@ impl PgType { PgType::TimeArray => &PgTypeKind::Array(PgTypeInfo(PgType::Time)), PgType::Timestamptz => &PgTypeKind::Simple, PgType::TimestamptzArray => &PgTypeKind::Array(PgTypeInfo(PgType::Timestamptz)), + PgType::Interval => &PgTypeKind::Simple, + PgType::IntervalArray => &PgTypeKind::Array(PgTypeInfo(PgType::Interval)), PgType::NumericArray => &PgTypeKind::Array(PgTypeInfo(PgType::Numeric)), PgType::Timetz => &PgTypeKind::Simple, PgType::TimetzArray => &PgTypeKind::Array(PgTypeInfo(PgType::Timetz)), @@ -689,7 +696,6 @@ impl PgType { PgType::VarbitArray => &PgTypeKind::Array(PgTypeInfo(PgType::Varbit)), PgType::Numeric => &PgTypeKind::Simple, PgType::Record => &PgTypeKind::Simple, - PgType::Interval => &PgTypeKind::Simple, PgType::RecordArray => &PgTypeKind::Array(PgTypeInfo(PgType::Record)), PgType::Uuid => &PgTypeKind::Simple, PgType::UuidArray => &PgTypeKind::Array(PgTypeInfo(PgType::Uuid)), @@ -866,6 +872,7 @@ impl PgTypeInfo { // time interval pub(crate) const INTERVAL: Self = Self(PgType::Interval); + pub(crate) const INTERVAL_ARRAY: Self = Self(PgType::IntervalArray); // // geometric types diff --git a/sqlx-core/src/postgres/types/interval.rs b/sqlx-core/src/postgres/types/interval.rs new file mode 100644 index 00000000..f0794068 --- /dev/null +++ b/sqlx-core/src/postgres/types/interval.rs @@ -0,0 +1,147 @@ +use std::mem; + +use byteorder::{NetworkEndian, ReadBytesExt}; + +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; +use crate::types::Type; + +/// PostgreSQL INTERVAL type binding +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct PgInterval { + pub months: i32, + pub days: i32, + pub microseconds: i64, +} + +impl Type for PgInterval { + fn type_info() -> PgTypeInfo { + PgTypeInfo::INTERVAL + } +} + +impl Type for [PgInterval] { + fn type_info() -> PgTypeInfo { + PgTypeInfo::INTERVAL_ARRAY + } +} + +impl<'de> Decode<'de, Postgres> for PgInterval { + fn decode(value: PgValueRef<'de>) -> Result { + match value.format() { + PgValueFormat::Binary => { + let mut buf = value.as_bytes()?; + let microseconds = buf.read_i64::()?; + let days = buf.read_i32::()?; + let months = buf.read_i32::()?; + Ok(PgInterval { + months, + days, + microseconds, + }) + } + PgValueFormat::Text => Err("INTERVAL Text format unsuported".into()), + } + } +} + +impl Encode<'_, Postgres> for PgInterval { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { + if let IsNull::Yes = Encode::::encode(&self.microseconds, buf) { + return IsNull::Yes; + } + if let IsNull::Yes = Encode::::encode(&self.days, buf) { + return IsNull::Yes; + } + if let IsNull::Yes = Encode::::encode(&self.months, buf) { + return IsNull::Yes; + } + IsNull::No + } + + fn size_hint(&self) -> usize { + 2 * mem::size_of::() + } +} + +#[test] +fn test_encode_interval() { + let mut buf = PgArgumentBuffer::default(); + + let interval = PgInterval { + months: 0, + days: 0, + microseconds: 0, + }; + assert!(matches!( + Encode::::encode(&interval, &mut buf), + IsNull::No + )); + assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + buf.clear(); + + let interval = PgInterval { + months: 0, + days: 0, + microseconds: 1_000, + }; + assert!(matches!( + Encode::::encode(&interval, &mut buf), + IsNull::No + )); + assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 3, 232, 0, 0, 0, 0, 0, 0, 0, 0]); + buf.clear(); + + let interval = PgInterval { + months: 0, + days: 0, + microseconds: 1_000_000, + }; + assert!(matches!( + Encode::::encode(&interval, &mut buf), + IsNull::No + )); + assert_eq!(&**buf, [0, 0, 0, 0, 0, 15, 66, 64, 0, 0, 0, 0, 0, 0, 0, 0]); + buf.clear(); + + let interval = PgInterval { + months: 0, + days: 0, + microseconds: 3_600_000_000, + }; + assert!(matches!( + Encode::::encode(&interval, &mut buf), + IsNull::No + )); + assert_eq!( + &**buf, + [0, 0, 0, 0, 214, 147, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + buf.clear(); + + let interval = PgInterval { + months: 0, + days: 1, + microseconds: 0, + }; + assert!(matches!( + Encode::::encode(&interval, &mut buf), + IsNull::No + )); + assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]); + buf.clear(); + + let interval = PgInterval { + months: 1, + days: 0, + microseconds: 0, + }; + assert!(matches!( + Encode::::encode(&interval, &mut buf), + IsNull::No + )); + assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + buf.clear(); +} diff --git a/sqlx-core/src/postgres/types/mod.rs b/sqlx-core/src/postgres/types/mod.rs index b65c29d4..a3eed316 100644 --- a/sqlx-core/src/postgres/types/mod.rs +++ b/sqlx-core/src/postgres/types/mod.rs @@ -12,6 +12,7 @@ //! | `f64` | DOUBLE PRECISION, FLOAT8 | //! | `&str`, `String` | VARCHAR, CHAR(N), TEXT, NAME | //! | `&[u8]`, `Vec` | BYTEA | +//! | `sqlx::postgres::types::PgInterval` | INTERVAL | //! //! ### [`chrono`](https://crates.io/crates/chrono) //! @@ -137,6 +138,7 @@ mod bool; mod bytes; mod float; mod int; +mod interval; mod range; mod record; mod str; @@ -163,6 +165,7 @@ mod json; #[cfg(feature = "ipnetwork")] mod ipnetwork; +pub use interval::PgInterval; pub use range::PgRange; // used in derive(Type) for `struct` diff --git a/sqlx-macros/src/database/postgres.rs b/sqlx-macros/src/database/postgres.rs index f68eff61..3394588a 100644 --- a/sqlx-macros/src/database/postgres.rs +++ b/sqlx-macros/src/database/postgres.rs @@ -41,6 +41,8 @@ impl_database_ext! { #[cfg(feature = "time")] sqlx::types::time::OffsetDateTime, + sqlx::postgres::types::PgInterval, + #[cfg(feature = "bigdecimal")] sqlx::types::BigDecimal, diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index a6518c2e..bf6f7e9b 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -2,7 +2,7 @@ extern crate time_ as time; use std::ops::Bound; -use sqlx::postgres::types::PgRange; +use sqlx::postgres::types::{PgInterval, PgRange}; use sqlx::postgres::Postgres; use sqlx_test::{test_decode_type, test_prepared_type, test_type}; @@ -360,3 +360,31 @@ test_type!(int4range>(Postgres, "'[1,2)'::int4range" == PgRange::from((INC1, EXC2)), "'[1,2]'::int4range" == PgRange::from((INC1, EXC3)), )); + +test_prepared_type!(interval( + Postgres, + "INTERVAL '1h'" + == PgInterval { + months: 0, + days: 0, + microseconds: 3_600_000_000 + }, + "INTERVAL '-1 hours'" + == PgInterval { + months: 0, + days: 0, + microseconds: -3_600_000_000 + }, + "INTERVAL '3 months 12 days 1h 15 minutes 10 second '" + == PgInterval { + months: 3, + days: 12, + microseconds: (3_600 + 15 * 60 + 10) * 1_000_000 + }, + "INTERVAL '03:10:20.116100'" + == PgInterval { + months: 0, + days: 0, + microseconds: (3 * 3_600 + 10 * 60 + 20) * 1_000_000 + 116100 + }, +));