sqlx_postgres/types/
interval.rs

1use std::mem;
2
3use byteorder::{NetworkEndian, ReadBytesExt};
4
5use crate::decode::Decode;
6use crate::encode::{Encode, IsNull};
7use crate::error::BoxDynError;
8use crate::types::Type;
9use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
10
11// `PgInterval` is available for direct access to the INTERVAL type
12
13#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Default)]
14pub struct PgInterval {
15    pub months: i32,
16    pub days: i32,
17    pub microseconds: i64,
18}
19
20impl Type<Postgres> for PgInterval {
21    fn type_info() -> PgTypeInfo {
22        PgTypeInfo::INTERVAL
23    }
24}
25
26impl PgHasArrayType for PgInterval {
27    fn array_type_info() -> PgTypeInfo {
28        PgTypeInfo::INTERVAL_ARRAY
29    }
30}
31
32impl<'de> Decode<'de, Postgres> for PgInterval {
33    fn decode(value: PgValueRef<'de>) -> Result<Self, BoxDynError> {
34        match value.format() {
35            PgValueFormat::Binary => {
36                let mut buf = value.as_bytes()?;
37                let microseconds = buf.read_i64::<NetworkEndian>()?;
38                let days = buf.read_i32::<NetworkEndian>()?;
39                let months = buf.read_i32::<NetworkEndian>()?;
40
41                Ok(PgInterval {
42                    months,
43                    days,
44                    microseconds,
45                })
46            }
47
48            // TODO: Implement parsing of text mode
49            PgValueFormat::Text => {
50                Err("not implemented: decode `INTERVAL` in text mode (unprepared queries)".into())
51            }
52        }
53    }
54}
55
56impl Encode<'_, Postgres> for PgInterval {
57    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
58        buf.extend(&self.microseconds.to_be_bytes());
59        buf.extend(&self.days.to_be_bytes());
60        buf.extend(&self.months.to_be_bytes());
61
62        Ok(IsNull::No)
63    }
64
65    fn size_hint(&self) -> usize {
66        2 * mem::size_of::<i64>()
67    }
68}
69
70// We then implement Encode + Type for std Duration, chrono Duration, and time Duration
71// This is to enable ease-of-use for encoding when its simple
72
73impl Type<Postgres> for std::time::Duration {
74    fn type_info() -> PgTypeInfo {
75        PgTypeInfo::INTERVAL
76    }
77}
78
79impl PgHasArrayType for std::time::Duration {
80    fn array_type_info() -> PgTypeInfo {
81        PgTypeInfo::INTERVAL_ARRAY
82    }
83}
84
85impl Encode<'_, Postgres> for std::time::Duration {
86    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
87        PgInterval::try_from(*self)?.encode_by_ref(buf)
88    }
89
90    fn size_hint(&self) -> usize {
91        2 * mem::size_of::<i64>()
92    }
93}
94
95impl TryFrom<std::time::Duration> for PgInterval {
96    type Error = BoxDynError;
97
98    /// Convert a `std::time::Duration` to a `PgInterval`
99    ///
100    /// This returns an error if there is a loss of precision using nanoseconds or if there is a
101    /// microsecond overflow.
102    fn try_from(value: std::time::Duration) -> Result<Self, BoxDynError> {
103        if value.as_nanos() % 1000 != 0 {
104            return Err("PostgreSQL `INTERVAL` does not support nanoseconds precision".into());
105        }
106
107        Ok(Self {
108            months: 0,
109            days: 0,
110            microseconds: value.as_micros().try_into()?,
111        })
112    }
113}
114
115#[cfg(feature = "chrono")]
116impl Type<Postgres> for chrono::Duration {
117    fn type_info() -> PgTypeInfo {
118        PgTypeInfo::INTERVAL
119    }
120}
121
122#[cfg(feature = "chrono")]
123impl PgHasArrayType for chrono::Duration {
124    fn array_type_info() -> PgTypeInfo {
125        PgTypeInfo::INTERVAL_ARRAY
126    }
127}
128
129#[cfg(feature = "chrono")]
130impl Encode<'_, Postgres> for chrono::Duration {
131    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
132        let pg_interval = PgInterval::try_from(*self)?;
133        pg_interval.encode_by_ref(buf)
134    }
135
136    fn size_hint(&self) -> usize {
137        2 * mem::size_of::<i64>()
138    }
139}
140
141#[cfg(feature = "chrono")]
142impl TryFrom<chrono::Duration> for PgInterval {
143    type Error = BoxDynError;
144
145    /// Convert a `chrono::Duration` to a `PgInterval`.
146    ///
147    /// This returns an error if there is a loss of precision using nanoseconds or if there is a
148    /// nanosecond overflow.
149    fn try_from(value: chrono::Duration) -> Result<Self, BoxDynError> {
150        value
151            .num_nanoseconds()
152            .map_or::<Result<_, Self::Error>, _>(
153                Err("Overflow has occurred for PostgreSQL `INTERVAL`".into()),
154                |nanoseconds| {
155                    if nanoseconds % 1000 != 0 {
156                        return Err(
157                            "PostgreSQL `INTERVAL` does not support nanoseconds precision".into(),
158                        );
159                    }
160                    Ok(())
161                },
162            )?;
163
164        value.num_microseconds().map_or(
165            Err("Overflow has occurred for PostgreSQL `INTERVAL`".into()),
166            |microseconds| {
167                Ok(Self {
168                    months: 0,
169                    days: 0,
170                    microseconds,
171                })
172            },
173        )
174    }
175}
176
177#[cfg(feature = "time")]
178impl Type<Postgres> for time::Duration {
179    fn type_info() -> PgTypeInfo {
180        PgTypeInfo::INTERVAL
181    }
182}
183
184#[cfg(feature = "time")]
185impl PgHasArrayType for time::Duration {
186    fn array_type_info() -> PgTypeInfo {
187        PgTypeInfo::INTERVAL_ARRAY
188    }
189}
190
191#[cfg(feature = "time")]
192impl Encode<'_, Postgres> for time::Duration {
193    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
194        let pg_interval = PgInterval::try_from(*self)?;
195        pg_interval.encode_by_ref(buf)
196    }
197
198    fn size_hint(&self) -> usize {
199        2 * mem::size_of::<i64>()
200    }
201}
202
203#[cfg(feature = "time")]
204impl TryFrom<time::Duration> for PgInterval {
205    type Error = BoxDynError;
206
207    /// Convert a `time::Duration` to a `PgInterval`.
208    ///
209    /// This returns an error if there is a loss of precision using nanoseconds or if there is a
210    /// microsecond overflow.
211    fn try_from(value: time::Duration) -> Result<Self, BoxDynError> {
212        if value.whole_nanoseconds() % 1000 != 0 {
213            return Err("PostgreSQL `INTERVAL` does not support nanoseconds precision".into());
214        }
215
216        Ok(Self {
217            months: 0,
218            days: 0,
219            microseconds: value.whole_microseconds().try_into()?,
220        })
221    }
222}
223
224#[test]
225fn test_encode_interval() {
226    let mut buf = PgArgumentBuffer::default();
227
228    let interval = PgInterval {
229        months: 0,
230        days: 0,
231        microseconds: 0,
232    };
233    assert!(matches!(
234        Encode::<Postgres>::encode(&interval, &mut buf),
235        Ok(IsNull::No)
236    ));
237    assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
238    buf.clear();
239
240    let interval = PgInterval {
241        months: 0,
242        days: 0,
243        microseconds: 1_000,
244    };
245    assert!(matches!(
246        Encode::<Postgres>::encode(&interval, &mut buf),
247        Ok(IsNull::No)
248    ));
249    assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 3, 232, 0, 0, 0, 0, 0, 0, 0, 0]);
250    buf.clear();
251
252    let interval = PgInterval {
253        months: 0,
254        days: 0,
255        microseconds: 1_000_000,
256    };
257    assert!(matches!(
258        Encode::<Postgres>::encode(&interval, &mut buf),
259        Ok(IsNull::No)
260    ));
261    assert_eq!(&**buf, [0, 0, 0, 0, 0, 15, 66, 64, 0, 0, 0, 0, 0, 0, 0, 0]);
262    buf.clear();
263
264    let interval = PgInterval {
265        months: 0,
266        days: 0,
267        microseconds: 3_600_000_000,
268    };
269    assert!(matches!(
270        Encode::<Postgres>::encode(&interval, &mut buf),
271        Ok(IsNull::No)
272    ));
273    assert_eq!(
274        &**buf,
275        [0, 0, 0, 0, 214, 147, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0]
276    );
277    buf.clear();
278
279    let interval = PgInterval {
280        months: 0,
281        days: 1,
282        microseconds: 0,
283    };
284    assert!(matches!(
285        Encode::<Postgres>::encode(&interval, &mut buf),
286        Ok(IsNull::No)
287    ));
288    assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]);
289    buf.clear();
290
291    let interval = PgInterval {
292        months: 1,
293        days: 0,
294        microseconds: 0,
295    };
296    assert!(matches!(
297        Encode::<Postgres>::encode(&interval, &mut buf),
298        Ok(IsNull::No)
299    ));
300    assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
301    buf.clear();
302
303    assert_eq!(
304        PgInterval::default(),
305        PgInterval {
306            months: 0,
307            days: 0,
308            microseconds: 0,
309        }
310    );
311}
312
313#[test]
314fn test_pginterval_std() {
315    // Case for positive duration
316    let interval = PgInterval {
317        days: 0,
318        months: 0,
319        microseconds: 27_000,
320    };
321    assert_eq!(
322        &PgInterval::try_from(std::time::Duration::from_micros(27_000)).unwrap(),
323        &interval
324    );
325
326    // Case when precision loss occurs
327    assert!(PgInterval::try_from(std::time::Duration::from_nanos(27_000_001)).is_err());
328
329    // Case when microsecond overflow occurs
330    assert!(PgInterval::try_from(std::time::Duration::from_secs(20_000_000_000_000)).is_err());
331}
332
333#[test]
334#[cfg(feature = "chrono")]
335fn test_pginterval_chrono() {
336    // Case for positive duration
337    let interval = PgInterval {
338        days: 0,
339        months: 0,
340        microseconds: 27_000,
341    };
342    assert_eq!(
343        &PgInterval::try_from(chrono::Duration::microseconds(27_000)).unwrap(),
344        &interval
345    );
346
347    // Case for negative duration
348    let interval = PgInterval {
349        days: 0,
350        months: 0,
351        microseconds: -27_000,
352    };
353    assert_eq!(
354        &PgInterval::try_from(chrono::Duration::microseconds(-27_000)).unwrap(),
355        &interval
356    );
357
358    // Case when precision loss occurs
359    assert!(PgInterval::try_from(chrono::Duration::nanoseconds(27_000_001)).is_err());
360    assert!(PgInterval::try_from(chrono::Duration::nanoseconds(-27_000_001)).is_err());
361
362    // Case when nanosecond overflow occurs
363    assert!(PgInterval::try_from(chrono::Duration::seconds(10_000_000_000)).is_err());
364    assert!(PgInterval::try_from(chrono::Duration::seconds(-10_000_000_000)).is_err());
365}
366
367#[test]
368#[cfg(feature = "time")]
369fn test_pginterval_time() {
370    // Case for positive duration
371    let interval = PgInterval {
372        days: 0,
373        months: 0,
374        microseconds: 27_000,
375    };
376    assert_eq!(
377        &PgInterval::try_from(time::Duration::microseconds(27_000)).unwrap(),
378        &interval
379    );
380
381    // Case for negative duration
382    let interval = PgInterval {
383        days: 0,
384        months: 0,
385        microseconds: -27_000,
386    };
387    assert_eq!(
388        &PgInterval::try_from(time::Duration::microseconds(-27_000)).unwrap(),
389        &interval
390    );
391
392    // Case when precision loss occurs
393    assert!(PgInterval::try_from(time::Duration::nanoseconds(27_000_001)).is_err());
394    assert!(PgInterval::try_from(time::Duration::nanoseconds(-27_000_001)).is_err());
395
396    // Case when microsecond overflow occurs
397    assert!(PgInterval::try_from(time::Duration::seconds(10_000_000_000_000)).is_err());
398    assert!(PgInterval::try_from(time::Duration::seconds(-10_000_000_000_000)).is_err());
399}