refactor: split up postgres chrono/time modules

This commit is contained in:
Ryan Leckey 2020-07-17 02:24:30 -07:00
parent e413cd6b0b
commit e285f0858f
10 changed files with 247 additions and 208 deletions

View File

@ -0,0 +1,51 @@
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;
use chrono::{Duration, NaiveDate};
use std::mem;
impl Type<Postgres> for NaiveDate {
fn type_info() -> PgTypeInfo {
PgTypeInfo::DATE
}
}
impl Type<Postgres> for [NaiveDate] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::DATE_ARRAY
}
}
impl Type<Postgres> for Vec<NaiveDate> {
fn type_info() -> PgTypeInfo {
<[NaiveDate] as Type<Postgres>>::type_info()
}
}
impl Encode<'_, Postgres> for NaiveDate {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// DATE is encoded as the days since epoch
let days = (*self - NaiveDate::from_ymd(2000, 1, 1)).num_days() as i32;
Encode::<Postgres>::encode(&days, buf)
}
fn size_hint(&self) -> usize {
mem::size_of::<i32>()
}
}
impl<'r> Decode<'r, Postgres> for NaiveDate {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// DATE is encoded as the days since epoch
let days: i32 = Decode::<Postgres>::decode(value)?;
NaiveDate::from_ymd(2000, 1, 1) + Duration::days(days.into())
}
PgValueFormat::Text => NaiveDate::parse_from_str(value.as_str()?, "%Y-%m-%d")?,
})
}
}

View File

@ -1,24 +1,10 @@
use std::mem;
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
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;
impl Type<Postgres> for NaiveTime {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIME
}
}
impl Type<Postgres> for NaiveDate {
fn type_info() -> PgTypeInfo {
PgTypeInfo::DATE
}
}
use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime, TimeZone, Utc};
use std::mem;
impl Type<Postgres> for NaiveDateTime {
fn type_info() -> PgTypeInfo {
@ -32,18 +18,6 @@ impl<Tz: TimeZone> Type<Postgres> for DateTime<Tz> {
}
}
impl Type<Postgres> for [NaiveTime] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIME_ARRAY
}
}
impl Type<Postgres> for [NaiveDate] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::DATE_ARRAY
}
}
impl Type<Postgres> for [NaiveDateTime] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIMESTAMP_ARRAY
@ -56,18 +30,6 @@ impl<Tz: TimeZone> Type<Postgres> for [DateTime<Tz>] {
}
}
impl Type<Postgres> for Vec<NaiveTime> {
fn type_info() -> PgTypeInfo {
<[NaiveTime] as Type<Postgres>>::type_info()
}
}
impl Type<Postgres> for Vec<NaiveDate> {
fn type_info() -> PgTypeInfo {
<[NaiveDate] as Type<Postgres>>::type_info()
}
}
impl Type<Postgres> for Vec<NaiveDateTime> {
fn type_info() -> PgTypeInfo {
<[NaiveDateTime] as Type<Postgres>>::type_info()
@ -80,61 +42,6 @@ impl<Tz: TimeZone> Type<Postgres> for Vec<DateTime<Tz>> {
}
}
impl Encode<'_, Postgres> for NaiveTime {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// TIME is encoded as the microseconds since midnight
// NOTE: panic! is on overflow and 1 day does not have enough micros to overflow
let us = (*self - NaiveTime::from_hms(0, 0, 0))
.num_microseconds()
.unwrap();
Encode::<Postgres>::encode(&us, buf)
}
fn size_hint(&self) -> usize {
mem::size_of::<u64>()
}
}
impl<'r> Decode<'r, Postgres> for NaiveTime {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// TIME is encoded as the microseconds since midnight
let us: i64 = Decode::<Postgres>::decode(value)?;
NaiveTime::from_hms(0, 0, 0) + Duration::microseconds(us)
}
PgValueFormat::Text => NaiveTime::parse_from_str(value.as_str()?, "%H:%M:%S%.f")?,
})
}
}
impl Encode<'_, Postgres> for NaiveDate {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// DATE is encoded as the days since epoch
let days = (*self - NaiveDate::from_ymd(2000, 1, 1)).num_days() as i32;
Encode::<Postgres>::encode(&days, buf)
}
fn size_hint(&self) -> usize {
mem::size_of::<i32>()
}
}
impl<'r> Decode<'r, Postgres> for NaiveDate {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// DATE is encoded as the days since epoch
let days: i32 = Decode::<Postgres>::decode(value)?;
NaiveDate::from_ymd(2000, 1, 1) + Duration::days(days.into())
}
PgValueFormat::Text => NaiveDate::parse_from_str(value.as_str()?, "%Y-%m-%d")?,
})
}
}
impl Encode<'_, Postgres> for NaiveDateTime {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// FIXME: We should *really* be returning an error, Encode needs to be fallible
@ -143,6 +50,7 @@ impl Encode<'_, Postgres> for NaiveDateTime {
let us = (*self - epoch)
.num_microseconds()
.unwrap_or_else(|| panic!("NaiveDateTime out of range for Postgres: {:?}", self));
Encode::<Postgres>::encode(&us, buf)
}

