Skip to main content

diesel/connection/statement_cache/
strategy.rs

1use 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/// 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    /// Removes all cached statements so that subsequent queries are re-prepared.
47    // Only reached through `StatementCache::clear`, which is currently used only
48    // by the SQLite authorizer, so it is dead code for backends compiled without
49    // that caller.
50    #[cfg(feature = "__sqlite-shared")]
51    fn clear(&mut self);
52}
53
54/// Cache all (safe) statements for as long as connection is alive.
55#[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/// No statements will be cached,
100#[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}