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, 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) -> IsNull {
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        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) -> IsNull {
87        PgInterval::try_from(*self)
88            .expect("failed to encode `std::time::Duration`")
89            .encode_by_ref(buf)
90    }
91
92    fn size_hint(&self) -> usize {
93        2 * mem::size_of::<i64>()
94    }
95}
96
97impl TryFrom<std::time::Duration> for PgInterval {
98    type Error = BoxDynError;
99
100    /// Convert a `std::time::Duration` to a `PgInterval`
101    ///
102    /// This returns an error if there is a loss of precision using nanoseconds or if there is a
103    /// microsecond overflow.
104    fn try_from(value: std::time::Duration) -> Result<Self, BoxDynError> {
105        if value.as_nanos() % 1000 != 0 {
106            return Err("PostgreSQL `INTERVAL` does not support nanoseconds precision".into());
107        }
108
109        Ok(Self {
110            months: 0,
111            days: 0,
112            microseconds: value.as_micros().try_into()?,
113        })
114    }
115}
116
117#[cfg(feature = "chrono")]
118impl Type<Postgres> for chrono::Duration {
119    fn type_info() -> PgTypeInfo {
120        PgTypeInfo::INTERVAL
121    }
122}
123
124#[cfg(feature = "chrono")]
125impl PgHasArrayType for chrono::Duration {
126    fn array_type_info() -> PgTypeInfo {
127        PgTypeInfo::INTERVAL_ARRAY
128    }
129}
130
131#[cfg(feature = "chrono")]
132impl Encode<'_, Postgres> for chrono::Duration {
133    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
134        let pg_interval = PgInterval::try_from(*self).expect("Failed to encode chrono::Duration");
135        pg_interval.encode_by_ref(buf)
136    }
137
138    fn size_hint(&self) -> usize {
139        2 * mem::size_of::<i64>()
140    }
141}
142
143#[cfg(feature = "chrono")]
144impl TryFrom<chrono::Duration> for PgInterval {
145    type Error = BoxDynError;
146
147    /// Convert a `chrono::Duration` to a `PgInterval`.
148    ///
149    /// This returns an error if there is a loss of precision using nanoseconds or if there is a
150    /// nanosecond overflow.
151    fn try_from(value: chrono::Duration) -> Result<Self, BoxDynError> {
152        value
153            .num_nanoseconds()
154            .map_or::<Result<_, Self::Error>, _>(
155                Err("Overflow has occurred for PostgreSQL `INTERVAL`".into()),
156                |nanoseconds| {
157                    if nanoseconds % 1000 != 0 {
158                        return Err(
159                            "PostgreSQL `INTERVAL` does not support nanoseconds precision".into(),
160                        );
161                    }
162                    Ok(())
163                },
164            )?;
165
166        value.num_microseconds().map_or(
167            Err("Overflow has occurred for PostgreSQL `INTERVAL`".into()),
168            |microseconds| {
169                Ok(Self {
170                    months: 0,
171                    days: 0,
172                    microseconds: microseconds,
173                })
174            },
175        )
176    }
177}
178
179#[cfg(feature = "time")]
180impl Type<Postgres> for time::Duration {
181    fn type_info() -> PgTypeInfo {
182        PgTypeInfo::INTERVAL
183    }
184}
185
186#[cfg(feature = "time")]
187impl PgHasArrayType for time::Duration {
188    fn array_type_info() -> PgTypeInfo {
189        PgTypeInfo::INTERVAL_ARRAY
190    }
191}
192
193#[cfg(feature = "time")]
194impl Encode<'_, Postgres> for time::Duration {
195    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
196        let pg_interval = PgInterval::try_from(*self).expect("Failed to encode time::Duration");
197        pg_interval.encode_by_ref(buf)
198    }
199
200    fn size_hint(&self) -> usize {
201        2 * mem::size_of::<i64>()
202    }
203}
204
205#[cfg(feature = "time")]
206impl TryFrom<time::Duration> for PgInterval {
207    type Error = BoxDynError;
208
209    /// Convert a `time::Duration` to a `PgInterval`.
210    ///
211    /// This returns an error if there is a loss of precision using nanoseconds or if there is a
212    /// microsecond overflow.
213    fn try_from(value: time::Duration) -> Result<Self, BoxDynError> {
214        if value.whole_nanoseconds() % 1000 != 0 {
215            return Err("PostgreSQL `INTERVAL` does not support nanoseconds precision".into());
216        }
217
218        Ok(Self {
219            months: 0,
220            days: 0,
221            microseconds: value.whole_microseconds().try_into()?,
222        })
223    }
224}
225
226#[test]
227fn test_encode_interval() {
228    let mut buf = PgArgumentBuffer::default();
229
230    let interval = PgInterval {
231        months: 0,
232        days: 0,
233        microseconds: 0,
234    };
235    assert!(matches!(
236        Encode::<Postgres>::encode(&interval, &mut buf),
237        IsNull::No
238    ));
239    assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
240    buf.clear();
241
242    let interval = PgInterval {
243        months: 0,
244        days: 0,
245        microseconds: 1_000,
246    };
247    assert!(matches!(
248        Encode::<Postgres>::encode(&interval, &mut buf),
249        IsNull::No
250    ));
251    assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 3, 232, 0, 0, 0, 0, 0, 0, 0, 0]);
252    buf.clear();
253
254    let interval = PgInterval {
255        months: 0,
256        days: 0,
257        microseconds: 1_000_000,
258    };
259    assert!(matches!(
260        Encode::<Postgres>::encode(&interval, &mut buf),
261        IsNull::No
262    ));
263    assert_eq!(&**buf, [0, 0, 0, 0, 0, 15, 66, 64, 0, 0, 0, 0, 0, 0, 0, 0]);
264    buf.clear();
265
266    let interval = PgInterval {
267        months: 0,
268        days: 0,
269        microseconds: 3_600_000_000,
270    };
271    assert!(matches!(
272        Encode::<Postgres>::encode(&interval, &mut buf),
273        IsNull::No
274    ));
275    assert_eq!(
276        &**buf,
277        [0, 0, 0, 0, 214, 147, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0]
278    );
279    buf.clear();
280
281    let interval = PgInterval {
282        months: 0,
283        days: 1,
284        microseconds: 0,
285    };
286    assert!(matches!(
287        Encode::<Postgres>::encode(&interval, &mut buf),
288        IsNull::No
289    ));
290    assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]);
291    buf.clear();
292
293    let interval = PgInterval {
294        months: 1,
295        days: 0,
296        microseconds: 0,
297    };
298    assert!(matches!(
299        Encode::<Postgres>::encode(&interval, &mut buf),
300        IsNull::No
301    ));
302    assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
303    buf.clear();
304
305    assert_eq!(
306        PgInterval::default(),
307        PgInterval {
308            months: 0,
309            days: 0,
310            microseconds: 0,
311        }
312    );
313}
314
315#[test]
316fn test_pginterval_std() {
317    // Case for positive duration
318    let interval = PgInterval {
319        days: 0,
320        months: 0,
321        microseconds: 27_000,
322    };
323    assert_eq!(
324        &PgInterval::try_from(std::time::Duration::from_micros(27_000)).unwrap(),
325        &interval
326    );
327
328    // Case when precision loss occurs
329    assert!(PgInterval::try_from(std::time::Duration::from_nanos(27_000_001)).is_err());
330
331    // Case when microsecond overflow occurs
332    assert!(PgInterval::try_from(std::time::Duration::from_secs(20_000_000_000_000)).is_err());
333}
334
335#[test]
336#[cfg(feature = "chrono")]
337fn test_pginterval_chrono() {
338    // Case for positive duration
339    let interval = PgInterval {
340        days: 0,
341        months: 0,
342        microseconds: 27_000,
343    };
344    assert_eq!(
345        &PgInterval::try_from(chrono::Duration::microseconds(27_000)).unwrap(),
346        &interval
347    );
348
349    // Case for negative duration
350    let interval = PgInterval {
351        days: 0,
352        months: 0,
353        microseconds: -27_000,
354    };
355    assert_eq!(
356        &PgInterval::try_from(chrono::Duration::microseconds(-27_000)).unwrap(),
357        &interval
358    );
359
360    // Case when precision loss occurs
361    assert!(PgInterval::try_from(chrono::Duration::nanoseconds(27_000_001)).is_err());
362    assert!(PgInterval::try_from(chrono::Duration::nanoseconds(-27_000_001)).is_err());
363
364    // Case when nanosecond overflow occurs
365    assert!(PgInterval::try_from(chrono::Duration::seconds(10_000_000_000)).is_err());
366    assert!(PgInterval::try_from(chrono::Duration::seconds(-10_000_000_000)).is_err());
367}
368
369#[test]
370#[cfg(feature = "time")]
371fn test_pginterval_time() {
372    // Case for positive duration
373    let interval = PgInterval {
374        days: 0,
375        months: 0,
376        microseconds: 27_000,
377    };
378    assert_eq!(
379        &PgInterval::try_from(time::Duration::microseconds(27_000)).unwrap(),
380        &interval
381    );
382
383    // Case for negative duration
384    let interval = PgInterval {
385        days: 0,
386        months: 0,
387        microseconds: -27_000,
388    };
389    assert_eq!(
390        &PgInterval::try_from(time::Duration::microseconds(-27_000)).unwrap(),
391        &interval
392    );
393
394    // Case when precision loss occurs
395    assert!(PgInterval::try_from(time::Duration::nanoseconds(27_000_001)).is_err());
396    assert!(PgInterval::try_from(time::Duration::nanoseconds(-27_000_001)).is_err());
397
398    // Case when microsecond overflow occurs
399    assert!(PgInterval::try_from(time::Duration::seconds(10_000_000_000_000)).is_err());
400    assert!(PgInterval::try_from(time::Duration::seconds(-10_000_000_000_000)).is_err());
401}