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