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