feat(postgres) Create bindings for PgInterval

This commit is contained in:
Loïc Carr 2020-06-27 17:36:06 -07:00
parent 72c4e040bc
commit 71cb68b2f4
5 changed files with 194 additions and 7 deletions

View file

@ -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

View file

@ -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<Postgres> for PgInterval {
fn type_info() -> PgTypeInfo {
PgTypeInfo::INTERVAL
}
}
impl Type<Postgres> for [PgInterval] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::INTERVAL_ARRAY
}
}
impl<'de> Decode<'de, Postgres> for PgInterval {
fn decode(value: PgValueRef<'de>) -> Result<Self, BoxDynError> {
match value.format() {
PgValueFormat::Binary => {
let mut buf = value.as_bytes()?;
let microseconds = buf.read_i64::<NetworkEndian>()?;
let days = buf.read_i32::<NetworkEndian>()?;
let months = buf.read_i32::<NetworkEndian>()?;
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::<Postgres>::encode(&self.microseconds, buf) {
return IsNull::Yes;
}
if let IsNull::Yes = Encode::<Postgres>::encode(&self.days, buf) {
return IsNull::Yes;
}
if let IsNull::Yes = Encode::<Postgres>::encode(&self.months, buf) {
return IsNull::Yes;
}
IsNull::No
}
fn size_hint(&self) -> usize {
2 * mem::size_of::<i64>()
}
}
#[test]
fn test_encode_interval() {
let mut buf = PgArgumentBuffer::default();
let interval = PgInterval {
months: 0,
days: 0,
microseconds: 0,
};
assert!(matches!(
Encode::<Postgres>::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::<Postgres>::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::<Postgres>::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::<Postgres>::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::<Postgres>::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::<Postgres>::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();
}

View file

@ -12,6 +12,7 @@
//! | `f64` | DOUBLE PRECISION, FLOAT8 |
//! | `&str`, `String` | VARCHAR, CHAR(N), TEXT, NAME |
//! | `&[u8]`, `Vec<u8>` | 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`

View file

@ -41,6 +41,8 @@ impl_database_ext! {
#[cfg(feature = "time")]
sqlx::types::time::OffsetDateTime,
sqlx::postgres::types::PgInterval,
#[cfg(feature = "bigdecimal")]
sqlx::types::BigDecimal,

View file

@ -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<PgRange<i32>>(Postgres,
"'[1,2)'::int4range" == PgRange::from((INC1, EXC2)),
"'[1,2]'::int4range" == PgRange::from((INC1, EXC3)),
));
test_prepared_type!(interval<PgInterval>(
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
},
));