Skip to main content

diesel/query_builder/
query_id.rs

1use super::QueryFragment;
2use alloc::boxed::Box;
3use core::any::{Any, TypeId};
4
5/// Uniquely identifies queries by their type for the purpose of prepared
6/// statement caching.
7///
8/// All types which implement `QueryFragment` should also implement this trait
9/// (It is not an actual supertrait of `QueryFragment` for boxing purposes).
10///
11/// See the documentation of [the `QueryId` type] and [`HAS_STATIC_QUERY_ID`]
12/// for more details.
13///
14/// [the `QueryId` type]: QueryId::QueryId
15/// [`HAS_STATIC_QUERY_ID`]: QueryId::HAS_STATIC_QUERY_ID
16///
17/// ### Deriving
18///
19/// This trait can [be automatically derived](derive@QueryId)
20/// by Diesel.
21/// For example, given this struct:
22///
23/// If the SQL generated by a struct is not uniquely identifiable by its type,
24/// meaning that `HAS_STATIC_QUERY_ID` should always be false,
25/// you should not derive this trait.
26/// In that case you should manually implement it instead.
27pub trait QueryId {
28    /// A type which uniquely represents `Self` in a SQL query.
29    ///
30    /// Typically this will be a re-construction of `Self` using the `QueryId`
31    /// type of each of your type parameters. For example, the type `And<Left,
32    /// Right>` would have `type QueryId = And<Left::QueryId, Right::QueryId>`.
33    ///
34    /// The exception to this is when one of your type parameters does not
35    /// affect whether the same prepared statement can be used or not. For
36    /// example, a bind parameter is represented as `Bound<SqlType, RustType>`.
37    /// The actual Rust type we are serializing does not matter for the purposes
38    /// of prepared statement reuse, but a query which has identical SQL but
39    /// different types for its bind parameters requires a new prepared
40    /// statement. For this reason, `Bound` would have `type QueryId =
41    /// Bound<SqlType::QueryId, ()>`.
42    ///
43    /// If `HAS_STATIC_QUERY_ID` is `false`, you can put any type here
44    /// (typically `()`).
45    type QueryId: Any;
46
47    /// Can the SQL generated by `Self` be uniquely identified by its type?
48    ///
49    /// Typically this question can be answered by looking at whether
50    /// `unsafe_to_cache_prepared` is called in your implementation of
51    /// `QueryFragment::walk_ast`. In Diesel itself, the only type which has
52    /// `false` here, but is potentially safe to store in the prepared statement
53    /// cache is a boxed query.
54    const HAS_STATIC_QUERY_ID: bool = true;
55
56    #[doc(hidden)]
57    const IS_WINDOW_FUNCTION: bool = false;
58
59    /// Returns the type id of `Self::QueryId` if `Self::HAS_STATIC_QUERY_ID`.
60    /// Returns `None` otherwise.
61    ///
62    /// You should never need to override this method.
63    fn query_id() -> Option<TypeId> {
64        if Self::HAS_STATIC_QUERY_ID {
65            Some(TypeId::of::<Self::QueryId>())
66        } else {
67            None
68        }
69    }
70}
71
72#[doc(inline)]
73pub use diesel_derives::QueryId;
74
75impl QueryId for () {
76    type QueryId = ();
77
78    const HAS_STATIC_QUERY_ID: bool = true;
79}
80
81impl<T: QueryId + ?Sized> QueryId for Box<T> {
82    type QueryId = T::QueryId;
83
84    const HAS_STATIC_QUERY_ID: bool = T::HAS_STATIC_QUERY_ID;
85
86    const IS_WINDOW_FUNCTION: bool = T::IS_WINDOW_FUNCTION;
87}
88
89impl<T: QueryId + ?Sized> QueryId for alloc::rc::Rc<T> {
90    type QueryId = T::QueryId;
91
92    const HAS_STATIC_QUERY_ID: bool = T::HAS_STATIC_QUERY_ID;
93
94    const IS_WINDOW_FUNCTION: bool = T::IS_WINDOW_FUNCTION;
95}
96
97impl<T: QueryId + ?Sized> QueryId for alloc::sync::Arc<T> {
98    type QueryId = T::QueryId;
99
100    const HAS_STATIC_QUERY_ID: bool = T::HAS_STATIC_QUERY_ID;
101
102    const IS_WINDOW_FUNCTION: bool = T::IS_WINDOW_FUNCTION;
103}
104
105impl<T: QueryId + ?Sized> QueryId for &T {
106    type QueryId = T::QueryId;
107
108    const HAS_STATIC_QUERY_ID: bool = T::HAS_STATIC_QUERY_ID;
109    const IS_WINDOW_FUNCTION: bool = T::IS_WINDOW_FUNCTION;
110}
111
112impl<DB> QueryId for dyn QueryFragment<DB> {
113    type QueryId = ();
114
115    const HAS_STATIC_QUERY_ID: bool = false;
116    // todo: we need to deal with IS_WINDOW_FUNCTION here
117}
118
119#[cfg(test)]
120#[allow(unused_parens)] // FIXME: Remove this attribute once false positive is resolved.
121mod tests {
122    use std::any::TypeId;
123
124    use super::QueryId;
125    use crate::prelude::*;
126
127    table! {
128        users {
129            id -> Integer,
130            name -> VarChar,
131        }
132    }
133
134    fn query_id<T: QueryId>(_: T) -> Option<TypeId> {
135        T::query_id()
136    }
137
138    #[diesel_test_helper::test]
139    fn queries_with_no_dynamic_elements_have_a_static_id() {
140        use self::users::dsl::*;
141        assert!(query_id(users).is_some());
142        assert!(query_id(users.select(name)).is_some());
143        assert!(query_id(users.filter(name.eq("Sean"))).is_some());
144    }
145
146    #[diesel_test_helper::test]
147    fn queries_with_different_types_have_different_ids() {
148        let id1 = query_id(users::table.select(users::name));
149        let id2 = query_id(users::table.select(users::id));
150        assert_ne!(id1, id2);
151    }
152
153    #[diesel_test_helper::test]
154    fn bind_params_use_only_sql_type_for_query_id() {
155        use self::users::dsl::*;
156        let id1 = query_id(users.filter(name.eq("Sean")));
157        let id2 = query_id(users.filter(name.eq("Tess".to_string())));
158
159        assert_eq!(id1, id2);
160    }
161
162    #[diesel_test_helper::test]
163    #[cfg(feature = "postgres")]
164    fn boxed_queries_do_not_have_static_query_id() {
165        use crate::pg::Pg;
166        assert!(query_id(users::table.into_boxed::<Pg>()).is_none());
167    }
168}