Skip to content

Commit

Permalink
Add jiff tests and overflow checks
Browse files Browse the repository at this point in the history
This adds tests in the same fashion as the existing ones for `chrono`
and `time`.

Overflow is now handled using fallible operations.
For example, `Span:microseconds` is replaced with `Span::try_microseconds`.
Postgres infinity values are workiing as expected.

All tests are passing.
  • Loading branch information
allan2 committed Aug 15, 2024
1 parent c96342d commit afef88e
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 17 deletions.
71 changes: 54 additions & 17 deletions postgres-types/src/jiff_01.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,40 @@ fn round_us<'a>() -> SpanRound<'a> {
SpanRound::new().largest(Unit::Microsecond)
}

fn decode_err<E>(_e: E) -> Box<dyn Error + Sync + Send>
where
E: Error,
{
"value too large to decode".into()
}

fn transmit_err<E>(_e: E) -> Box<dyn Error + Sync + Send>
where
E: Error,
{
"value too large to transmit".into()
}

impl<'a> FromSql<'a> for DateTime {
fn from_sql(_: &Type, raw: &[u8]) -> Result<DateTime, Box<dyn Error + Sync + Send>> {
let t = types::timestamp_from_sql(raw)?;
Ok(base().checked_add(Span::new().microseconds(t))?)
let v = types::timestamp_from_sql(raw)?;
Span::new()
.try_microseconds(v)
.and_then(|s| base().checked_add(s))
.map_err(decode_err)
}

accepts!(TIMESTAMP);
}

impl ToSql for DateTime {
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
let span = self.since(base())?.round(round_us())?;
types::timestamp_to_sql(span.get_microseconds(), w);
let v = self
.since(base())
.and_then(|s| s.round(round_us()))
.map_err(transmit_err)?
.get_microseconds();
types::timestamp_to_sql(v, w);
Ok(IsNull::No)
}

Expand All @@ -45,17 +66,24 @@ impl ToSql for DateTime {

impl<'a> FromSql<'a> for Timestamp {
fn from_sql(_: &Type, raw: &[u8]) -> Result<Timestamp, Box<dyn Error + Sync + Send>> {
let t = types::timestamp_from_sql(raw)?;
Ok(base_ts().checked_add(Span::new().microseconds(t))?)
let v = types::timestamp_from_sql(raw)?;
Span::new()
.try_microseconds(v)
.and_then(|s| base_ts().checked_add(s))
.map_err(decode_err)
}

accepts!(TIMESTAMPTZ);
}

impl ToSql for Timestamp {
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
let span = self.since(base_ts())?.round(round_us())?;
types::timestamp_to_sql(span.get_microseconds(), w);
let v = self
.since(base_ts())
.and_then(|s| s.round(round_us()))
.map_err(transmit_err)?
.get_microseconds();
types::timestamp_to_sql(v, w);
Ok(IsNull::No)
}

Expand All @@ -65,17 +93,19 @@ impl ToSql for Timestamp {

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().checked_add(Span::new().days(jd))?)
let v = types::date_from_sql(raw)?;
Span::new()
.try_days(v)
.and_then(|s| base().date().checked_add(s))
.map_err(decode_err)
}

accepts!(DATE);
}

impl ToSql for Date {
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
let jd = self.since(base().date())?.get_days();
types::date_to_sql(jd, w);
let v = self.since(base().date()).map_err(transmit_err)?.get_days();
types::date_to_sql(v, w);
Ok(IsNull::No)
}

Expand All @@ -85,17 +115,24 @@ impl ToSql for Date {

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::midnight() + Span::new().microseconds(usec))
let v = types::time_from_sql(raw)?;
Span::new()
.try_microseconds(v)
.and_then(|s| Time::midnight().checked_add(s))
.map_err(decode_err)
}

accepts!(TIME);
}

impl ToSql for Time {
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
let span = self.since(Time::midnight())?.round(round_us())?;
types::time_to_sql(span.get_microseconds(), w);
let v = self
.since(Time::midnight())
.and_then(|s| s.round(round_us()))
.map_err(transmit_err)?
.get_microseconds();
types::time_to_sql(v, w);
Ok(IsNull::No)
}

