Skip to content

Commit 80f6f90

Browse files
authored
impl cache for prepared_stmt (#67)
Change-Id: I9276afeb167e252ad1ad083c6fa20faa22ed4579
1 parent 213b1a5 commit 80f6f90

File tree

4 files changed

+406
-1
lines changed

4 files changed

+406
-1
lines changed

src/cache.rs

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
//! Prepared statements cache for faster execution.
2+
3+
use crate::raw_statement::RawStatement;
4+
use crate::{Connection, Result, Statement};
5+
use hashlink::LruCache;
6+
use std::cell::RefCell;
7+
use std::ops::{Deref, DerefMut};
8+
use std::sync::Arc;
9+
10+
impl Connection {
11+
/// Prepare a SQL statement for execution, returning a previously prepared
12+
/// (but not currently in-use) statement if one is available. The
13+
/// returned statement will be cached for reuse by future calls to
14+
/// [`prepare_cached`](Connection::prepare_cached) once it is dropped.
15+
///
16+
/// ```rust,no_run
17+
/// # use duckdb::{Connection, Result};
18+
/// fn insert_new_people(conn: &Connection) -> Result<()> {
19+
/// {
20+
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
21+
/// stmt.execute(["Joe Smith"])?;
22+
/// }
23+
/// {
24+
/// // This will return the same underlying SQLite statement handle without
25+
/// // having to prepare it again.
26+
/// let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?)")?;
27+
/// stmt.execute(["Bob Jones"])?;
28+
/// }
29+
/// Ok(())
30+
/// }
31+
/// ```
32+
///
33+
/// # Failure
34+
///
35+
/// Will return `Err` if `sql` cannot be converted to a C-compatible string
36+
/// or if the underlying SQLite call fails.
37+
#[inline]
38+
pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>> {
39+
self.cache.get(self, sql)
40+
}
41+
42+
/// Set the maximum number of cached prepared statements this connection
43+
/// will hold. By default, a connection will hold a relatively small
44+
/// number of cached statements. If you need more, or know that you
45+
/// will not use cached statements, you
46+
/// can set the capacity manually using this method.
47+
#[inline]
48+
pub fn set_prepared_statement_cache_capacity(&self, capacity: usize) {
49+
self.cache.set_capacity(capacity);
50+
}
51+
52+
/// Remove/finalize all prepared statements currently in the cache.
53+
#[inline]
54+
pub fn flush_prepared_statement_cache(&self) {
55+
self.cache.flush();
56+
}
57+
}
58+
59+
/// Prepared statements LRU cache.
60+
#[derive(Debug)]
61+
pub struct StatementCache(RefCell<LruCache<Arc<str>, RawStatement>>);
62+
63+
#[allow(clippy::non_send_fields_in_send_ty)]
64+
unsafe impl Send for StatementCache {}
65+
66+
/// Cacheable statement.
67+
///
68+
/// Statement will return automatically to the cache by default.
69+
/// If you want the statement to be discarded, call
70+
/// [`discard()`](CachedStatement::discard) on it.
71+
pub struct CachedStatement<'conn> {
72+
stmt: Option<Statement<'conn>>,
73+
cache: &'conn StatementCache,
74+
}
75+
76+
impl<'conn> Deref for CachedStatement<'conn> {
77+
type Target = Statement<'conn>;
78+
79+
#[inline]
80+
fn deref(&self) -> &Statement<'conn> {
81+
self.stmt.as_ref().unwrap()
82+
}
83+
}
84+
85+
impl<'conn> DerefMut for CachedStatement<'conn> {
86+
#[inline]
87+
fn deref_mut(&mut self) -> &mut Statement<'conn> {
88+
self.stmt.as_mut().unwrap()
89+
}
90+
}
91+
92+
impl Drop for CachedStatement<'_> {
93+
#[allow(unused_must_use)]
94+
#[inline]
95+
fn drop(&mut self) {
96+
if let Some(stmt) = self.stmt.take() {
97+
self.cache.cache_stmt(unsafe { stmt.into_raw() });
98+
}
99+
}
100+
}
101+
102+
impl CachedStatement<'_> {
103+
#[inline]
104+
fn new<'conn>(stmt: Statement<'conn>, cache: &'conn StatementCache) -> CachedStatement<'conn> {
105+
CachedStatement {
106+
stmt: Some(stmt),
107+
cache,
108+
}
109+
}
110+
111+
/// Discard the statement, preventing it from being returned to its
112+
/// [`Connection`]'s collection of cached statements.
113+
#[inline]
114+
pub fn discard(mut self) {
115+
self.stmt = None;
116+
}
117+
}
118+
119+
impl StatementCache {
120+
/// Create a statement cache.
121+
#[inline]
122+
pub fn with_capacity(capacity: usize) -> StatementCache {
123+
StatementCache(RefCell::new(LruCache::new(capacity)))
124+
}
125+
126+
#[inline]
127+
fn set_capacity(&self, capacity: usize) {
128+
self.0.borrow_mut().set_capacity(capacity);
129+
}
130+
131+
// Search the cache for a prepared-statement object that implements `sql`.
132+
// If no such prepared-statement can be found, allocate and prepare a new one.
133+
//
134+
// # Failure
135+
//
136+
// Will return `Err` if no cached statement can be found and the underlying
137+
// SQLite prepare call fails.
138+
fn get<'conn>(&'conn self, conn: &'conn Connection, sql: &str) -> Result<CachedStatement<'conn>> {
139+
let trimmed = sql.trim();
140+
let mut cache = self.0.borrow_mut();
141+
let stmt = match cache.remove(trimmed) {
142+
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
143+
None => conn.prepare(trimmed),
144+
};
145+
stmt.map(|mut stmt| {
146+
stmt.stmt.set_statement_cache_key(trimmed);
147+
CachedStatement::new(stmt, self)
148+
})
149+
}
150+
151+
// Return a statement to the cache.
152+
fn cache_stmt(&self, stmt: RawStatement) {
153+
if stmt.is_null() {
154+
return;
155+
}
156+
let mut cache = self.0.borrow_mut();
157+
stmt.clear_bindings();
158+
if let Some(sql) = stmt.statement_cache_key() {
159+
cache.insert(sql, stmt);
160+
} else {
161+
debug_assert!(
162+
false,
163+
"bug in statement cache code, statement returned to cache that without key"
164+
);
165+
}
166+
}
167+
168+
#[inline]
169+
fn flush(&self) {
170+
let mut cache = self.0.borrow_mut();
171+
cache.clear();
172+
}
173+
}
174+
175+
#[cfg(test)]
176+
mod test {
177+
use super::StatementCache;
178+
use crate::{Connection, Result};
179+
use fallible_iterator::FallibleIterator;
180+
181+
impl StatementCache {
182+
fn clear(&self) {
183+
self.0.borrow_mut().clear();
184+
}
185+
186+
fn len(&self) -> usize {
187+
self.0.borrow().len()
188+
}
189+
190+
fn capacity(&self) -> usize {
191+
self.0.borrow().capacity()
192+
}
193+
}
194+
195+
#[test]
196+
fn test_cache() -> Result<()> {
197+
let db = Connection::open_in_memory()?;
198+
let cache = &db.cache;
199+
let initial_capacity = cache.capacity();
200+
assert_eq!(0, cache.len());
201+
assert!(initial_capacity > 0);
202+
203+
let sql = "PRAGMA database_list";
204+
{
205+
let mut stmt = db.prepare_cached(sql)?;
206+
assert_eq!(0, cache.len());
207+
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
208+
}
209+
assert_eq!(1, cache.len());
210+
211+
{
212+
let mut stmt = db.prepare_cached(sql)?;
213+
assert_eq!(0, cache.len());
214+
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
215+
}
216+
assert_eq!(1, cache.len());
217+
218+
cache.clear();
219+
assert_eq!(0, cache.len());
220+
assert_eq!(initial_capacity, cache.capacity());
221+
Ok(())
222+
}
223+
224+
#[test]
225+
fn test_set_capacity() -> Result<()> {
226+
let db = Connection::open_in_memory()?;
227+
let cache = &db.cache;
228+
229+
let sql = "PRAGMA database_list";
230+
{
231+
let mut stmt = db.prepare_cached(sql)?;
232+
assert_eq!(0, cache.len());
233+
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
234+
}
235+
assert_eq!(1, cache.len());
236+
237+
db.set_prepared_statement_cache_capacity(0);
238+
assert_eq!(0, cache.len());
239+
240+
{
241+
let mut stmt = db.prepare_cached(sql)?;
242+
assert_eq!(0, cache.len());
243+
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
244+
}
245+
assert_eq!(0, cache.len());
246+
247+
db.set_prepared_statement_cache_capacity(8);
248+
{
249+
let mut stmt = db.prepare_cached(sql)?;
250+
assert_eq!(0, cache.len());
251+
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
252+
}
253+
assert_eq!(1, cache.len());
254+
Ok(())
255+
}
256+
257+
#[test]
258+
fn test_discard() -> Result<()> {
259+
let db = Connection::open_in_memory()?;
260+
let cache = &db.cache;
261+
262+
let sql = "PRAGMA database_list";
263+
{
264+
let mut stmt = db.prepare_cached(sql)?;
265+
assert_eq!(0, cache.len());
266+
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
267+
stmt.discard();
268+
}
269+
assert_eq!(0, cache.len());
270+
Ok(())
271+
}
272+
273+
#[test]
274+
fn test_ddl() -> Result<()> {
275+
let db = Connection::open_in_memory()?;
276+
db.execute_batch(
277+
r#"
278+
CREATE TABLE foo (x INT);
279+
INSERT INTO foo VALUES (1);
280+
"#,
281+
)?;
282+
283+
let sql = "SELECT x FROM foo";
284+
285+
{
286+
let mut stmt = db.prepare_cached(sql)?;
287+
assert_eq!(Ok(Some(1i32)), stmt.query([])?.map(|r| r.get(0)).next());
288+
}
289+
290+
let sql_cannot_cache = "SELECT * FROM foo";
291+
292+
{
293+
let mut stmt = db.prepare_cached(sql_cannot_cache)?;
294+
assert_eq!(Ok(Some(1i32)), stmt.query([])?.map(|r| r.get(0)).next());
295+
}
296+
297+
db.execute_batch(
298+
r#"
299+
ALTER TABLE foo ADD COLUMN y INT;
300+
UPDATE foo SET y = 2;
301+
"#,
302+
)?;
303+
304+
{
305+
let mut stmt = db.prepare_cached(sql)?;
306+
assert_eq!(Ok(Some(1i32)), stmt.query([])?.map(|r| r.get(0)).next());
307+
}
308+
309+
{
310+
// Rebinding statement after catalog change resulted in change of types
311+
let mut stmt = db.prepare_cached(sql_cannot_cache)?;
312+
let result = stmt.query([]);
313+
assert!(result.is_err());
314+
}
315+
Ok(())
316+
}
317+
318+
#[test]
319+
fn test_connection_close() -> Result<()> {
320+
let conn = Connection::open_in_memory()?;
321+
conn.prepare_cached("SELECT * FROM sqlite_master;")?;
322+
323+
conn.close().expect("connection not closed");
324+
Ok(())
325+
}
326+
327+
#[test]
328+
fn test_cache_key() -> Result<()> {
329+
let db = Connection::open_in_memory()?;
330+
let cache = &db.cache;
331+
assert_eq!(0, cache.len());
332+
333+
let sql = "PRAGMA database_list; ";
334+
{
335+
let mut stmt = db.prepare_cached(sql)?;
336+
assert_eq!(0, cache.len());
337+
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
338+
}
339+
assert_eq!(1, cache.len());
340+
341+
{
342+
let mut stmt = db.prepare_cached(sql)?;
343+
assert_eq!(0, cache.len());
344+
assert_eq!(0, stmt.query_row([], |r| r.get::<_, i64>(0))?);
345+
}
346+
assert_eq!(1, cache.len());
347+
Ok(())
348+
}
349+
350+
#[test]
351+
fn test_cannot_prepare_empty_stmt() -> Result<()> {
352+
let conn = Connection::open_in_memory()?;
353+
let result = conn.prepare_cached("");
354+
assert!(result.is_err());
355+
Ok(())
356+
}
357+
}

0 commit comments

Comments
 (0)