Skip to content

Add support for time-0.2 types #580

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions postgres-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ with-eui48-0_4 = ["eui48-04"]
with-geo-types-0_4 = ["geo-types-04"]
with-serde_json-1 = ["serde-1", "serde_json-1"]
with-uuid-0_8 = ["uuid-08"]
with-time-0_2 = ["time-02"]

[dependencies]
bytes = "0.5"
Expand All @@ -32,3 +33,4 @@ geo-types-04 = { version = "0.4", package = "geo-types", optional = true }
serde-1 = { version = "1.0", package = "serde", optional = true }
serde_json-1 = { version = "1.0", package = "serde_json", optional = true }
uuid-08 = { version = "0.8", package = "uuid", optional = true }
time-02 = { version = "0.2", package = "time", optional = true }
14 changes: 14 additions & 0 deletions postgres-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,15 @@ mod eui48_04;
mod geo_types_04;
#[cfg(feature = "with-serde_json-1")]
mod serde_json_1;
#[cfg(feature = "with-time-0_2")]
mod time_02;
#[cfg(feature = "with-uuid-0_8")]
mod uuid_08;

// The time::{date, time} macros produce compile errors if the crate package is renamed.
#[cfg(feature = "with-time-0_2")]
extern crate time_02 as time;

#[doc(hidden)]
pub mod private;
mod special;
Expand Down Expand Up @@ -391,6 +397,10 @@ impl WrongType {
/// | `chrono::DateTime<FixedOffset>` | TIMESTAMP WITH TIME ZONE |
/// | `chrono::NaiveDate` | DATE |
/// | `chrono::NaiveTime` | TIME |
/// | `time::PrimitiveDateTime` | TIMESTAMP |
/// | `time::OffsetDateTime` | TIMESTAMP WITH TIME ZONE |
/// | `time::Date` | DATE |
/// | `time::Time` | TIME |
/// | `eui48::MacAddress` | MACADDR |
/// | `geo_types::Point<f64>` | POINT |
/// | `geo_types::Rect<f64>` | BOX |
Expand Down Expand Up @@ -650,6 +660,10 @@ pub enum IsNull {
/// | `chrono::DateTime<FixedOffset>` | TIMESTAMP WITH TIME ZONE |
/// | `chrono::NaiveDate` | DATE |
/// | `chrono::NaiveTime` | TIME |
/// | `time::PrimitiveDateTime` | TIMESTAMP |
/// | `time::OffsetDateTime` | TIMESTAMP WITH TIME ZONE |
/// | `time::Date` | DATE |
/// | `time::Time` | TIME |
/// | `eui48::MacAddress` | MACADDR |
/// | `geo_types::Point<f64>` | POINT |
/// | `geo_types::Rect<f64>` | BOX |
Expand Down
109 changes: 109 additions & 0 deletions postgres-types/src/time_02.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use bytes::BytesMut;
use postgres_protocol::types;
use std::convert::TryFrom;
use std::error::Error;
use time_02::{date, time, Date, Duration, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};

use crate::{FromSql, IsNull, ToSql, Type};

#[rustfmt::skip]
const fn base() -> PrimitiveDateTime {
PrimitiveDateTime::new(date!(2000-01-01), time!(00:00:00))
}

impl<'a> FromSql<'a> for PrimitiveDateTime {
fn from_sql(_: &Type, raw: &[u8]) -> Result<PrimitiveDateTime, Box<dyn Error + Sync + Send>> {
let t = types::timestamp_from_sql(raw)?;
Ok(base() + Duration::microseconds(t))
}

accepts!(TIMESTAMP);
}

impl ToSql for PrimitiveDateTime {
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
let time = match i64::try_from((*self - base()).whole_microseconds()) {
Ok(time) => time,
Err(_) => return Err("value too large to transmit".into()),
};
types::timestamp_to_sql(time, w);
Ok(IsNull::No)
}

accepts!(TIMESTAMP);
to_sql_checked!();
}

impl<'a> FromSql<'a> for OffsetDateTime {
fn from_sql(type_: &Type, raw: &[u8]) -> Result<OffsetDateTime, Box<dyn Error + Sync + Send>> {
let primitive = PrimitiveDateTime::from_sql(type_, raw)?;
Ok(primitive.assume_utc())
}

accepts!(TIMESTAMPTZ);
}

impl ToSql for OffsetDateTime {
fn to_sql(
&self,
type_: &Type,
w: &mut BytesMut,
) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
let utc_datetime = self.to_offset(UtcOffset::UTC);
let date = utc_datetime.date();
let time = utc_datetime.time();
let primitive = PrimitiveDateTime::new(date, time);
primitive.to_sql(type_, w)
}

accepts!(TIMESTAMPTZ);
to_sql_checked!();
}