Expand Down
175 changes: 175 additions & 0 deletions tokio-postgres/tests/test/types/jiff_01.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use jiff_01::{
civil::{Date as JiffDate, DateTime, Time},
Timestamp as JiffTimestamp,
};
use std::fmt;
use tokio_postgres::{
types::{Date, FromSqlOwned, Timestamp},
Client,
};

use crate::connect;
use crate::types::test_type;

#[tokio::test]
async fn test_datetime_params() {
fn make_check(s: &str) -> (Option<DateTime>, &str) {
(Some(s.trim_matches('\'').parse().unwrap()), s)
}
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_datetime_params() {
fn make_check(s: &str) -> (Timestamp<DateTime>, &str) {
(Timestamp::Value(s.trim_matches('\'').parse().unwrap()), s)
}
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_timestamp_params() {
fn make_check(s: &str) -> (Option<JiffTimestamp>, &str) {
(Some(s.trim_matches('\'').parse().unwrap()), s)
}
test_type(
"TIMESTAMP WITH TIME ZONE",
&[
make_check("'1970-01-01 00:00:00.010000000Z'"),
make_check("'1965-09-25 11:19:33.100314000Z'"),
make_check("'2010-02-09 23:11:45.120200000Z'"),
(None, "NULL"),
],
)
.await;
}

#[tokio::test]
async fn test_with_special_timestamp_params() {
fn make_check(s: &str) -> (Timestamp<JiffTimestamp>, &str) {
(Timestamp::Value(s.trim_matches('\'').parse().unwrap()), s)
}
test_type(
"TIMESTAMP WITH TIME ZONE",
&[
make_check("'1970-01-01 00:00:00.010000000Z'"),
make_check("'1965-09-25 11:19:33.100314000Z'"),
make_check("'2010-02-09 23:11:45.120200000Z'"),
(Timestamp::PosInfinity, "'infinity'"),
(Timestamp::NegInfinity, "'-infinity'"),
],
)
.await;
}

#[tokio::test]
async fn test_date_params() {
fn make_check(s: &str) -> (Option<JiffDate>, &str) {
(Some(s.trim_matches('\'').parse().unwrap()), s)
}
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(s: &str) -> (Date<JiffDate>, &str) {
(Date::Value(s.trim_matches('\'').parse().unwrap()), s)
}
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(s: &str) -> (Option<Time>, &str) {
(Some(s.trim_matches('\'').parse().unwrap()), s)
}
test_type(
"TIME",
&[
make_check("'00:00:00.010000000'"),
make_check("'11:19:33.100314000'"),
make_check("'23:11:45.120200000'"),
(None, "NULL"),
],
)
.await;
}

#[tokio::test]
async fn test_special_params_without_wrapper() {
async fn assert_overflows<T>(client: &mut Client, val: &str, sql_type: &str)
where
T: FromSqlOwned + fmt::Debug,
{
let err = client
.query_one(&*format!("SELECT {}::{}", val, sql_type), &[])
.await
.unwrap()
.try_get::<_, T>(0)
.unwrap_err();

assert_eq!(
err.to_string(),
"error deserializing column 0: value too large to decode"
);

let err = client
.query_one(&*format!("SELECT {}::{}", val, sql_type), &[])
.await
.unwrap()
.try_get::<_, T>(0)
.unwrap_err();

assert_eq!(
err.to_string(),
"error deserializing column 0: value too large to decode"
);
}

let mut client = connect("user=postgres").await;

assert_overflows::<DateTime>(&mut client, "'-infinity'", "timestamp").await;
assert_overflows::<DateTime>(&mut client, "'infinity'", "timestamp").await;
assert_overflows::<JiffTimestamp>(&mut client, "'-infinity'", "timestamptz").await;
assert_overflows::<JiffTimestamp>(&mut client, "'infinity'", "timestamptz").await;
assert_overflows::<JiffDate>(&mut client, "'-infinity'", "date").await;
assert_overflows::<JiffDate>(&mut client, "'infinity'", "date").await;
}
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 @@ -23,6 +23,8 @@ mod eui48_1;
mod geo_types_06;
#[cfg(feature = "with-geo-types-0_7")]
mod geo_types_07;
#[cfg(feature = "with-jiff-0_1")]
mod jiff_01;
#[cfg(feature = "with-serde_json-1")]
mod serde_json_1;
#[cfg(feature = "with-smol_str-01")]
Expand Down

0 comments on commit afef88e

Please sign in to comment.