diff --git a/.travis.yml b/.travis.yml
index ba5124f..50e7585 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,4 +12,5 @@ script:
- cargo test --features load_extension
- cargo test --features trace
- cargo test --features functions
- - cargo test --features "backup functions load_extension trace"
+ - cargo test --features cache
+ - cargo test --features "backup cache functions load_extension trace"
diff --git a/Cargo.toml b/Cargo.toml
index 3555180..ddf89d2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,7 @@ name = "rusqlite"
load_extension = ["libsqlite3-sys/load_extension"]
backup = []
blob = []
+cache = []
functions = []
trace = []
diff --git a/benches/lib.rs b/benches/lib.rs
new file mode 100644
index 0000000..92fddef
--- /dev/null
+++ b/benches/lib.rs
@@ -0,0 +1,23 @@
+#![feature(test)]
+extern crate test;
+
+extern crate rusqlite;
+
+use rusqlite::Connection;
+use rusqlite::cache::StatementCache;
+use test::Bencher;
+
+#[bench]
+fn bench_no_cache(b: &mut Bencher) {
+ let db = Connection::open_in_memory().unwrap();
+ let sql = "SELECT 1, 'test', 3.14 UNION SELECT 2, 'exp', 2.71";
+ b.iter(|| db.prepare(sql).unwrap());
+}
+
+#[bench]
+fn bench_cache(b: &mut Bencher) {
+ let db = Connection::open_in_memory().unwrap();
+ let cache = StatementCache::new(&db, 15);
+ let sql = "SELECT 1, 'test', 3.14 UNION SELECT 2, 'exp', 2.71";
+ b.iter(|| cache.get(sql).unwrap());
+}
diff --git a/publish-ghp-docs.sh b/publish-ghp-docs.sh
index f9eeb17..de31a13 100755
--- a/publish-ghp-docs.sh
+++ b/publish-ghp-docs.sh
@@ -8,7 +8,7 @@ fi
cd $(git rev-parse --show-toplevel)
rm -rf target/doc/
-multirust run nightly cargo doc --no-deps --features "load_extension trace"
+multirust run nightly cargo doc --no-deps --features "backup cache functions load_extension trace"
echo '' > target/doc/index.html
ghp-import target/doc
git push origin gh-pages:gh-pages
diff --git a/src/cache.rs b/src/cache.rs
new file mode 100644
index 0000000..a0cce77
--- /dev/null
+++ b/src/cache.rs
@@ -0,0 +1,168 @@
+//! Prepared statements cache for faster execution.
+
+use std::cell::RefCell;
+use std::collections::VecDeque;
+use std::ops::{Deref, DerefMut};
+use {Result, Connection, Statement};
+
+/// Prepared statements LRU cache.
+#[derive(Debug)]
+pub struct StatementCache<'conn> {
+ conn: &'conn Connection,
+ cache: RefCell>>, // back = LRU
+}
+
+/// Cacheable statement.
+///
+/// Statement will return automatically to the cache by default.
+/// If you want the statement to be discarded, you can set the `cacheable` flag to `false`.
+pub struct CachedStatement<'c: 's, 's> {
+ stmt: Option>,
+ cache: &'s StatementCache<'c>,
+ pub cacheable: bool,
+}
+
+impl<'c, 's> Deref for CachedStatement<'c, 's> {
+ type Target = Statement<'c>;
+
+ fn deref(&self) -> &Statement<'c> {
+ self.stmt.as_ref().unwrap()
+ }
+}
+
+impl<'c, 's> DerefMut for CachedStatement<'c, 's> {
+ fn deref_mut(&mut self) -> &mut Statement<'c> {
+ self.stmt.as_mut().unwrap()
+ }
+}
+
+impl<'c, 's> Drop for CachedStatement<'c, 's> {
+ #[allow(unused_must_use)]
+ fn drop(&mut self) {
+ if self.cacheable {
+ self.cache.release(self.stmt.take().unwrap());
+ } else {
+ self.stmt.take().unwrap().finalize();
+ }
+ }
+}
+
+impl<'c, 's> CachedStatement<'c, 's> {
+ fn new(stmt: Statement<'c>, cache: &'s StatementCache<'c>) -> CachedStatement<'c, 's> {
+ CachedStatement {
+ stmt: Some(stmt),
+ cache: cache,
+ cacheable: true,
+ }
+ }
+}
+
+impl<'conn> StatementCache<'conn> {
+ /// Create a statement cache.
+ pub fn new(conn: &'conn Connection, capacity: usize) -> StatementCache<'conn> {
+ StatementCache {
+ conn: conn,
+ cache: RefCell::new(VecDeque::with_capacity(capacity)),
+ }
+ }
+
+ /// Search the cache for a prepared-statement object that implements `sql`.
+ // If no such prepared-statement can be found, allocate and prepare a new one.
+ ///
+ /// # Failure
+ ///
+ /// Will return `Err` if no cached statement can be found and the underlying SQLite prepare call fails.
+ pub fn get<'s>(&'s self, sql: &str) -> Result> {
+ let mut cache = self.cache.borrow_mut();
+ let stmt = match cache.iter().rposition(|entry| entry.eq(sql)) {
+ Some(index) => Ok(cache.swap_remove_front(index).unwrap()), // FIXME Not LRU compliant
+ _ => self.conn.prepare(sql),
+ };
+ stmt.map(|stmt| CachedStatement::new(stmt, self))
+ }
+
+ /// If `discard` is true, then the statement is deleted immediately.
+ /// Otherwise it is added to the LRU list and may be returned
+ /// by a subsequent call to `get()`.
+ ///
+ /// # Failure
+ ///
+ /// Will return `Err` if `stmt` (or the already cached statement implementing the same SQL) statement is `discard`ed
+ /// and the underlying SQLite finalize call fails.
+ fn release(&self, mut stmt: Statement<'conn>) {
+ let mut cache = self.cache.borrow_mut();
+ if cache.capacity() == cache.len() {
+ // is full
+ cache.pop_back(); // LRU dropped
+ }
+ stmt.reset_if_needed();
+ stmt.clear_bindings();
+ cache.push_front(stmt)
+ }
+
+ /// Flush the prepared statement cache
+ pub fn clear(&self) {
+ self.cache.borrow_mut().clear();
+ }
+
+ /// Return current cache size.
+ pub fn len(&self) -> usize {
+ self.cache.borrow().len()
+ }
+
+ /// Return maximum cache size.
+ pub fn capacity(&self) -> usize {
+ self.cache.borrow().capacity()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use Connection;
+ use super::StatementCache;
+
+ #[test]
+ fn test_cache() {
+ let db = Connection::open_in_memory().unwrap();
+ let cache = StatementCache::new(&db, 15);
+ assert_eq!(0, cache.len());
+ assert_eq!(15, cache.capacity());
+
+ let sql = "PRAGMA schema_version";
+ {
+ let mut stmt = cache.get(sql).unwrap();
+ assert_eq!(0, cache.len());
+ assert_eq!(0,
+ stmt.query(&[]).unwrap().get_expected_row().unwrap().get::(0));
+ }
+ assert_eq!(1, cache.len());
+
+ {
+ let mut stmt = cache.get(sql).unwrap();
+ assert_eq!(0, cache.len());
+ assert_eq!(0,
+ stmt.query(&[]).unwrap().get_expected_row().unwrap().get::(0));
+ }
+ assert_eq!(1, cache.len());
+
+ cache.clear();
+ assert_eq!(0, cache.len());
+ assert_eq!(15, cache.capacity());
+ }
+
+ #[test]
+ fn test_cacheable() {
+ let db = Connection::open_in_memory().unwrap();
+ let cache = StatementCache::new(&db, 15);
+
+ let sql = "PRAGMA schema_version";
+ {
+ let mut stmt = cache.get(sql).unwrap();
+ assert_eq!(0, cache.len());
+ assert_eq!(0,
+ stmt.query(&[]).unwrap().get_expected_row().unwrap().get::(0));
+ stmt.cacheable = false;
+ }
+ assert_eq!(0, cache.len());
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index d4b445e..ecbdfa0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -87,6 +87,7 @@ mod error;
#[cfg(feature = "load_extension")]mod load_extension_guard;
#[cfg(feature = "trace")]pub mod trace;
#[cfg(feature = "backup")]pub mod backup;
+#[cfg(feature = "cache")] pub mod cache;
#[cfg(feature = "functions")] pub mod functions;
#[cfg(feature = "blob")] pub mod blob;
@@ -912,6 +913,21 @@ impl<'conn> Statement<'conn> {
}
}
+ #[cfg(feature = "cache")]
+ fn clear_bindings(&mut self) {
+ unsafe {
+ ffi::sqlite3_clear_bindings(self.stmt);
+ };
+ }
+
+ #[cfg(feature = "cache")]
+ fn eq(&self, sql: &str) -> bool {
+ unsafe {
+ let c_slice = CStr::from_ptr(ffi::sqlite3_sql(self.stmt)).to_bytes();
+ str::from_utf8(c_slice).unwrap().eq(sql)
+ }
+ }
+
fn finalize_(&mut self) -> Result<()> {
let r = unsafe { ffi::sqlite3_finalize(self.stmt) };
self.stmt = ptr::null_mut();