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/// Indicates the cache key status
9//
10// This is a separate enum and not just `Option<Entry>`
11// as we need to return the cache key for owner ship reasons
12// if we don't have a cache at all
13#[cfg_attr(
14    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
15    allow(missing_debug_implementations)
16)]
17// cannot implement debug easily as StatementCacheKey is not Debug
18pub enum LookupStatementResult<'a, DB, Statement>
19where
20    DB: Backend,
21{
22    /// The cache entry, either already populated or vacant
23    /// in the later case the caller needs to prepare the
24    /// statement and insert it into the cache
25    CacheEntry(Entry<'a, StatementCacheKey<DB>, Statement>),
26    /// This key should not be cached
27    NoCache(StatementCacheKey<DB>),
28}
29
30/// Implement this trait, in order to control statement caching.
31#[allow(unreachable_pub)]
32pub trait StatementCacheStrategy<DB, Statement>: Send + 'static
33where
34    DB: Backend,
35    StatementCacheKey<DB>: Hash + Eq,
36{
37    /// Returns which prepared statement cache size is implemented by this trait
38    fn cache_size(&self) -> CacheSize;
39
40    /// Returns whether or not the corresponding cache key is already cached
41    fn lookup_statement(
42        &mut self,
43        key: StatementCacheKey<DB>,
44    ) -> LookupStatementResult<'_, DB, Statement>;
45}
46
47/// Cache all (safe) statements for as long as connection is alive.
48#[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/// No statements will be cached,
88#[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}