Skip to content

Commit afef88e

Browse files
committed
Add jiff tests and overflow checks
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.
1 parent c96342d commit afef88e

File tree

3 files changed

+231
-17
lines changed

3 files changed

+231
-17
lines changed

postgres-types/src/jiff_01.rs

+54-17
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,40 @@ fn round_us<'a>() -> SpanRound<'a> {
2323
SpanRound::new().largest(Unit::Microsecond)
2424
}
2525

26+
fn decode_err<E>(_e: E) -> Box<dyn Error + Sync + Send>
27+
where
28+
E: Error,
29+
{
30+
"value too large to decode".into()
31+
}
32+
33+
fn transmit_err<E>(_e: E) -> Box<dyn Error + Sync + Send>
34+
where
35+
E: Error,
36+
{
37+
"value too large to transmit".into()
38+
}
39+
2640
impl<'a> FromSql<'a> for DateTime {
2741
fn from_sql(_: &Type, raw: &[u8]) -> Result<DateTime, Box<dyn Error + Sync + Send>> {
28-
let t = types::timestamp_from_sql(raw)?;
29-
Ok(base().checked_add(Span::new().microseconds(t))?)
42+
let v = types::timestamp_from_sql(raw)?;
43+
Span::new()
44+
.try_microseconds(v)
45+
.and_then(|s| base().checked_add(s))
46+
.map_err(decode_err)
3047
}
3148

3249
accepts!(TIMESTAMP);
3350
}
3451

3552
impl ToSql for DateTime {
3653
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
37-
let span = self.since(base())?.round(round_us())?;
38-
types::timestamp_to_sql(span.get_microseconds(), w);
54+
let v = self
55+
.since(base())
56+
.and_then(|s| s.round(round_us()))
57+
.map_err(transmit_err)?
58+
.get_microseconds();
59+
types::timestamp_to_sql(v, w);
3960
Ok(IsNull::No)
4061
}
4162

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

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

5276
accepts!(TIMESTAMPTZ);
5377
}
5478

5579
impl ToSql for Timestamp {
5680
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
57-
let span = self.since(base_ts())?.round(round_us())?;
58-
types::timestamp_to_sql(span.get_microseconds(), w);
81+
let v = self
82+
.since(base_ts())
83+
.and_then(|s| s.round(round_us()))
84+
.map_err(transmit_err)?
85+
.get_microseconds();
86+
types::timestamp_to_sql(v, w);
5987
Ok(IsNull::No)
6088
}
6189

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

6694
impl<'a> FromSql<'a> for Date {
6795
fn from_sql(_: &Type, raw: &[u8]) -> Result<Date, Box<dyn Error + Sync + Send>> {
68-
let jd = types::date_from_sql(raw)?;
69-
Ok(base().date().checked_add(Span::new().days(jd))?)
96+
let v = types::date_from_sql(raw)?;
97+
Span::new()
98+
.try_days(v)
99+
.and_then(|s| base().date().checked_add(s))
100+
.map_err(decode_err)
70101
}
71-
72102
accepts!(DATE);
73103
}
74104

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

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

86116
impl<'a> FromSql<'a> for Time {
87117
fn from_sql(_: &Type, raw: &[u8]) -> Result<Time, Box<dyn Error + Sync + Send>> {
88-
let usec = types::time_from_sql(raw)?;
89-
Ok(Time::midnight() + Span::new().microseconds(usec))
118+
let v = types::time_from_sql(raw)?;
119+
Span::new()
120+
.try_microseconds(v)
121+
.and_then(|s| Time::midnight().checked_add(s))
122+
.map_err(decode_err)
90123
}
91124

92125
accepts!(TIME);
93126
}
94127

