Skip to content

Commit 4bbd581

Browse files
committed
Allow now to be used as a timestamptz
There are two things wrong with this PR, but I still think this is the best short term solution. The first and most glaringly obviously wrong thing about it is that the type of `NOW()` **IS** `timestamptz`. However, since we currently have that defined as a PG specific type, we can't set the type of the expression properly. Timestamp with/without timezone is a thing in the SQL standard, (it's just ignored by SQLite). I need to examine how MySQL handles it and figure out how to promote it to a more general type. However, I don't want to deal with that right now (and probably not before 0.11). I do still want to fix this issue, so here we are. We also have a more general problem of type equivalence that we need to address. `Json` expressions can always be used where a `Jsonb` is expected. `Text` with `VarChar` (though we've skirted that one by making them type aliases). Again, this is a deeper problem that I don't currently have a solution in mind for. So in the short term, this gives us a way to punt on the problem for a while. For the most common cases that crop up, we can write `AsExpression` impls for those cases when we notice them. Fixes diesel-rs#685.
1 parent e1b78e0 commit 4bbd581

5 files changed

Lines changed: 116 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
2727
[max-0.11.0]: http://docs.diesel.rs/diesel/expression/dsl/fn.max.html
2828
[min-0.11.0]: http://docs.diesel.rs/diesel/expression/dsl/fn.min.html
2929

30+
* [`now`][now-0.11.0] can now be used as an expression of type `Timestamptz`.
31+
32+
[now-0.11.0]: http://docs.diesel.rs/diesel/expression/dsl/struct.now.html
33+
3034
## [0.10.1] - 2017-02-08
3135

3236
### Fixed

diesel/src/expression/coerce.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use std::marker::PhantomData;
2+
3+
use backend::Backend;
4+
use expression::{Expression, SelectableExpression, NonAggregate};
5+
use query_builder::*;
6+
use result::QueryResult;
7+
8+
#[derive(Debug, Copy, Clone)]
9+
#[doc(hidden)]
10+
/// Coerces an expression to be another type. No checks are performed to ensure
11+
/// that the new type is valid in all positions that the previous type was.
12+
/// This does not perform an actual cast, it just lies to our type system.
13+
///
14+
/// This is used for a few expressions where we know that the types are actually
15+
/// always interchangeable. (Examples of this include `Timestamp` vs
16+
/// `Timestamptz`, `VarChar` vs `Text`, and `Json` vs `Jsonb`).
17+
///
18+
/// This struct should not be considered a general solution to equivalent types.
19+
/// It is a short term workaround for expressions which are known to be commonly
20+
/// used.
21+
pub struct Coerce<T, ST> {
22+
expr: T,
23+
_marker: PhantomData<ST>,
24+
}
25+
26+
impl<T, ST> Coerce<T, ST> {
27+
pub fn new(expr: T) -> Self {
28+
Coerce {
29+
expr: expr,
30+
_marker: PhantomData,
31+
}
32+
}
33+
}
34+
35+
impl<T, ST> Expression for Coerce<T, ST> where
36+
T: Expression,
37+
{
38+
type SqlType = ST;
39+
}
40+
41+
impl<T, ST, QS> SelectableExpression<QS> for Coerce<T, ST> where
42+
T: SelectableExpression<QS>,
43+
{
44+
}
45+
46+
impl<T, ST, DB> QueryFragment<DB> for Coerce<T, ST> where
47+
T: QueryFragment<DB>,
48+
DB: Backend,
49+
{
50+
fn to_sql(&self, out: &mut DB::QueryBuilder) -> BuildQueryResult {
51+
self.expr.to_sql(out)
52+
}
53+
54+
fn collect_binds(&self, out: &mut DB::BindCollector) -> QueryResult<()> {
55+
self.expr.collect_binds(out)
56+
}
57+
58+
fn is_safe_to_cache_prepared(&self) -> bool {
59+
self.expr.is_safe_to_cache_prepared()
60+
}
61+
}
62+
63+
impl<T: QueryId, ST: 'static> QueryId for Coerce<T, ST> {
64+
type QueryId = Coerce<T::QueryId, ST>;
65+
66+
fn has_static_query_id() -> bool {
67+
true
68+
}
69+
}
70+
71+
impl<T, ST> NonAggregate for Coerce<T, ST> where
72+
T: NonAggregate,
73+
Coerce<T, ST>: Expression,
74+
{
75+
}

diesel/src/expression/functions/date_and_time.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,19 @@ operator_allowed!(now, Sub, sub);
4242
sql_function!(date, date_t, (x: Timestamp) -> Date,
4343
"Represents the SQL DATE() function. The argument should be a Timestamp
4444
expression, and the return value will be an expression of type Date");
45+
46+
#[cfg(feature="postgres")]
47+
use expression::AsExpression;
48+
#[cfg(feature="postgres")]
49+
use expression::coerce::Coerce;
50+
#[cfg(feature="postgres")]
51+
use types::Timestamptz;
52+
53+
#[cfg(feature="postgres")]
54+
impl AsExpression<Timestamptz> for now {
55+
type Expression = Coerce<now, Timestamptz>;
56+
57+
fn as_expression(self) -> Self::Expression {
58+
Coerce::new(self)
59+
}
60+
}

diesel/src/expression/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ pub mod array_comparison;
2424
#[doc(hidden)]
2525
pub mod bound;
2626
#[doc(hidden)]
27+
pub mod coerce;
28+
#[doc(hidden)]
2729
pub mod count;
2830
#[doc(hidden)]
2931
pub mod exists;

diesel_tests/tests/expressions/date_and_time.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ fn now_executes_sql_function_now() {
4040
assert_eq!(Ok(vec![2]), after_today);
4141
}
4242

43+
#[test]
44+
#[cfg(feature = "postgres")]
45+
// FIXME: Replace this with an actual timestamptz expression
46+
fn now_can_be_used_as_timestamptz() {
47+
use self::has_timestamps::dsl::*;
48+
use diesel::types::Timestamptz;
49+
50+
let connection = connection();
51+
setup_test_table(&connection);
52+
connection.execute("INSERT INTO has_timestamps (created_at) VALUES \
53+
(NOW() - '1 day'::interval)").unwrap();
54+
55+
let created_at_tz = sql::<Timestamptz>("created_at");
56+
let before_now = has_timestamps.select(id)
57+
.filter(created_at_tz.lt(now))
58+
.load::<i32>(&connection);
59+
assert_eq!(Ok(vec![1]), before_now);
60+
}
61+
4362
#[test]
4463
#[cfg(feature = "sqlite")]
4564
fn now_executes_sql_function_now() {

0 commit comments

Comments
 (0)