impl<'a> FromSql<'a> for Date {
fn from_sql(_: &Type, raw: &[u8]) -> Result<Date, Box<dyn Error + Sync + Send>> {
let jd = types::date_from_sql(raw)?;
Ok(base().date() + Duration::days(i64::from(jd)))
}

accepts!(DATE);
}

impl ToSql for Date {
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
let jd = (*self - base().date()).whole_days();
if jd > i64::from(i32::max_value()) || jd < i64::from(i32::min_value()) {
return Err("value too large to transmit".into());
}

types::date_to_sql(jd as i32, w);
Ok(IsNull::No)
}

accepts!(DATE);
to_sql_checked!();
}

impl<'a> FromSql<'a> for Time {
fn from_sql(_: &Type, raw: &[u8]) -> Result<Time, Box<dyn Error + Sync + Send>> {
let usec = types::time_from_sql(raw)?;
Ok(time!(00:00:00) + Duration::microseconds(usec))
}

accepts!(TIME);
}

impl ToSql for Time {
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
let delta = *self - time!(00:00:00);
let time = match i64::try_from(delta.whole_microseconds()) {
Ok(time) => time,
Err(_) => return Err("value too large to transmit".into()),
};
types::time_to_sql(time, w);
Ok(IsNull::No)
}

accepts!(TIME);
to_sql_checked!();
}
1 change: 1 addition & 0 deletions postgres/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ with-eui48-0_4 = ["tokio-postgres/with-eui48-0_4"]
with-geo-types-0_4 = ["tokio-postgres/with-geo-types-0_4"]
with-serde_json-1 = ["tokio-postgres/with-serde_json-1"]
with-uuid-0_8 = ["tokio-postgres/with-uuid-0_8"]
with-time-0_2 = ["tokio-postgres/with-time-0_2"]

[dependencies]
bytes = "0.5"
Expand Down
2 changes: 2 additions & 0 deletions tokio-postgres/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ with-eui48-0_4 = ["postgres-types/with-eui48-0_4"]
with-geo-types-0_4 = ["postgres-types/with-geo-types-0_4"]
with-serde_json-1 = ["postgres-types/with-serde_json-1"]
with-uuid-0_8 = ["postgres-types/with-uuid-0_8"]
with-time-0_2 = ["postgres-types/with-time-0_2"]

[dependencies]
async-trait = "0.1"
Expand Down Expand Up @@ -62,4 +63,5 @@ geo-types-04 = { version = "0.4", package = "geo-types" }
serde-1 = { version = "1.0", package = "serde" }
serde_json-1 = { version = "1.0", package = "serde_json" }
uuid-08 = { version = "0.8", package = "uuid" }
time-02 = { version = "0.2", package = "time" }

2 changes: 2 additions & 0 deletions tokio-postgres/tests/test/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ mod eui48_04;
mod geo_010;
#[cfg(feature = "with-serde_json-1")]
mod serde_json_1;
#[cfg(feature = "with-time-0_2")]
mod time_02;
#[cfg(feature = "with-uuid-0_8")]
mod uuid_08;

Expand Down
150 changes: 150 additions & 0 deletions tokio-postgres/tests/test/types/time_02.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use time_02::{OffsetDateTime, PrimitiveDateTime};
use tokio_postgres::types::{Date, Timestamp};

use crate::types::test_type;

// time 0.2 does not [yet?] support parsing fractional seconds
// https://github.com/time-rs/time/issues/226

