mirror of
https://github.com/isar/rusqlite.git
synced 2025-02-01 19:40:52 +08:00
Merge branch 'stmt-cache' of https://github.com/gwenn/rusqlite into gwenn-stmt-cache
This commit is contained in:
commit
a9a953e6b7
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user