Skip to content

Commit 490c485

Browse files
authored
Merge pull request #1217 from NOBLES5E/master
feat: support jiff v0.2
2 parents 4c2ded1 + 7b8d796 commit 490c485

File tree

7 files changed

+323
-0
lines changed

7 files changed

+323
-0
lines changed

postgres-types/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ with-eui48-1 = ["eui48-1"]
2323
with-geo-types-0_6 = ["geo-types-06"]
2424
with-geo-types-0_7 = ["geo-types-0_7"]
2525
with-jiff-0_1 = ["jiff-01"]
26+
with-jiff-0_2 = ["jiff-02"]
2627
with-serde_json-1 = ["serde-1", "serde_json-1"]
2728
with-smol_str-01 = ["smol_str-01"]
2829
with-uuid-0_8 = ["uuid-08"]
@@ -50,6 +51,7 @@ eui48-1 = { version = "1.0", package = "eui48", optional = true, default-feature
5051
geo-types-06 = { version = "0.6", package = "geo-types", optional = true }
5152
geo-types-0_7 = { version = "0.7", package = "geo-types", optional = true }
5253
jiff-01 = { version = "0.1", package = "jiff", optional = true }
54+
jiff-02 = { version = "0.2", package = "jiff", optional = true }
5355
serde-1 = { version = "1.0", package = "serde", optional = true }
5456
serde_json-1 = { version = "1.0", package = "serde_json", optional = true }
5557
uuid-08 = { version = "0.8", package = "uuid", optional = true }

postgres-types/src/jiff_02.rs

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use bytes::BytesMut;
2+
use jiff_02::{
3+
civil::{Date, DateTime, Time},
4+
Span, SpanRound, Timestamp, Unit,
5+
};
6+
use postgres_protocol::types;
7+
use std::error::Error;
8+
9+
use crate::{FromSql, IsNull, ToSql, Type};
10+
11+
const fn base() -> DateTime {
12+
DateTime::constant(2000, 1, 1, 0, 0, 0, 0)
13+
}
14+
15+
/// The number of seconds from the Unix epoch to 2000-01-01 00:00:00 UTC.
16+
const PG_EPOCH: i64 = 946684800;
17+
18+
fn base_ts() -> Timestamp {
19+
Timestamp::new(PG_EPOCH, 0).unwrap()
20+
}
21+
22+
fn round_us<'a>() -> SpanRound<'a> {
23+
SpanRound::new().largest(Unit::Microsecond)
24+
}
25+
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+
40+
impl<'a> FromSql<'a> for DateTime {
41+
fn from_sql(_: &Type, raw: &[u8]) -> Result<DateTime, Box<dyn Error + Sync + Send>> {
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)
47+
}
48+
49+
accepts!(TIMESTAMP);
50+
}
51+
52+
impl ToSql for DateTime {
53+
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
54+
let v = dbg!(dbg!(self.since(base())).and_then(|s| s.round(round_us().relative(base()))))
55+
.map_err(transmit_err)?
56+
.get_microseconds();
57+
types::timestamp_to_sql(v, w);
58+
Ok(IsNull::No)
59+
}
60+
61+
accepts!(TIMESTAMP);
62+
to_sql_checked!();
63+
}
64+
65+
impl<'a> FromSql<'a> for Timestamp {
66+
fn from_sql(_: &Type, raw: &[u8]) -> Result<Timestamp, Box<dyn Error + Sync + Send>> {
67+
let v = types::timestamp_from_sql(raw)?;
68+
Span::new()
69+
.try_microseconds(v)
70+
.and_then(|s| base_ts().checked_add(s))
71+
.map_err(decode_err)
72+
}
73+
74+
accepts!(TIMESTAMPTZ);
75+
}
76+
77+
impl ToSql for Timestamp {
78+
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
79+
let v = self
80+
.since(base_ts())
81+
.and_then(|s| s.round(round_us()))
82+
.map_err(transmit_err)?
83+
.get_microseconds();
84+
types::timestamp_to_sql(v, w);
85+
Ok(IsNull::No)
86+
}
87+
88+
accepts!(TIMESTAMPTZ);
89+
to_sql_checked!();
90+
}
91+
92+
impl<'a> FromSql<'a> for Date {
93+
fn from_sql(_: &Type, raw: &[u8]) -> Result<Date, Box<dyn Error + Sync + Send>> {
94+
let v = types::date_from_sql(raw)?;
95+
Span::new()
96+
.try_days(v)
97+
.and_then(|s| base().date().checked_add(s))
98+
.map_err(decode_err)
99+
}
100+
accepts!(DATE);
101+
}
102+
103+
impl ToSql for Date {
104+
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
105+
let v = self.since(base().date()).map_err(transmit_err)?.get_days();
106+
types::date_to_sql(v, w);
107+
Ok(IsNull::No)
108+
}
109+
110+
accepts!(DATE);
111+
to_sql_checked!();
112+
}
113+
114+
impl<'a> FromSql<'a> for Time {
115+
fn from_sql(_: &Type, raw: &[u8]) -> Result<Time, Box<dyn Error + Sync + Send>> {
116+
let v = types::time_from_sql(raw)?;
117+
Span::new()
118+
.try_microseconds(v)
119+
.and_then(|s| Time::midnight().checked_add(s))
120+
.map_err(decode_err)
121+
}
122+
123+
accepts!(TIME);
124+
}
125+
126+
impl ToSql for Time {
127+
fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
128+
let v = self
129+
.since(Time::midnight())
130+
.and_then(|s| s.round(round_us()))
131+
.map_err(transmit_err)?
132+
.get_microseconds();
133+
types::time_to_sql(v, w);
134+
Ok(IsNull::No)
135+
}
136+
137+
accepts!(TIME);
138+
to_sql_checked!();
139+
}

