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