mirror of
				https://github.com/isar/rusqlite.git
				synced 2025-10-31 13:58:55 +08:00 
			
		
		
		
	Merge branch 'stmt-cache' of https://github.com/gwenn/rusqlite into gwenn-stmt-cache
This commit is contained in:
		| @@ -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" | ||||
|   | ||||
| @@ -16,6 +16,7 @@ name = "rusqlite" | ||||
| load_extension = ["libsqlite3-sys/load_extension"] | ||||
| backup = [] | ||||
| blob = [] | ||||
| cache = [] | ||||
| functions = [] | ||||
| trace = [] | ||||
|  | ||||
|   | ||||
							
								
								
									
										23
									
								
								benches/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								benches/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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()); | ||||
| } | ||||
| @@ -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 '<meta http-equiv=refresh content=0;url=rusqlite/index.html>' > target/doc/index.html | ||||
| ghp-import target/doc | ||||
| git push origin gh-pages:gh-pages | ||||
|   | ||||
							
								
								
									
										168
									
								
								src/cache.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								src/cache.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<VecDeque<Statement<'conn>>>, // 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<Statement<'c>>, | ||||
|     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<CachedStatement<'conn, 's>> { | ||||
|         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::<i64>(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::<i64>(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::<i64>(0)); | ||||
|             stmt.cacheable = false; | ||||
|         } | ||||
|         assert_eq!(0, cache.len()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								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(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user