#[tokio::test]
async fn test_primitive_date_time_params() {
fn make_check(time: &str) -> (Option<PrimitiveDateTime>, &str) {
(
Some(PrimitiveDateTime::parse(time, "'%Y-%m-%d %H:%M:%S'").unwrap()),
time,
)
}
test_type(
"TIMESTAMP",
&[
make_check("'1970-01-01 00:00:00'"), // .010000000
make_check("'1965-09-25 11:19:33'"), // .100314000
make_check("'2010-02-09 23:11:45'"), // .120200000
(None, "NULL"),
],
)
.await;
}

#[tokio::test]
async fn test_with_special_primitive_date_time_params() {
fn make_check(time: &str) -> (Timestamp<PrimitiveDateTime>, &str) {
(
Timestamp::Value(PrimitiveDateTime::parse(time, "'%Y-%m-%d %H:%M:%S'").unwrap()),
time,
)
}
test_type(
"TIMESTAMP",
&[
make_check("'1970-01-01 00:00:00'"), // .010000000
make_check("'1965-09-25 11:19:33'"), // .100314000
make_check("'2010-02-09 23:11:45'"), // .120200000
(Timestamp::PosInfinity, "'infinity'"),
(Timestamp::NegInfinity, "'-infinity'"),
],
)
.await;
}

#[tokio::test]
async fn test_offset_date_time_params() {
fn make_check(time: &str) -> (Option<OffsetDateTime>, &str) {
(
Some(OffsetDateTime::parse(time, "'%Y-%m-%d %H:%M:%S %z'").unwrap()),
time,
)
}
test_type(
"TIMESTAMP WITH TIME ZONE",
&[
make_check("'1970-01-01 00:00:00 +0000'"), // .010000000
make_check("'1965-09-25 11:19:33 +0000'"), // .100314000
make_check("'2010-02-09 23:11:45 +0000'"), // .120200000
(None, "NULL"),
],
)
.await;
}

#[tokio::test]
async fn test_with_special_offset_date_time_params() {
fn make_check(time: &str) -> (Timestamp<OffsetDateTime>, &str) {
(
Timestamp::Value(OffsetDateTime::parse(time, "'%Y-%m-%d %H:%M:%S %z'").unwrap()),
time,
)
}
test_type(
"TIMESTAMP WITH TIME ZONE",
&[
make_check("'1970-01-01 00:00:00 +0000'"), // .010000000
make_check("'1965-09-25 11:19:33 +0000'"), // .100314000
make_check("'2010-02-09 23:11:45 +0000'"), // .120200000
(Timestamp::PosInfinity, "'infinity'"),
(Timestamp::NegInfinity, "'-infinity'"),
],
)
.await;
}

#[tokio::test]
async fn test_date_params() {
fn make_check(time: &str) -> (Option<time_02::Date>, &str) {
(
Some(time_02::Date::parse(time, "'%Y-%m-%d'").unwrap()),
time,
)
}
test_type(
"DATE",
&[
make_check("'1970-01-01'"),
make_check("'1965-09-25'"),
make_check("'2010-02-09'"),
(None, "NULL"),
],
)
.await;
}

#[tokio::test]
async fn test_with_special_date_params() {
fn make_check(date: &str) -> (Date<time_02::Date>, &str) {
(
Date::Value(time_02::Date::parse(date, "'%Y-%m-%d'").unwrap()),
date,
)
}
test_type(
"DATE",
&[
make_check("'1970-01-01'"),
make_check("'1965-09-25'"),
make_check("'2010-02-09'"),
(Date::PosInfinity, "'infinity'"),
(Date::NegInfinity, "'-infinity'"),
],
)
.await;
}

#[tokio::test]
async fn test_time_params() {
fn make_check(time: &str) -> (Option<time_02::Time>, &str) {
(
Some(time_02::Time::parse(time, "'%H:%M:%S'").unwrap()),
time,
)
}
test_type(
"TIME",
&[
make_check("'00:00:00'"), // .010000000
make_check("'11:19:33'"), // .100314000
make_check("'23:11:45'"), // .120200000
(None, "NULL"),
],
)
.await;
}