diesel/pg/expression/extensions/
interval_dsl.rs

1use std::ops::Mul;
2
3use data_types::PgInterval;
4
5/// A DSL added to integers and `f64` to construct PostgreSQL intervals.
6///
7/// The behavior of these methods when called on `NAN` or `Infinity`, or when
8/// overflow occurs is unspecified.
9///
10/// # Examples
11///
12/// ```rust
13/// # #[macro_use] extern crate diesel;
14/// # include!("../../../doctest_setup.rs");
15/// # use diesel::dsl::*;
16/// #
17/// # table! {
18/// #     users {
19/// #         id -> Serial,
20/// #         name -> VarChar,
21/// #         created_at -> Timestamp,
22/// #     }
23/// # }
24/// #
25/// # fn main() {
26/// #     use self::users::dsl::*;
27/// #     let connection = connection_no_data();
28/// #     connection.execute("CREATE TABLE users (id serial primary key, name
29/// #        varchar not null, created_at timestamp not null)").unwrap();
30/// connection.execute("INSERT INTO users (name, created_at) VALUES
31///     ('Sean', NOW()), ('Tess', NOW() - '5 minutes'::interval),
32///     ('Jim', NOW() - '10 minutes'::interval)").unwrap();
33///
34/// let mut data: Vec<String> = users
35///     .select(name)
36///     .filter(created_at.gt(now - 7.minutes()))
37///     .load(&connection).unwrap();
38/// assert_eq!(2, data.len());
39/// assert_eq!("Sean".to_string(), data[0]);
40/// assert_eq!("Tess".to_string(), data[1]);
41/// # }
42/// ```
43///
44/// ```rust
45/// # #[macro_use] extern crate diesel;
46/// # include!("../../../doctest_setup.rs");
47/// # use diesel::dsl::*;
48/// #
49/// # table! {
50/// #     users {
51/// #         id -> Serial,
52/// #         name -> VarChar,
53/// #         created_at -> Timestamp,
54/// #     }
55/// # }
56/// #
57/// # fn main() {
58/// #     use self::users::dsl::*;
59/// #     let connection = connection_no_data();
60/// #     connection.execute("CREATE TABLE users (id serial primary key, name
61/// #        varchar not null, created_at timestamp not null)").unwrap();
62/// connection.execute("INSERT INTO users (name, created_at) VALUES
63///     ('Sean', NOW()), ('Tess', NOW() - '5 days'::interval),
64///     ('Jim', NOW() - '10 days'::interval)").unwrap();
65///
66/// let mut data: Vec<String> = users
67///     .select(name)
68///     .filter(created_at.gt(now - 7.days()))
69///     .load(&connection).unwrap();
70/// assert_eq!(2, data.len());
71/// assert_eq!("Sean".to_string(), data[0]);
72/// assert_eq!("Tess".to_string(), data[1]);
73/// # }
74/// ```
75pub trait IntervalDsl: Sized + From<i32> + Mul<Self, Output = Self> {
76    /// Returns a PgInterval representing `self` as microseconds
77    fn microseconds(self) -> PgInterval;
78    /// Returns a PgInterval representing `self` in days
79    fn days(self) -> PgInterval;
80    /// Returns a PgInterval representing `self` in months
81    fn months(self) -> PgInterval;
82
83    /// Returns a PgInterval representing `self` as milliseconds
84    fn milliseconds(self) -> PgInterval {
85        (self * 1000.into()).microseconds()
86    }
87
88    /// Returns a PgInterval representing `self` as seconds
89    fn seconds(self) -> PgInterval {
90        (self * 1000.into()).milliseconds()
91    }
92
93    /// Returns a PgInterval representing `self` as minutes
94    fn minutes(self) -> PgInterval {
95        (self * 60.into()).seconds()
96    }
97
98    /// Returns a PgInterval representing `self` as hours
99    fn hours(self) -> PgInterval {
100        (self * 60.into()).minutes()
101    }
102
103    /// Returns a PgInterval representing `self` in weeks
104    ///
105    /// Note: When called on a high precision float, the returned interval may
106    /// be 1 microsecond different than the equivalent string passed to
107    /// PostgreSQL.
108    fn weeks(self) -> PgInterval {
109        (self * 7.into()).days()
110    }
111
112    /// Returns a PgInterval representing `self` in weeks
113    ///
114    /// Note: When called on a float, this method will mimic the behavior of
115    /// PostgreSQL's interval parsing, and will ignore units smaller than
116    /// months.
117    ///
118    /// ```rust
119    /// # use diesel::dsl::*;
120    /// assert_eq!(1.08.years(), 1.year());
121    /// assert_eq!(1.09.years(), 1.year() + 1.month());
122    /// ```
123    fn years(self) -> PgInterval {
124        (self * 12.into()).months()
125    }
126
127    /// Identical to `microseconds`
128    fn microsecond(self) -> PgInterval {
129        self.microseconds()
130    }
131
132    /// Identical to `milliseconds`
133    fn millisecond(self) -> PgInterval {
134        self.milliseconds()
135    }
136
137    /// Identical to `seconds`
138    fn second(self) -> PgInterval {
139        self.seconds()
140    }
141
142    /// Identical to `minutes`
143    fn minute(self) -> PgInterval {
144        self.minutes()
145    }
146
147    /// Identical to `hours`
148    fn hour(self) -> PgInterval {
149        self.hours()
150    }
151
152    /// Identical to `days`
153    fn day(self) -> PgInterval {
154        self.days()
155    }
156
157    /// Identical to `weeks`
158    fn week(self) -> PgInterval {
159        self.weeks()
160    }
161
162    /// Identical to `months`
163    fn month(self) -> PgInterval {
164        self.months()
165    }
166
167    /// Identical to `years`
168    fn year(self) -> PgInterval {
169        self.years()
170    }
171}
172
173impl IntervalDsl for i32 {
174    fn microseconds(self) -> PgInterval {
175        i64::from(self).microseconds()
176    }
177
178    fn days(self) -> PgInterval {
179        PgInterval::from_days(self)
180    }
181
182    fn months(self) -> PgInterval {
183        PgInterval::from_months(self)
184    }
185
186    fn milliseconds(self) -> PgInterval {
187        i64::from(self).milliseconds()
188    }
189
190    fn seconds(self) -> PgInterval {
191        i64::from(self).seconds()
192    }
193
194    fn minutes(self) -> PgInterval {
195        i64::from(self).minutes()
196    }
197
198    fn hours(self) -> PgInterval {
199        i64::from(self).hours()
200    }
201}
202
203impl IntervalDsl for i64 {
204    fn microseconds(self) -> PgInterval {
205        PgInterval::from_microseconds(self)
206    }
207
208    fn days(self) -> PgInterval {
209        (self as i32).days()
210    }
211
212    fn months(self) -> PgInterval {
213        (self as i32).months()
214    }
215}
216
217impl IntervalDsl for f64 {
218    fn microseconds(self) -> PgInterval {
219        (self.round() as i64).microseconds()
220    }
221
222    fn days(self) -> PgInterval {
223        let fractional_days = (self.fract() * 86_400.0).seconds();
224        PgInterval::from_days(self.trunc() as i32) + fractional_days
225    }
226
227    fn months(self) -> PgInterval {
228        let fractional_months = (self.fract() * 30.0).days();
229        PgInterval::from_months(self.trunc() as i32) + fractional_months
230    }
231
232    fn years(self) -> PgInterval {
233        ((self * 12.0).trunc() as i32).months()
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    extern crate dotenv;
240    extern crate quickcheck;
241
242    use self::dotenv::dotenv;
243    use self::quickcheck::quickcheck;
244
245    use super::*;
246    use data_types::PgInterval;
247    use dsl::sql;
248    use prelude::*;
249    use {select, sql_types};
250
251    thread_local! {
252        static CONN: PgConnection = {
253            dotenv().ok();
254
255            let connection_url = ::std::env::var("PG_DATABASE_URL")
256                .or_else(|_| ::std::env::var("DATABASE_URL"))
257                .expect("DATABASE_URL must be set in order to run tests");
258            PgConnection::establish(&connection_url).unwrap()
259        }
260    }
261
262    macro_rules! test_fn {
263        ($tpe:ty, $test_name:ident, $units:ident) => {
264            fn $test_name(val: $tpe) -> bool {
265                CONN.with(|connection| {
266                    let sql_str = format!(concat!("'{} ", stringify!($units), "'::interval"), val);
267                    let query = select(sql::<sql_types::Interval>(&sql_str));
268                    let val = val.$units();
269                    query
270                        .get_result::<PgInterval>(connection)
271                        .map(|res| {
272                            val.months == res.months
273                                && val.days == res.days
274                                && val.microseconds - res.microseconds.abs() <= 1
275                        })
276                        .unwrap_or(false)
277                })
278            }
279
280            quickcheck($test_name as fn($tpe) -> bool);
281        };
282    }
283
284    #[test]
285    fn intervals_match_pg_values_i32() {
286        test_fn!(i32, test_microseconds, microseconds);
287        test_fn!(i32, test_milliseconds, milliseconds);
288        test_fn!(i32, test_seconds, seconds);
289        test_fn!(i32, test_minutes, minutes);
290        test_fn!(i32, test_hours, hours);
291        test_fn!(i32, test_days, days);
292        test_fn!(i32, test_weeks, weeks);
293        test_fn!(i32, test_months, months);
294        test_fn!(i32, test_years, years);
295    }
296
297    #[test]
298    fn intervals_match_pg_values_i64() {
299        test_fn!(i64, test_microseconds, microseconds);
300        test_fn!(i64, test_milliseconds, milliseconds);
301        test_fn!(i64, test_seconds, seconds);
302        test_fn!(i64, test_minutes, minutes);
303        test_fn!(i64, test_hours, hours);
304        test_fn!(i64, test_days, days);
305        test_fn!(i64, test_weeks, weeks);
306        test_fn!(i64, test_months, months);
307        test_fn!(i64, test_years, years);
308    }
309
310    #[test]
311    fn intervals_match_pg_values_f64() {
312        test_fn!(f64, test_microseconds, microseconds);
313        test_fn!(f64, test_milliseconds, milliseconds);
314        test_fn!(f64, test_seconds, seconds);
315        test_fn!(f64, test_minutes, minutes);
316        test_fn!(f64, test_hours, hours);
317        test_fn!(f64, test_days, days);
318        test_fn!(f64, test_weeks, weeks);
319        test_fn!(f64, test_months, months);
320        test_fn!(f64, test_years, years);
321    }
322}