diesel/connection/statement_cache/
strategy.rs1use crate::backend::Backend;
2use crate::util::std_compat::Entry;
3use crate::util::std_compat::HashMap;
4use core::hash::Hash;
5
6use super::{CacheSize, StatementCacheKey};
7
8#[cfg_attr(
14 feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
15 allow(missing_debug_implementations)
16)]
17pub enum LookupStatementResult<'a, DB, Statement>
19where
20 DB: Backend,
21{
22 CacheEntry(Entry<'a, StatementCacheKey<DB>, Statement>),
26 NoCache(StatementCacheKey<DB>),
28}
29
30#[allow(unreachable_pub)]
32pub trait StatementCacheStrategy<DB, Statement>: Send + 'static
33where
34 DB: Backend,
35 StatementCacheKey<DB>: Hash + Eq,
36{
37 fn cache_size(&self) -> CacheSize;
39
40 fn lookup_statement(
42 &mut self,
43 key: StatementCacheKey<DB>,
44 ) -> LookupStatementResult<'_, DB, Statement>;
45
46 #[cfg(feature = "__sqlite-shared")]
51 fn clear(&mut self);
52}
53
54#[allow(missing_debug_implementations, unreachable_pub)]
56pub struct WithCacheStrategy<DB, Statement>
57where
58 DB: Backend,
59{
60 cache: HashMap<StatementCacheKey<DB>, Statement>,
61}
62
63impl<DB, Statement> Default for WithCacheStrategy<DB, Statement>
64where
65 DB: Backend,
66{
67 fn default() -> Self {
68 Self {
69 cache: Default::default(),
70 }
71 }
72}
73
74impl<DB, Statement> StatementCacheStrategy<DB, Statement> for WithCacheStrategy<DB, Statement>
75where
76 DB: Backend + 'static,
77 StatementCacheKey<DB>: Hash + Eq,
78 DB::TypeMetadata: Send + Clone,
79 DB::QueryBuilder: Default,
80 Statement: Send + 'static,
81{
82 fn lookup_statement(
83 &mut self,
84 entry: StatementCacheKey<DB>,
85 ) -> LookupStatementResult<'_, DB, Statement> {
86 LookupStatementResult::CacheEntry(self.cache.entry(entry))
87 }
88
89 fn cache_size(&self) -> CacheSize {
90 CacheSize::Unbounded
91 }
92
93 #[cfg(feature = "__sqlite-shared")]
94 fn clear(&mut self) {
95 self.cache.clear();
96 }
97}
98
99#[allow(missing_debug_implementations, unreachable_pub)]
101#[derive(#[automatically_derived]
#[allow(missing_debug_implementations, unreachable_pub)]
impl ::core::clone::Clone for WithoutCacheStrategy {
#[inline]
fn clone(&self) -> WithoutCacheStrategy { *self }
}Clone, #[automatically_derived]
#[allow(missing_debug_implementations, unreachable_pub)]
impl ::core::marker::Copy for WithoutCacheStrategy { }Copy, #[automatically_derived]
#[allow(missing_debug_implementations, unreachable_pub)]
impl ::core::default::Default for WithoutCacheStrategy {
#[inline]
fn default() -> WithoutCacheStrategy { WithoutCacheStrategy {} }
}Default)]
102pub struct WithoutCacheStrategy {}
103
104impl<DB, Statement> StatementCacheStrategy<DB, Statement> for WithoutCacheStrategy
105where
106 DB: Backend,
107 StatementCacheKey<DB>: Hash + Eq,
108 DB::TypeMetadata: Clone,
109 DB::QueryBuilder: Default,
110 Statement: 'static,
111{
112 fn lookup_statement(
113 &mut self,
114 entry: StatementCacheKey<DB>,
115 ) -> LookupStatementResult<'_, DB, Statement> {
116 LookupStatementResult::NoCache(entry)
117 }
118
119 fn cache_size(&self) -> CacheSize {
120 CacheSize::Disabled
121 }
122
123 #[cfg(feature = "__sqlite-shared")]
124 fn clear(&mut self) {}
125}
126
127#[allow(dead_code)]
128#[cfg(test)]
129mod testing_utils {
130
131 use crate::{
132 Connection,
133 connection::{Instrumentation, InstrumentationEvent},
134 };
135
136 #[derive(Default)]
137 pub struct RecordCacheEvents {
138 pub list: Vec<String>,
139 }
140
141 impl Instrumentation for RecordCacheEvents {
142 fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
143 if let InstrumentationEvent::CacheQuery { sql } = event {
144 self.list.push(sql.to_owned());
145 }
146 }
147 }
148
149 pub fn count_cache_calls(conn: &mut impl Connection) -> usize {
150 if let Some(events) = conn
151 .instrumentation()
152 .as_any()
153 .downcast_ref::<RecordCacheEvents>()
154 {
155 events.list.len()
156 } else {
157 0
158 }
159 }
160}
161
162#[cfg(test)]
163#[cfg(feature = "postgres")]
164mod tests_pg {
165 use crate::connection::CacheSize;
166 use crate::dsl::sql;
167 use crate::insertable::Insertable;
168 use crate::pg::Pg;
169 use crate::sql_types::{Integer, VarChar};
170 use crate::table;
171 use crate::test_helpers::pg_database_url;
172 use crate::{Connection, ExpressionMethods, IntoSql, PgConnection, QueryDsl, RunQueryDsl};
173
174 use super::testing_utils::{RecordCacheEvents, count_cache_calls};
175
176 #[crate::declare_sql_function]
177 extern "SQL" {
178 fn lower(x: VarChar) -> VarChar;
179 }
180
181 table! {
182 users {
183 id -> Integer,
184 name -> Text,
185 }
186 }
187
188 pub fn connection() -> PgConnection {
189 let mut conn = PgConnection::establish(&pg_database_url()).unwrap();
190 conn.set_instrumentation(RecordCacheEvents::default());
191 conn
192 }
193
194 #[diesel_test_helper::test]
195 fn prepared_statements_are_cached() {
196 let connection = &mut connection();
197
198 let query = crate::select(1.into_sql::<Integer>());
199
200 assert_eq!(Ok(1), query.get_result(connection));
201 assert_eq!(1, count_cache_calls(connection));
202 assert_eq!(Ok(1), query.get_result(connection));
203 assert_eq!(1, count_cache_calls(connection));
204 }
205
206 #[diesel_test_helper::test]
207 fn queries_with_identical_sql_but_different_types_are_cached_separately() {
208 let connection = &mut connection();
209
210 let query = crate::select(1.into_sql::<Integer>());
211 let query2 = crate::select("hi".into_sql::<VarChar>());
212
213 assert_eq!(Ok(1), query.get_result(connection));
214 assert_eq!(1, count_cache_calls(connection));
215 assert_eq!(Ok("hi".to_string()), query2.get_result(connection));
216 assert_eq!(2, count_cache_calls(connection));
217 }
218
219 #[diesel_test_helper::test]
220 fn queries_with_identical_types_and_sql_but_different_bind_types_are_cached_separately() {
221 let connection = &mut connection();
222
223 let query = crate::select(1.into_sql::<Integer>()).into_boxed::<Pg>();
224 let query2 = crate::select("hi".into_sql::<VarChar>()).into_boxed::<Pg>();
225
226 assert_eq!(Ok(1), query.get_result(connection));
227 assert_eq!(1, count_cache_calls(connection));
228 assert_eq!(Ok("hi".to_string()), query2.get_result(connection));
229 assert_eq!(2, count_cache_calls(connection));
230 }
231
232 #[diesel_test_helper::test]
233 fn queries_with_identical_types_and_binds_but_different_sql_are_cached_separately() {
234 let connection = &mut connection();
235
236 let hi = "HI".into_sql::<VarChar>();
237 let query = crate::select(hi).into_boxed::<Pg>();
238 let query2 = crate::select(lower(hi)).into_boxed::<Pg>();
239
240 assert_eq!(Ok("HI".to_string()), query.get_result(connection));
241 assert_eq!(1, count_cache_calls(connection));
242 assert_eq!(Ok("hi".to_string()), query2.get_result(connection));
243 assert_eq!(2, count_cache_calls(connection));
244 }
245
246 #[diesel_test_helper::test]
247 fn queries_with_sql_literal_nodes_are_not_cached() {
248 let connection = &mut connection();
249 let query = crate::select(sql::<Integer>("1"));
250
251 assert_eq!(Ok(1), query.get_result(connection));
252 assert_eq!(0, count_cache_calls(connection));
253 }
254
255 #[diesel_test_helper::test]
256 fn inserts_from_select_are_cached() {
257 let connection = &mut connection();
258 connection.begin_test_transaction().unwrap();
259
260 crate::sql_query(
261 "CREATE TEMPORARY TABLE users(id INTEGER PRIMARY KEY, name TEXT NOT NULL);",
262 )
263 .execute(connection)
264 .unwrap();
265
266 let query = users::table.filter(users::id.eq(42));
267 let insert = query
268 .insert_into(users::table)
269 .into_columns((users::id, users::name));
270 assert!(insert.execute(connection).is_ok());
271 assert_eq!(1, count_cache_calls(connection));
272
273 let query = users::table.filter(users::id.eq(42)).into_boxed();
274 let insert = query
275 .insert_into(users::table)
276 .into_columns((users::id, users::name));
277 assert!(insert.execute(connection).is_ok());
278 assert_eq!(2, count_cache_calls(connection));
279 }
280
281 #[diesel_test_helper::test]
282 fn single_inserts_are_cached() {
283 let connection = &mut connection();
284 connection.begin_test_transaction().unwrap();
285
286 crate::sql_query(
287 "CREATE TEMPORARY TABLE users(id INTEGER PRIMARY KEY, name TEXT NOT NULL);",
288 )
289 .execute(connection)
290 .unwrap();
291
292 let insert =
293 crate::insert_into(users::table).values((users::id.eq(42), users::name.eq("Foo")));
294
295 assert!(insert.execute(connection).is_ok());
296 assert_eq!(1, count_cache_calls(connection));
297 }
298
299 #[diesel_test_helper::test]
300 fn dynamic_batch_inserts_are_not_cached() {
301 let connection = &mut connection();
302 connection.begin_test_transaction().unwrap();
303
304 crate::sql_query(
305 "CREATE TEMPORARY TABLE users(id INTEGER PRIMARY KEY, name TEXT NOT NULL);",
306 )
307 .execute(connection)
308 .unwrap();
309
310 let insert = crate::insert_into(users::table)
311 .values(vec![(users::id.eq(42), users::name.eq("Foo"))]);
312
313 assert!(insert.execute(connection).is_ok());
314 assert_eq!(0, count_cache_calls(connection));
315 }
316
317 #[diesel_test_helper::test]
318 fn static_batch_inserts_are_cached() {
319 let connection = &mut connection();
320 connection.begin_test_transaction().unwrap();
321
322 crate::sql_query(
323 "CREATE TEMPORARY TABLE users(id INTEGER PRIMARY KEY, name TEXT NOT NULL);",
324 )
325 .execute(connection)
326 .unwrap();
327
328 let insert =
329 crate::insert_into(users::table).values([(users::id.eq(42), users::name.eq("Foo"))]);
330
331 assert!(insert.execute(connection).is_ok());
332 assert_eq!(1, count_cache_calls(connection));
333 }
334
335 #[diesel_test_helper::test]
336 fn queries_containing_in_with_vec_are_cached() {
337 let connection = &mut connection();
338 let one_as_expr = 1.into_sql::<Integer>();
339 let query = crate::select(one_as_expr.eq_any(vec![1, 2, 3]));
340
341 assert_eq!(Ok(true), query.get_result(connection));
342 assert_eq!(1, count_cache_calls(connection));
343 }
344
345 #[diesel_test_helper::test]
346 fn disabling_the_cache_works() {
347 let connection = &mut connection();
348 connection.set_prepared_statement_cache_size(CacheSize::Disabled);
349
350 let query = crate::select(1.into_sql::<Integer>());
351
352 assert_eq!(Ok(1), query.get_result(connection));
353 assert_eq!(0, count_cache_calls(connection));
354 assert_eq!(Ok(1), query.get_result(connection));
355 assert_eq!(0, count_cache_calls(connection));
356 }
357}
358
359#[cfg(test)]
360#[cfg(feature = "__sqlite-shared")]
361mod tests_sqlite {
362
363 use crate::connection::CacheSize;
364 use crate::dsl::sql;
365 use crate::query_dsl::RunQueryDsl;
366 use crate::sql_types::Integer;
367 use crate::{Connection, ExpressionMethods, IntoSql, SqliteConnection};
368
369 use super::testing_utils::{RecordCacheEvents, count_cache_calls};
370
371 pub fn connection() -> SqliteConnection {
372 let mut conn = SqliteConnection::establish(":memory:").unwrap();
373 conn.set_instrumentation(RecordCacheEvents::default());
374 conn
375 }
376
377 #[diesel_test_helper::test]
378 fn prepared_statements_are_cached_when_run() {
379 let connection = &mut connection();
380 let query = crate::select(1.into_sql::<Integer>());
381
382 assert_eq!(Ok(1), query.get_result(connection));
383 assert_eq!(1, count_cache_calls(connection));
384 assert_eq!(Ok(1), query.get_result(connection));
385 assert_eq!(1, count_cache_calls(connection));
386 }
387
388 #[diesel_test_helper::test]
389 fn sql_literal_nodes_are_not_cached() {
390 let connection = &mut connection();
391 let query = crate::select(sql::<Integer>("1"));
392
393 assert_eq!(Ok(1), query.get_result(connection));
394 assert_eq!(0, count_cache_calls(connection));
395 }
396
397 #[diesel_test_helper::test]
398 fn queries_containing_sql_literal_nodes_are_not_cached() {
399 let connection = &mut connection();
400 let one_as_expr = 1.into_sql::<Integer>();
401 let query = crate::select(one_as_expr.eq(sql::<Integer>("1")));
402
403 assert_eq!(Ok(true), query.get_result(connection));
404 assert_eq!(0, count_cache_calls(connection));
405 }
406
407 #[diesel_test_helper::test]
408 fn queries_containing_in_with_vec_are_not_cached() {
409 let connection = &mut connection();
410 let one_as_expr = 1.into_sql::<Integer>();
411 let query = crate::select(one_as_expr.eq_any(vec![1, 2, 3]));
412
413 assert_eq!(Ok(true), query.get_result(connection));
414 assert_eq!(0, count_cache_calls(connection));
415 }
416
417 #[diesel_test_helper::test]
418 fn queries_containing_in_with_subselect_are_cached() {
419 let connection = &mut connection();
420 let one_as_expr = 1.into_sql::<Integer>();
421 let query = crate::select(one_as_expr.eq_any(crate::select(one_as_expr)));
422
423 assert_eq!(Ok(true), query.get_result(connection));
424 assert_eq!(1, count_cache_calls(connection));
425 }
426
427 #[diesel_test_helper::test]
428 fn disabling_the_cache_works() {
429 let connection = &mut connection();
430 connection.set_prepared_statement_cache_size(CacheSize::Disabled);
431
432 let query = crate::select(1.into_sql::<Integer>());
433
434 assert_eq!(Ok(1), query.get_result(connection));
435 assert_eq!(0, count_cache_calls(connection));
436 assert_eq!(Ok(1), query.get_result(connection));
437 assert_eq!(0, count_cache_calls(connection));
438 }
439}