diesel/pg/expression/extensions/
interval_dsl.rs1use std::ops::Mul;
2
3use crate::data_types::PgInterval;
4
5#[cfg(feature = "postgres_backend")]
82pub trait IntervalDsl: Sized + From<i32> + Mul<Self, Output = Self> {
83    fn microseconds(self) -> PgInterval;
85    fn days(self) -> PgInterval;
87    fn months(self) -> PgInterval;
89
90    fn milliseconds(self) -> PgInterval {
92        (self * 1000.into()).microseconds()
93    }
94
95    fn seconds(self) -> PgInterval {
97        (self * 1000.into()).milliseconds()
98    }
99
100    fn minutes(self) -> PgInterval {
102        (self * 60.into()).seconds()
103    }
104
105    fn hours(self) -> PgInterval {
107        (self * 60.into()).minutes()
108    }
109
110    fn weeks(self) -> PgInterval {
116        (self * 7.into()).days()
117    }
118
119    fn years(self) -> PgInterval {
131        (self * 12.into()).months()
132    }
133
134    fn microsecond(self) -> PgInterval {
136        self.microseconds()
137    }
138
139    fn millisecond(self) -> PgInterval {
141        self.milliseconds()
142    }
143
144    fn second(self) -> PgInterval {
146        self.seconds()
147    }
148
149    fn minute(self) -> PgInterval {
151        self.minutes()
152    }
153
154    fn hour(self) -> PgInterval {
156        self.hours()
157    }
158
159    fn day(self) -> PgInterval {
161        self.days()
162    }
163
164    fn week(self) -> PgInterval {
166        self.weeks()
167    }
168
169    fn month(self) -> PgInterval {
171        self.months()
172    }
173
174    fn year(self) -> PgInterval {
176        self.years()
177    }
178}
179
180impl IntervalDsl for i32 {
181    fn microseconds(self) -> PgInterval {
182        i64::from(self).microseconds()
183    }
184
185    fn days(self) -> PgInterval {
186        PgInterval::from_days(self)
187    }
188
189    fn months(self) -> PgInterval {
190        PgInterval::from_months(self)
191    }
192
193    fn milliseconds(self) -> PgInterval {
194        i64::from(self).milliseconds()
195    }
196
197    fn seconds(self) -> PgInterval {
198        i64::from(self).seconds()
199    }
200
201    fn minutes(self) -> PgInterval {
202        i64::from(self).minutes()
203    }
204
205    fn hours(self) -> PgInterval {
206        i64::from(self).hours()
207    }
208}
209
210impl IntervalDsl for i64 {
211    fn microseconds(self) -> PgInterval {
212        PgInterval::from_microseconds(self)
213    }
214
215    fn days(self) -> PgInterval {
216        i32::try_from(self)
217            .expect("Maximal supported day interval size is 32 bit")
218            .days()
219    }
220
221    fn months(self) -> PgInterval {
222        i32::try_from(self)
223            .expect("Maximal supported month interval size is 32 bit")
224            .months()
225    }
226}
227
228#[allow(clippy::cast_possible_truncation)] impl IntervalDsl for f64 {
230    fn microseconds(self) -> PgInterval {
231        (self.round() as i64).microseconds()
232    }
233
234    fn days(self) -> PgInterval {
235        let fractional_days = (self.fract() * 86_400.0).seconds();
236        PgInterval::from_days(self.trunc() as i32) + fractional_days
237    }
238
239    fn months(self) -> PgInterval {
240        let fractional_months = (self.fract() * 30.0).days();
241        PgInterval::from_months(self.trunc() as i32) + fractional_months
242    }
243
244    fn years(self) -> PgInterval {
245        ((self * 12.0).trunc() as i32).months()
246    }
247}
248
249#[cfg(test)]
250#[allow(clippy::items_after_statements)]
253mod tests {
254    extern crate dotenvy;
255    extern crate quickcheck;
256
257    use self::quickcheck::quickcheck;
258
259    use super::*;
260    use crate::dsl::sql;
261    use crate::prelude::*;
262    use crate::test_helpers::*;
263    use crate::{select, sql_types};
264
265    macro_rules! test_fn {
266        ($tpe:ty, $test_name:ident, $units: ident, $max_range: expr) => {
267            test_fn!($tpe, $test_name, $units, $max_range, 1, 0);
268        };
269        ($tpe:ty, $test_name:ident, $units:ident, $max_range: expr, $max_diff: expr) => {
270            test_fn!($tpe, $test_name, $units, $max_range, $max_diff, 0);
271        };
272        ($tpe:ty, $test_name:ident, $units:ident, $max_range: expr, $max_diff: expr, $max_month_diff: expr) => {
273            fn $test_name(val: $tpe) -> bool {
274                if val > $max_range || val < (-1 as $tpe) * $max_range || (val as f64).is_nan() {
275                    return true;
276                }
277                let conn = &mut pg_connection();
278                let sql_str = format!(concat!("'{} ", stringify!($units), "'::interval"), val);
279                let query = select(sql::<sql_types::Interval>(&sql_str));
280                let value = val.$units();
281                query
282                    .get_result::<PgInterval>(conn)
283                    .map(|res| {
284                        (value.months - res.months).abs() <= $max_month_diff
285                            && value.days == res.days
286                            && (value.microseconds - res.microseconds).abs() <= $max_diff
287                    })
288                    .unwrap_or(false)
289            }
290
291            quickcheck($test_name as fn($tpe) -> bool);
292        };
293    }
294
295    #[test]
296    fn intervals_match_pg_values_i32() {
297        test_fn!(i32, test_microseconds, microseconds, i32::MAX);
298        test_fn!(i32, test_milliseconds, milliseconds, i32::MAX);
299        test_fn!(i32, test_seconds, seconds, i32::MAX);
300        test_fn!(i32, test_minutes, minutes, i32::MAX);
301        test_fn!(i32, test_hours, hours, i32::MAX);
302        test_fn!(i32, test_days, days, i32::MAX);
303        test_fn!(i32, test_weeks, weeks, i32::MAX / 7);
304        test_fn!(i32, test_months, months, i32::MAX);
305        test_fn!(i32, test_years, years, i32::MAX / 12);
306    }
307
308    #[test]
309    fn intervals_match_pg_values_i64() {
310        test_fn!(i64, test_microseconds, microseconds, i32::MAX as i64);
313        test_fn!(i64, test_milliseconds, milliseconds, i32::MAX as i64);
314        test_fn!(i64, test_seconds, seconds, i32::MAX as i64);
315        test_fn!(i64, test_minutes, minutes, i32::MAX as i64);
316        test_fn!(i64, test_hours, hours, i32::MAX as i64);
317        test_fn!(i64, test_days, days, i32::MAX as i64);
318        test_fn!(i64, test_weeks, weeks, (i32::MAX / 7) as i64);
319        test_fn!(i64, test_months, months, i32::MAX as i64);
320        test_fn!(i64, test_years, years, (i32::MAX / 12) as i64);
321    }
322
323    #[test]
324    fn intervals_match_pg_values_f64() {
325        const MAX_DIFF: i64 = 1_000_000;
326        test_fn!(
329            f64,
330            test_microseconds,
331            microseconds,
332            i32::MAX as f64,
333            MAX_DIFF
334        );
335        test_fn!(
336            f64,
337            test_milliseconds,
338            milliseconds,
339            i32::MAX as f64,
340            MAX_DIFF
341        );
342        test_fn!(f64, test_seconds, seconds, i32::MAX as f64, MAX_DIFF);
343        test_fn!(f64, test_minutes, minutes, i32::MAX as f64, MAX_DIFF);
344        test_fn!(f64, test_hours, hours, i32::MAX as f64, MAX_DIFF);
345        test_fn!(f64, test_days, days, i32::MAX as f64, MAX_DIFF);
346        test_fn!(f64, test_weeks, weeks, (i32::MAX / 7) as f64, MAX_DIFF);
347        test_fn!(f64, test_months, months, i32::MAX as f64, MAX_DIFF);
348        test_fn!(f64, test_years, years, (i32::MAX / 12) as f64, MAX_DIFF, 1);
352    }
353}