View File

@ -0,0 +1,3 @@
mod date;
mod datetime;
mod time;

View File

@ -0,0 +1,55 @@
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;
use chrono::{Duration, NaiveTime};
use std::mem;
impl Type<Postgres> for NaiveTime {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIME
}
}
impl Type<Postgres> for [NaiveTime] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIME_ARRAY
}
}
impl Type<Postgres> for Vec<NaiveTime> {
fn type_info() -> PgTypeInfo {
<[NaiveTime] as Type<Postgres>>::type_info()
}
}
impl Encode<'_, Postgres> for NaiveTime {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// TIME is encoded as the microseconds since midnight
// NOTE: panic! is on overflow and 1 day does not have enough micros to overflow
let us = (*self - NaiveTime::from_hms(0, 0, 0))
.num_microseconds()
.unwrap();
Encode::<Postgres>::encode(&us, buf)
}
fn size_hint(&self) -> usize {
mem::size_of::<u64>()
}
}
impl<'r> Decode<'r, Postgres> for NaiveTime {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// TIME is encoded as the microseconds since midnight
let us: i64 = Decode::<Postgres>::decode(value)?;
NaiveTime::from_hms(0, 0, 0) + Duration::microseconds(us)
}
PgValueFormat::Text => NaiveTime::parse_from_str(value.as_str()?, "%H:%M:%S%.f")?,
})
}
}

View File

@ -5,6 +5,7 @@
//! | Rust type | Postgres type(s) |
//! |---------------------------------------|------------------------------------------------------|
//! | `bool` | BOOL |
//! | `i8` | "CHAR" |
//! | `i16` | SMALLINT, SMALLSERIAL, INT2 |
//! | `i32` | INT, SERIAL, INT4 |
//! | `i64` | BIGINT, BIGSERIAL, INT8 |
@ -143,11 +144,6 @@
//! enum Mood { Sad = 0, Ok = 1, Happy = 2 }
//! ```
//!
//! # Nullable
//!
//! In addition, `Option<T>` is supported where `T` implements `Type`. An `Option<T>` represents
//! a potentially `NULL` value from Postgres.
//!
use crate::postgres::type_info::PgTypeKind;
use crate::postgres::{PgTypeInfo, Postgres};

View File

@ -0,0 +1,52 @@
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::postgres::types::time::PG_EPOCH;
use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
use crate::types::Type;
use std::mem;
use time::{Date, Duration};
impl Type<Postgres> for Date {
fn type_info() -> PgTypeInfo {
PgTypeInfo::DATE
}
}
impl Type<Postgres> for [Date] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::DATE_ARRAY
}
}
impl Type<Postgres> for Vec<Date> {
fn type_info() -> PgTypeInfo {
<[Date] as Type<Postgres>>::type_info()
}
}
impl Encode<'_, Postgres> for Date {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// DATE is encoded as the days since epoch
let days = (*self - PG_EPOCH).whole_days() as i32;
Encode::<Postgres>::encode(&days, buf)
}
fn size_hint(&self) -> usize {
mem::size_of::<i32>()
}
}
impl<'r> Decode<'r, Postgres> for Date {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// DATE is encoded as the days since epoch
let days: i32 = Decode::<Postgres>::decode(value)?;
PG_EPOCH + Duration::days(days.into())
}
PgValueFormat::Text => Date::parse(value.as_str()?, "%Y-%m-%d")?,
})
}
}

View File