postgres-types/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ mod geo_types_06;
280280
mod geo_types_07;
281281
#[cfg(feature = "with-jiff-0_1")]
282282
mod jiff_01;
283+
#[cfg(feature = "with-jiff-0_2")]
284+
mod jiff_02;
283285
#[cfg(feature = "with-serde_json-1")]
284286
mod serde_json_1;
285287
#[cfg(feature = "with-smol_str-01")]

postgres/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ with-eui48-1 = ["tokio-postgres/with-eui48-1"]
3131
with-geo-types-0_6 = ["tokio-postgres/with-geo-types-0_6"]
3232
with-geo-types-0_7 = ["tokio-postgres/with-geo-types-0_7"]
3333
with-jiff-0_1 = ["tokio-postgres/with-jiff-0_1"]
34+
with-jiff-0_2 = ["tokio-postgres/with-jiff-0_2"]
3435
with-serde_json-1 = ["tokio-postgres/with-serde_json-1"]
3536
with-smol_str-01 = ["tokio-postgres/with-smol_str-01"]
3637
with-uuid-0_8 = ["tokio-postgres/with-uuid-0_8"]

tokio-postgres/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ with-eui48-1 = ["postgres-types/with-eui48-1"]
3737
with-geo-types-0_6 = ["postgres-types/with-geo-types-0_6"]
3838
with-geo-types-0_7 = ["postgres-types/with-geo-types-0_7"]
3939
with-jiff-0_1 = ["postgres-types/with-jiff-0_1"]
40+
with-jiff-0_2 = ["postgres-types/with-jiff-0_2"]
4041
with-serde_json-1 = ["postgres-types/with-serde_json-1"]
4142
with-smol_str-01 = ["postgres-types/with-smol_str-01"]
4243
with-uuid-0_8 = ["postgres-types/with-uuid-0_8"]
@@ -85,6 +86,7 @@ eui48-1 = { version = "1.0", package = "eui48", default-features = false }
8586
geo-types-06 = { version = "0.6", package = "geo-types" }
8687
geo-types-07 = { version = "0.7", package = "geo-types" }
8788
jiff-01 = { version = "0.1", package = "jiff" }
89+
jiff-02 = { version = "0.2", package = "jiff" }
8890
serde-1 = { version = "1.0", package = "serde" }
8991
serde_json-1 = { version = "1.0", package = "serde_json" }
9092
smol_str-01 = { version = "0.1", package = "smol_str" }
+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use jiff_02::{
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
@@ -25,6 +25,8 @@ mod geo_types_06;
2525
mod geo_types_07;
2626
#[cfg(feature = "with-jiff-0_1")]
2727
mod jiff_01;
28+
#[cfg(feature = "with-jiff-0_2")]
29+
mod jiff_02;
2830
#[cfg(feature = "with-serde_json-1")]
2931
mod serde_json_1;
3032
#[cfg(feature = "with-smol_str-01")]

0 commit comments

Comments
 (0)