95128
impl ToSql for Time {
96129
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
97-
let span = self.since(Time::midnight())?.round(round_us())?;
98-
types::time_to_sql(span.get_microseconds(), w);
130+
let v = self
131+
.since(Time::midnight())
132+
.and_then(|s| s.round(round_us()))
133+
.map_err(transmit_err)?
134+
.get_microseconds();
135+
types::time_to_sql(v, w);
99136
Ok(IsNull::No)
100137
}
101138

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use jiff_01::{
2+
civil::{Date as JiffDate, DateTime, Time},
3+
Timestamp as JiffTimestamp,
4+
};
5+
use std::fmt;
6+
use tokio_postgres::{
7+
types::{Date, FromSqlOwned, Timestamp},
8+
Client,
9+
};
10+
11+
use crate::connect;
12+
use crate::types::test_type;
13+
14+
#[tokio::test]
15+
async fn test_datetime_params() {
16+
fn make_check(s: &str) -> (Option<DateTime>, &str) {
17+
(Some(s.trim_matches('\'').parse().unwrap()), s)
18+
}
19+
test_type(
20+
"TIMESTAMP",
21+
&[
22+
make_check("'1970-01-01 00:00:00.010000000'"),
23+
make_check("'1965-09-25 11:19:33.100314000'"),
24+
make_check("'2010-02-09 23:11:45.120200000'"),
25+
(None, "NULL"),
26+
],
27+
)
28+
.await;
29+
}
30+
31+
#[tokio::test]
32+
async fn test_with_special_datetime_params() {
33+
fn make_check(s: &str) -> (Timestamp<DateTime>, &str) {
34+
(Timestamp::Value(s.trim_matches('\'').parse().unwrap()), s)
35+
}
36+
test_type(
37+
"TIMESTAMP",
38+
&[
39+
make_check("'1970-01-01 00:00:00.010000000'"),
40+
make_check("'1965-09-25 11:19:33.100314000'"),
41+
make_check("'2010-02-09 23:11:45.120200000'"),
42+
(Timestamp::PosInfinity, "'infinity'"),
43+
(Timestamp::NegInfinity, "'-infinity'"),
44+
],
45+
)
46+
.await;
47+
}
48+
49+
#[tokio::test]
50+
async fn test_timestamp_params() {
51+
fn make_check(s: &str) -> (Option<JiffTimestamp>, &str) {
52+
(Some(s.trim_matches('\'').parse().unwrap()), s)
53+
}
54+
test_type(
55+
"TIMESTAMP WITH TIME ZONE",
56+
&[
57+
make_check("'1970-01-01 00:00:00.010000000Z'"),
58+
make_check("'1965-09-25 11:19:33.100314000Z'"),
59+
make_check("'2010-02-09 23:11:45.120200000Z'"),
60+
(None, "NULL"),
61+
],
62+
)
63+
.await;
64+
}
65+
66+
#[tokio::test]
67+
async fn test_with_special_timestamp_params() {
68+
fn make_check(s: &str) -> (Timestamp<JiffTimestamp>, &str) {
69+
(Timestamp::Value(s.trim_matches('\'').parse().unwrap()), s)
70+
}
71+
test_type(
72+
"TIMESTAMP WITH TIME ZONE",
73+
&[
74+
make_check("'1970-01-01 00:00:00.010000000Z'"),
75+
make_check("'1965-09-25 11:19:33.100314000Z'"),
76+
make_check("'2010-02-09 23:11:45.120200000Z'"),
77+
(Timestamp::PosInfinity, "'infinity'"),
78+
(Timestamp::NegInfinity, "'-infinity'"),
79+
],
80+
)
81+
.await;
82+
}
83+
84+
#[tokio::test]
85+
async fn test_date_params() {
86+
fn make_check(s: &str) -> (Option<JiffDate>, &str) {
87+
(Some(s.trim_matches('\'').parse().unwrap()), s)
88+
}
89+
test_type(
90+
"DATE",
91+
&[
92+
make_check("'1970-01-01'"),
93+
make_check("'1965-09-25'"),
94+
make_check("'2010-02-09'"),
95+
(None, "NULL"),
96+
],
97+
)
98+
.await;
99+
}
100+
101+
#[tokio::test]
102+
async fn test_with_special_date_params() {
103+
fn make_check(s: &str) -> (Date<JiffDate>, &str) {
104+
(Date::Value(s.trim_matches('\'').parse().unwrap()), s)
105+
}
106+
test_type(
107+
"DATE",
108+
&[
109+
make_check("'1970-01-01'"),
110+
make_check("'1965-09-25'"),
111+
make_check("'2010-02-09'"),
112+
(Date::PosInfinity, "'infinity'"),
113+
(Date::NegInfinity, "'-infinity'"),
114+
],
115+
)
116+
.await;
117+
}
118+
119+
#[tokio::test]
120+
async fn test_time_params() {
121+
fn make_check(s: &str) -> (Option<Time>, &str) {
122+
(Some(s.trim_matches('\'').parse().unwrap()), s)
123+
}
124+
test_type(
125+
"TIME",
126+
&[
127+
make_check("'00:00:00.010000000'"),
128+
make_check("'11:19:33.100314000'"),
129+
make_check("'23:11:45.120200000'"),
130+
(None, "NULL"),
131+
],
132+
)
133+
.await;
134+
}
135+
136+
#[tokio::test]
137+
async fn test_special_params_without_wrapper() {
138+
async fn assert_overflows<T>(client: &mut Client, val: &str, sql_type: &str)
139+
where
140+
T: FromSqlOwned + fmt::Debug,
141+
{
142+
let err = client
143+
.query_one(&*format!("SELECT {}::{}", val, sql_type), &[])
144+
.await
145+
.unwrap()
146+
.try_get::<_, T>(0)
147+
.unwrap_err();
148+
149+
assert_eq!(
150+
err.to_string(),
151+
"error deserializing column 0: value too large to decode"
152+
);
153+
154+
let err = client
155+
.query_one(&*format!("SELECT {}::{}", val, sql_type), &[])
156+
.await
157+
.unwrap()
158+
.try_get::<_, T>(0)
159+
.unwrap_err();
160+
161+
assert_eq!(
162+
err.to_string(),
163+
"error deserializing column 0: value too large to decode"
164+
);
165+
}
166+
167+
let mut client = connect("user=postgres").await;
168+
169+
assert_overflows::<DateTime>(&mut client, "'-infinity'", "timestamp").await;
170+
assert_overflows::<DateTime>(&mut client, "'infinity'", "timestamp").await;
171+
assert_overflows::<JiffTimestamp>(&mut client, "'-infinity'", "timestamptz").await;
172+
assert_overflows::<JiffTimestamp>(&mut client, "'infinity'", "timestamptz").await;
173+
assert_overflows::<JiffDate>(&mut client, "'-infinity'", "date").await;
174+
assert_overflows::<JiffDate>(&mut client, "'infinity'", "date").await;
175+
}

tokio-postgres/tests/test/types/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ mod eui48_1;
2323
mod geo_types_06;
2424
#[cfg(feature = "with-geo-types-0_7")]
2525
mod geo_types_07;
26+
#[cfg(feature = "with-jiff-0_1")]
27+
mod jiff_01;
2628
#[cfg(feature = "with-serde_json-1")]
2729
mod serde_json_1;
2830
#[cfg(feature = "with-smol_str-01")]

0 commit comments

Comments
 (0)