@ -1,27 +1,12 @@
use time::{date, offset, Date, Duration, OffsetDateTime, PrimitiveDateTime, Time};
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::postgres::types::time::PG_EPOCH;
use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
use crate::types::Type;
use std::borrow::Cow;
use std::mem;
#[rustfmt::skip]
const PG_EPOCH: Date = date!(2000-1-1);
impl Type<Postgres> for Time {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIME
}
}
impl Type<Postgres> for Date {
fn type_info() -> PgTypeInfo {
PgTypeInfo::DATE
}
}
use time::{offset, Duration, OffsetDateTime, PrimitiveDateTime};
impl Type<Postgres> for PrimitiveDateTime {
fn type_info() -> PgTypeInfo {
@ -35,18 +20,6 @@ impl Type<Postgres> for OffsetDateTime {
}
}
impl Type<Postgres> for [Time] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIME_ARRAY
}
}
impl Type<Postgres> for [Date] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::DATE_ARRAY
}
}
impl Type<Postgres> for [PrimitiveDateTime] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIMESTAMP_ARRAY
@ -59,18 +32,6 @@ impl Type<Postgres> for [OffsetDateTime] {
}
}
impl Type<Postgres> for Vec<Time> {
fn type_info() -> PgTypeInfo {
<[Time] as Type<Postgres>>::type_info()
}
}
impl Type<Postgres> for Vec<Date> {
fn type_info() -> PgTypeInfo {
<[Date] as Type<Postgres>>::type_info()
}
}
impl Type<Postgres> for Vec<PrimitiveDateTime> {
fn type_info() -> PgTypeInfo {
<[PrimitiveDateTime] as Type<Postgres>>::type_info()
@ -83,73 +44,6 @@ impl Type<Postgres> for Vec<OffsetDateTime> {
}
}
impl Encode<'_, Postgres> for Time {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// TIME is encoded as the microseconds since midnight
let us = (*self - Time::midnight()).whole_microseconds() as i64;
Encode::<Postgres>::encode(&us, buf)
}
fn size_hint(&self) -> usize {
mem::size_of::<u64>()
}
}
impl<'r> Decode<'r, Postgres> for Time {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// TIME is encoded as the microseconds since midnight
let us = Decode::<Postgres>::decode(value)?;
Time::midnight() + Duration::microseconds(us)
}
PgValueFormat::Text => {
// If there are less than 9 digits after the decimal point
// We need to zero-pad
// FIXME: Ask [time] to add a parse % for less-than-fixed-9 nanos
let s = value.as_str()?;
let s = if s.len() < 20 {
Cow::Owned(format!("{:0<19}", s))
} else {
Cow::Borrowed(s)
};
Time::parse(&*s, "%H:%M:%S.%N")?
}
})
}
}
impl Encode<'_, Postgres> for Date {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// DATE is encoded as the days since epoch
let days = (*self - PG_EPOCH).whole_days() as i32;
Encode::<Postgres>::encode(&days, buf)
}
fn size_hint(&self) -> usize {
mem::size_of::<i32>()
}
}
impl<'r> Decode<'r, Postgres> for Date {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// DATE is encoded as the days since epoch
let days: i32 = Decode::<Postgres>::decode(value)?;
PG_EPOCH + Duration::days(days.into())
}
PgValueFormat::Text => Date::parse(value.as_str()?, "%Y-%m-%d")?,
})
}
}
impl Encode<'_, Postgres> for PrimitiveDateTime {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// TIMESTAMP is encoded as the microseconds since the epoch

View File

@ -0,0 +1,6 @@
mod date;
mod datetime;
mod time;
#[rustfmt::skip]
const PG_EPOCH: ::time::Date = ::time::date!(2000-1-1);

View File

@ -0,0 +1,67 @@
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;
use std::borrow::Cow;
use std::mem;
use time::{Duration, Time};
impl Type<Postgres> for Time {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIME
}
}
impl Type<Postgres> for [Time] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIME_ARRAY
}
}
impl Type<Postgres> for Vec<Time> {
fn type_info() -> PgTypeInfo {
<[Time] as Type<Postgres>>::type_info()
}
}
impl Encode<'_, Postgres> for Time {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// TIME is encoded as the microseconds since midnight
let us = (*self - Time::midnight()).whole_microseconds() as i64;
Encode::<Postgres>::encode(&us, buf)
}
fn size_hint(&self) -> usize {
mem::size_of::<u64>()
}
}
impl<'r> Decode<'r, Postgres> for Time {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// TIME is encoded as the microseconds since midnight
let us = Decode::<Postgres>::decode(value)?;
Time::midnight() + Duration::microseconds(us)
}
PgValueFormat::Text => {
// If there are less than 9 digits after the decimal point
// We need to zero-pad
// FIXME: Ask [time] to add a parse % for less-than-fixed-9 nanos
let s = value.as_str()?;
let s = if s.len() < 20 {
Cow::Owned(format!("{:0<19}", s))
} else {
Cow::Borrowed(s)
};
Time::parse(&*s, "%H:%M:%S.%N")?
}
})
}
}

View File

@ -6,10 +6,17 @@
//! * [PostgreSQL](../postgres/types/index.html)
//! * [MySQL](../mysql/types/index.html)
//! * [SQLite](../sqlite/types/index.html)
//! * [MSSQL](../mssql/types/index.html)
//!
//! Any external types that have had [`Type`] implemented for, are re-exported in this module
//! for convenience as downstream users need to use a compatible version of the external crate
//! to take advantage of the implementation.
//!
//! # Nullable
//!
//! To represents nullable SQL types, `Option<T>` is supported where `T` implements `Type`.
//! An `Option<T>` represents a potentially `NULL` value from SQL.
//!
use crate::database::Database;