Merge branch 'master' of https://github.com/jgallagher/rusqlite into stmt-cache

This commit is contained in:
Gwenael Treguier 2015-12-05 11:58:06 +01:00
commit 18ff9cf4ca
9 changed files with 328 additions and 29 deletions

View File

@ -8,3 +8,5 @@ rusqlite contributors (sorted alphabetically)
* [Huon Wilson](https://github.com/huonw) * [Huon Wilson](https://github.com/huonw)
* [Patrick Fernie](https://github.com/pfernie) * [Patrick Fernie](https://github.com/pfernie)
* [Steve Klabnik](https://github.com/steveklabnik) * [Steve Klabnik](https://github.com/steveklabnik)
* [krdln](https://github.com/krdln)
* [Ben Striegel](https://github.com/bstrie)

View File

@ -15,14 +15,16 @@ name = "rusqlite"
[features] [features]
load_extension = ["libsqlite3-sys/load_extension"] load_extension = ["libsqlite3-sys/load_extension"]
cache = ["lru-cache"] cache = ["lru-cache"]
trace = []
[dependencies] [dependencies]
time = "~0.1.0" time = "~0.1.0"
bitflags = "~0.1" bitflags = "~0.1"
libc = "~0.1" libc = "~0.2"
[dev-dependencies] [dev-dependencies]
tempdir = "~0.3.4" tempdir = "~0.3.4"
lazy_static = "~0.1"
[dependencies.libsqlite3-sys] [dependencies.libsqlite3-sys]
path = "libsqlite3-sys" path = "libsqlite3-sys"
@ -30,4 +32,8 @@ version = "0.2.0"
[dependencies.lru-cache] [dependencies.lru-cache]
version = "~0.0.4" version = "~0.0.4"
optional = true optional = true
[[test]]
name = "config_log"
harness = false

View File

@ -1,3 +1,11 @@
# Version UPCOMING (TBD)
* Adds `trace` feature that allows the use of SQLite's logging, tracing, and profiling hooks.
* Slight change to the closure types passed to `query_map` and `query_and_then`:
* Remove the `'static` requirement on the closure's output type.
* Give the closure a `&SqliteRow` instead of a `SqliteRow`.
* Add more documentation for failure modes of functions that return `SqliteResult`s.
# Version 0.4.0 (2015-11-03) # Version 0.4.0 (2015-11-03)
* Adds `Sized` bound to `FromSql` trait as required by RFC 1214. * Adds `Sized` bound to `FromSql` trait as required by RFC 1214.

View File

@ -91,7 +91,7 @@ There are other, less obvious things that may result in a panic as well, such as
`collect()` on a `SqliteRows` and then trying to use the collected rows. `collect()` on a `SqliteRows` and then trying to use the collected rows.
Strongly consider using the method `query_map()` instead, if you can. Strongly consider using the method `query_map()` instead, if you can.
`query_map()` returns an iterator over rows-mapped-to-some-`'static`-type. This `query_map()` returns an iterator over rows-mapped-to-some-type. This
iterator does not have any of the above issues with panics due to attempting to iterator does not have any of the above issues with panics due to attempting to
access stale rows. access stale rows.

View File

@ -15,4 +15,4 @@ load_extension = []
pkg-config = "~0.3" pkg-config = "~0.3"
[dependencies] [dependencies]
libc = "~0.1" libc = "~0.2"

View File

@ -92,3 +92,5 @@ pub fn code_to_str(code: c_int) -> &'static str {
_ => "Unknown error code", _ => "Unknown error code",
} }
} }
pub const SQLITE_CONFIG_LOG : c_int = 16;

View File

@ -53,6 +53,7 @@
extern crate libc; extern crate libc;
extern crate libsqlite3_sys as ffi; extern crate libsqlite3_sys as ffi;
#[macro_use] extern crate bitflags; #[macro_use] extern crate bitflags;
#[cfg(test)] #[macro_use] extern crate lazy_static;
use std::default::Default; use std::default::Default;
use std::convert; use std::convert;
@ -81,6 +82,7 @@ pub mod types;
mod transaction; mod transaction;
#[cfg(feature = "load_extension")] mod load_extension_guard; #[cfg(feature = "load_extension")] mod load_extension_guard;
#[cfg(feature = "cache")] pub mod cache; #[cfg(feature = "cache")] pub mod cache;
#[cfg(feature = "trace")] pub mod trace;
/// A typedef of the result returned by many methods. /// A typedef of the result returned by many methods.
pub type SqliteResult<T> = Result<T, SqliteError>; pub type SqliteResult<T> = Result<T, SqliteError>;
@ -129,28 +131,19 @@ impl SqliteError {
fn str_to_cstring(s: &str) -> SqliteResult<CString> { fn str_to_cstring(s: &str) -> SqliteResult<CString> {
CString::new(s).map_err(|_| SqliteError{ CString::new(s).map_err(|_| SqliteError{
code: ffi::SQLITE_MISUSE, code: ffi::SQLITE_MISUSE,
message: "Could not convert path to C-combatible string".to_string() message: format!("Could not convert string {} to C-combatible string", s),
}) })
} }
fn path_to_cstring(p: &Path) -> SqliteResult<CString> { fn path_to_cstring(p: &Path) -> SqliteResult<CString> {
let s = try!(p.to_str().ok_or(SqliteError{ let s = try!(p.to_str().ok_or(SqliteError{
code: ffi::SQLITE_MISUSE, code: ffi::SQLITE_MISUSE,
message: "Could not convert path to UTF-8 string".to_string() message: format!("Could not convert path {} to UTF-8 string", p.to_string_lossy()),
})); }));
str_to_cstring(s) str_to_cstring(s)
} }
/// A connection to a SQLite database. /// A connection to a SQLite database.
///
/// ## Warning
///
/// Note that despite the fact that most `SqliteConnection` methods take an immutable reference to
/// `self`, `SqliteConnection` is NOT threadsafe, and using it from multiple threads may result in
/// runtime panics or data races. The SQLite connection handle has at least two pieces of internal
/// state (the last insertion ID and the last error message) that rusqlite uses, but wrapping these
/// APIs in a safe way from Rust would be too restrictive (for example, you would not be able to
/// prepare multiple statements at the same time).
pub struct SqliteConnection { pub struct SqliteConnection {
db: RefCell<InnerSqliteConnection>, db: RefCell<InnerSqliteConnection>,
path: Option<PathBuf>, path: Option<PathBuf>,
@ -163,12 +156,21 @@ impl SqliteConnection {
/// ///
/// `SqliteConnection::open(path)` is equivalent to `SqliteConnection::open_with_flags(path, /// `SqliteConnection::open(path)` is equivalent to `SqliteConnection::open_with_flags(path,
/// SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE)`. /// SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE)`.
///
/// # Failure
///
/// Will return `Err` if `path` cannot be converted to a C-compatible string or if the
/// underlying SQLite open call fails.
pub fn open<P: AsRef<Path>>(path: P) -> SqliteResult<SqliteConnection> { pub fn open<P: AsRef<Path>>(path: P) -> SqliteResult<SqliteConnection> {
let flags = Default::default(); let flags = Default::default();
SqliteConnection::open_with_flags(path, flags) SqliteConnection::open_with_flags(path, flags)
} }
/// Open a new connection to an in-memory SQLite database. /// Open a new connection to an in-memory SQLite database.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite open call fails.
pub fn open_in_memory() -> SqliteResult<SqliteConnection> { pub fn open_in_memory() -> SqliteResult<SqliteConnection> {
let flags = Default::default(); let flags = Default::default();
SqliteConnection::open_in_memory_with_flags(flags) SqliteConnection::open_in_memory_with_flags(flags)
@ -178,6 +180,11 @@ impl SqliteConnection {
/// ///
/// Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid /// Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid
/// flag combinations. /// flag combinations.
///
/// # Failure
///
/// Will return `Err` if `path` cannot be converted to a C-compatible string or if the
/// underlying SQLite open call fails.
pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: SqliteOpenFlags) pub fn open_with_flags<P: AsRef<Path>>(path: P, flags: SqliteOpenFlags)
-> SqliteResult<SqliteConnection> { -> SqliteResult<SqliteConnection> {
let c_path = try!(path_to_cstring(path.as_ref())); let c_path = try!(path_to_cstring(path.as_ref()));
@ -190,6 +197,10 @@ impl SqliteConnection {
/// ///
/// Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid /// Database Connection](http://www.sqlite.org/c3ref/open.html) for a description of valid
/// flag combinations. /// flag combinations.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite open call fails.
pub fn open_in_memory_with_flags(flags: SqliteOpenFlags) -> SqliteResult<SqliteConnection> { pub fn open_in_memory_with_flags(flags: SqliteOpenFlags) -> SqliteResult<SqliteConnection> {
let c_memory = try!(str_to_cstring(":memory:")); let c_memory = try!(str_to_cstring(":memory:"));
InnerSqliteConnection::open_with_flags(&c_memory, flags).map(|db| { InnerSqliteConnection::open_with_flags(&c_memory, flags).map(|db| {
@ -217,6 +228,10 @@ impl SqliteConnection {
/// tx.commit() /// tx.commit()
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn transaction<'a>(&'a self) -> SqliteResult<SqliteTransaction<'a>> { pub fn transaction<'a>(&'a self) -> SqliteResult<SqliteTransaction<'a>> {
SqliteTransaction::new(self, SqliteTransactionDeferred) SqliteTransaction::new(self, SqliteTransactionDeferred)
} }
@ -224,6 +239,10 @@ impl SqliteConnection {
/// Begin a new transaction with a specified behavior. /// Begin a new transaction with a specified behavior.
/// ///
/// See `transaction`. /// See `transaction`.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn transaction_with_behavior<'a>(&'a self, behavior: SqliteTransactionBehavior) pub fn transaction_with_behavior<'a>(&'a self, behavior: SqliteTransactionBehavior)
-> SqliteResult<SqliteTransaction<'a>> { -> SqliteResult<SqliteTransaction<'a>> {
SqliteTransaction::new(self, behavior) SqliteTransaction::new(self, behavior)
@ -244,6 +263,11 @@ impl SqliteConnection {
/// COMMIT;") /// COMMIT;")
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn execute_batch(&self, sql: &str) -> SqliteResult<()> { pub fn execute_batch(&self, sql: &str) -> SqliteResult<()> {
self.db.borrow_mut().execute_batch(sql) self.db.borrow_mut().execute_batch(sql)
} }
@ -264,6 +288,11 @@ impl SqliteConnection {
/// } /// }
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn execute(&self, sql: &str, params: &[&ToSql]) -> SqliteResult<c_int> { pub fn execute(&self, sql: &str, params: &[&ToSql]) -> SqliteResult<c_int> {
self.prepare(sql).and_then(|mut stmt| stmt.execute(params)) self.prepare(sql).and_then(|mut stmt| stmt.execute(params))
} }
@ -290,6 +319,11 @@ impl SqliteConnection {
/// ``` /// ```
/// ///
/// If the query returns more than one row, all rows except the first are ignored. /// If the query returns more than one row, all rows except the first are ignored.
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn query_row<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> SqliteResult<T> pub fn query_row<T, F>(&self, sql: &str, params: &[&ToSql], f: F) -> SqliteResult<T>
where F: FnOnce(SqliteRow) -> T { where F: FnOnce(SqliteRow) -> T {
let mut stmt = try!(self.prepare(sql)); let mut stmt = try!(self.prepare(sql));
@ -320,6 +354,11 @@ impl SqliteConnection {
/// ``` /// ```
/// ///
/// If the query returns more than one row, all rows except the first are ignored. /// If the query returns more than one row, all rows except the first are ignored.
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn query_row_and_then<T, E, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T, E> pub fn query_row_and_then<T, E, F>(&self, sql: &str, params: &[&ToSql], f: F) -> Result<T, E>
where F: FnOnce(SqliteRow) -> Result<T, E>, where F: FnOnce(SqliteRow) -> Result<T, E>,
E: convert::From<SqliteError> { E: convert::From<SqliteError> {
@ -372,6 +411,11 @@ impl SqliteConnection {
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if `sql` cannot be converted to a C-compatible string or if the
/// underlying SQLite call fails.
pub fn prepare<'a>(&'a self, sql: &str) -> SqliteResult<SqliteStatement<'a>> { pub fn prepare<'a>(&'a self, sql: &str) -> SqliteResult<SqliteStatement<'a>> {
self.db.borrow_mut().prepare(self, sql) self.db.borrow_mut().prepare(self, sql)
} }
@ -380,6 +424,10 @@ impl SqliteConnection {
/// ///
/// This is functionally equivalent to the `Drop` implementation for `SqliteConnection` except /// This is functionally equivalent to the `Drop` implementation for `SqliteConnection` except
/// that it returns any error encountered to the caller. /// that it returns any error encountered to the caller.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn close(self) -> SqliteResult<()> { pub fn close(self) -> SqliteResult<()> {
let mut db = self.db.borrow_mut(); let mut db = self.db.borrow_mut();
db.close() db.close()
@ -399,6 +447,10 @@ impl SqliteConnection {
/// conn.load_extension_disable() /// conn.load_extension_disable()
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
pub fn load_extension_enable(&self) -> SqliteResult<()> { pub fn load_extension_enable(&self) -> SqliteResult<()> {
self.db.borrow_mut().enable_load_extension(1) self.db.borrow_mut().enable_load_extension(1)
@ -407,6 +459,10 @@ impl SqliteConnection {
/// Disable loading of SQLite extensions. /// Disable loading of SQLite extensions.
/// ///
/// See `load_extension_enable` for an example. /// See `load_extension_enable` for an example.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
pub fn load_extension_disable(&self) -> SqliteResult<()> { pub fn load_extension_disable(&self) -> SqliteResult<()> {
self.db.borrow_mut().enable_load_extension(0) self.db.borrow_mut().enable_load_extension(0)
@ -429,6 +485,10 @@ impl SqliteConnection {
/// ///
/// conn.load_extension("my_sqlite_extension", None) /// conn.load_extension("my_sqlite_extension", None)
/// } /// }
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
#[cfg(feature = "load_extension")] #[cfg(feature = "load_extension")]
pub fn load_extension<P: AsRef<Path>>(&self, dylib_path: P, entry_point: Option<&str>) -> SqliteResult<()> { pub fn load_extension<P: AsRef<Path>>(&self, dylib_path: P, entry_point: Option<&str>) -> SqliteResult<()> {
self.db.borrow_mut().load_extension(dylib_path, entry_point) self.db.borrow_mut().load_extension(dylib_path, entry_point)
@ -445,7 +505,9 @@ impl SqliteConnection {
impl fmt::Debug for SqliteConnection { impl fmt::Debug for SqliteConnection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "SqliteConnection( path: {:?} )", &self.path) f.debug_struct("SqliteConnection")
.field("path", &self.path)
.finish()
} }
} }
@ -652,6 +714,11 @@ impl<'conn> SqliteStatement<'conn> {
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if binding parameters fails, the executed statement returns rows (in
/// which case `query` should be used instead), or the underling SQLite call fails.
pub fn execute(&mut self, params: &[&ToSql]) -> SqliteResult<c_int> { pub fn execute(&mut self, params: &[&ToSql]) -> SqliteResult<c_int> {
unsafe { unsafe {
try!(self.bind_parameters(params)); try!(self.bind_parameters(params));
@ -693,6 +760,10 @@ impl<'conn> SqliteStatement<'conn> {
/// Ok(names) /// Ok(names)
/// } /// }
/// ``` /// ```
///
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query<'a>(&'a mut self, params: &[&ToSql]) -> SqliteResult<SqliteRows<'a>> { pub fn query<'a>(&'a mut self, params: &[&ToSql]) -> SqliteResult<SqliteRows<'a>> {
self.reset_if_needed(); self.reset_if_needed();
@ -709,10 +780,13 @@ impl<'conn> SqliteStatement<'conn> {
/// ///
/// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility /// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility
/// for accessing stale rows. /// for accessing stale rows.
///
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F) pub fn query_map<'a, T, F>(&'a mut self, params: &[&ToSql], f: F)
-> SqliteResult<MappedRows<'a, F>> -> SqliteResult<MappedRows<'a, F>>
where T: 'static, where F: FnMut(&SqliteRow) -> T {
F: FnMut(SqliteRow) -> T {
let row_iter = try!(self.query(params)); let row_iter = try!(self.query(params));
Ok(MappedRows{ Ok(MappedRows{
@ -727,11 +801,14 @@ impl<'conn> SqliteStatement<'conn> {
/// ///
/// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility /// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility
/// for accessing stale rows. /// for accessing stale rows.
///
/// # Failure
///
/// Will return `Err` if binding parameters fails.
pub fn query_and_then<'a, T, E, F>(&'a mut self, params: &[&ToSql], f: F) pub fn query_and_then<'a, T, E, F>(&'a mut self, params: &[&ToSql], f: F)
-> SqliteResult<AndThenRows<'a, F>> -> SqliteResult<AndThenRows<'a, F>>
where T: 'static, where E: convert::From<SqliteError>,
E: convert::From<SqliteError>, F: FnMut(&SqliteRow) -> Result<T, E> {
F: FnMut(SqliteRow) -> Result<T, E> {
let row_iter = try!(self.query(params)); let row_iter = try!(self.query(params));
Ok(AndThenRows{ Ok(AndThenRows{
@ -744,6 +821,10 @@ impl<'conn> SqliteStatement<'conn> {
/// ///
/// Functionally equivalent to the `Drop` implementation, but allows callers to see any errors /// Functionally equivalent to the `Drop` implementation, but allows callers to see any errors
/// that occur. /// that occur.
///
/// # Failure
///
/// Will return `Err` if the underlying SQLite call fails.
pub fn finalize(mut self) -> SqliteResult<()> { pub fn finalize(mut self) -> SqliteResult<()> {
self.finalize_() self.finalize_()
} }
@ -789,7 +870,11 @@ impl<'conn> fmt::Debug for SqliteStatement<'conn> {
let c_slice = CStr::from_ptr(ffi::sqlite3_sql(self.stmt)).to_bytes(); let c_slice = CStr::from_ptr(ffi::sqlite3_sql(self.stmt)).to_bytes();
str::from_utf8(c_slice) str::from_utf8(c_slice)
}; };
write!(f, "SqliteStatement( conn: {:?}, stmt: {:?}, sql: {:?} )", self.conn, self.stmt, sql) f.debug_struct("SqliteStatement")
.field("conn", self.conn)
.field("stmt", &self.stmt)
.field("sql", &sql)
.finish()
} }
} }
@ -807,12 +892,11 @@ pub struct MappedRows<'stmt, F> {
} }
impl<'stmt, T, F> Iterator for MappedRows<'stmt, F> impl<'stmt, T, F> Iterator for MappedRows<'stmt, F>
where T: 'static, where F: FnMut(&SqliteRow) -> T {
F: FnMut(SqliteRow) -> T {
type Item = SqliteResult<T>; type Item = SqliteResult<T>;
fn next(&mut self) -> Option<SqliteResult<T>> { fn next(&mut self) -> Option<SqliteResult<T>> {
self.rows.next().map(|row_result| row_result.map(|row| (self.map)(row))) self.rows.next().map(|row_result| row_result.map(|row| (self.map)(&row)))
} }
} }
@ -824,15 +908,14 @@ pub struct AndThenRows<'stmt, F> {
} }
impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F> impl<'stmt, T, E, F> Iterator for AndThenRows<'stmt, F>
where T: 'static, where E: convert::From<SqliteError>,
E: convert::From<SqliteError>, F: FnMut(&SqliteRow) -> Result<T, E> {
F: FnMut(SqliteRow) -> Result<T, E> {
type Item = Result<T, E>; type Item = Result<T, E>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.rows.next().map(|row_result| row_result self.rows.next().map(|row_result| row_result
.map_err(E::from) .map_err(E::from)
.and_then(|row| (self.map)(row))) .and_then(|row| (self.map)(&row)))
} }
} }

162
src/trace.rs Normal file
View File

@ -0,0 +1,162 @@
//! Tracing and profiling functions. Error and warning log.
use libc::{c_char, c_int, c_void};
use std::ffi::{CStr, CString};
use std::mem;
use std::ptr;
use std::str;
use std::time::Duration;
use super::ffi;
use {SqliteError, SqliteResult, SqliteConnection};
/// Set up the process-wide SQLite error logging callback.
/// This function is marked unsafe for two reasons:
///
/// * The function is not threadsafe. No other SQLite calls may be made while
/// `config_log` is running, and multiple threads may not call `config_log`
/// simultaneously.
/// * The provided `callback` itself function has two requirements:
/// * It must not invoke any SQLite calls.
/// * It must be threadsafe if SQLite is used in a multithreaded way.
///
/// cf [The Error And Warning Log](http://sqlite.org/errlog.html).
pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> SqliteResult<()> {
extern "C" fn log_callback(p_arg: *mut c_void, err: c_int, msg: *const c_char) {
let c_slice = unsafe { CStr::from_ptr(msg).to_bytes() };
let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
if let Ok(s) = str::from_utf8(c_slice) {
callback(err, s);
}
}
let rc = match callback {
Some(f) => {
let p_arg: *mut c_void = mem::transmute(f);
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, Some(log_callback), p_arg)
},
None => {
let nullptr: *mut c_void = ptr::null_mut();
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
}
};
if rc != ffi::SQLITE_OK {
return Err(SqliteError{ code: rc, message: "sqlite3_config(SQLITE_CONFIG_LOG, ...)".to_string() });
}
Ok(())
}
/// Write a message into the error log established by `config_log`.
pub fn log(err_code: c_int, msg: &str) {
let msg = CString::new(msg).expect("SQLite log messages cannot contain embedded zeroes");
unsafe {
ffi::sqlite3_log(err_code, msg.as_ptr());
}
}
impl SqliteConnection {
/// Register or clear a callback function that can be used for tracing the execution of SQL statements.
///
/// Prepared statement placeholders are replaced/logged with their assigned values.
/// There can only be a single tracer defined for each database connection.
/// Setting a new tracer clears the old one.
pub fn trace(&mut self, trace_fn: Option<fn(&str)>) {
extern "C" fn trace_callback (p_arg: *mut c_void, z_sql: *const c_char) {
let trace_fn: fn(&str) = unsafe { mem::transmute(p_arg) };
let c_slice = unsafe { CStr::from_ptr(z_sql).to_bytes() };
if let Ok(s) = str::from_utf8(c_slice) {
trace_fn(s);
}
}
let c = self.db.borrow_mut();
match trace_fn {
Some(f) => unsafe { ffi::sqlite3_trace(c.db(), Some(trace_callback), mem::transmute(f)); },
None => unsafe { ffi::sqlite3_trace(c.db(), None, ptr::null_mut()); },
}
}
/// Register or clear a callback function that can be used for profiling the execution of SQL statements.
///
/// There can only be a single profiler defined for each database connection.
/// Setting a new profiler clears the old one.
pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
extern "C" fn profile_callback(p_arg: *mut c_void, z_sql: *const c_char, nanoseconds: u64) {
let profile_fn: fn(&str, Duration) = unsafe { mem::transmute(p_arg) };
let c_slice = unsafe { CStr::from_ptr(z_sql).to_bytes() };
if let Ok(s) = str::from_utf8(c_slice) {
const NANOS_PER_SEC: u64 = 1_000_000_000;
let duration = Duration::new(nanoseconds / NANOS_PER_SEC,
(nanoseconds % NANOS_PER_SEC) as u32);
profile_fn(s, duration);
}
}
let c = self.db.borrow_mut();
match profile_fn {
Some(f) => unsafe { ffi::sqlite3_profile(c.db(), Some(profile_callback), mem::transmute(f)) },
None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) },
};
}
}
#[cfg(test)]
mod test {
use std::sync::Mutex;
use std::time::Duration;
use SqliteConnection;
#[test]
fn test_trace() {
lazy_static! {
static ref TRACED_STMTS: Mutex<Vec<String>> = Mutex::new(Vec::new());
}
fn tracer(s: &str) {
let mut traced_stmts = TRACED_STMTS.lock().unwrap();
traced_stmts.push(s.to_owned());
}
let mut db = SqliteConnection::open_in_memory().unwrap();
db.trace(Some(tracer));
{
let _ = db.query_row("SELECT ?", &[&1i32], |_| {});
let _ = db.query_row("SELECT ?", &[&"hello"], |_| {});
}
db.trace(None);
{
let _ = db.query_row("SELECT ?", &[&2i32], |_| {});
let _ = db.query_row("SELECT ?", &[&"goodbye"], |_| {});
}
let traced_stmts = TRACED_STMTS.lock().unwrap();
assert_eq!(traced_stmts.len(), 2);
assert_eq!(traced_stmts[0], "SELECT 1");
assert_eq!(traced_stmts[1], "SELECT 'hello'");
}
#[test]
fn test_profile() {
lazy_static! {
static ref PROFILED: Mutex<Vec<(String, Duration)>> = Mutex::new(Vec::new());
}
fn profiler(s: &str, d: Duration) {
let mut profiled = PROFILED.lock().unwrap();
profiled.push((s.to_owned(), d));
}
let mut db = SqliteConnection::open_in_memory().unwrap();
db.profile(Some(profiler));
db.execute_batch("PRAGMA application_id = 1").unwrap();
db.profile(None);
db.execute_batch("PRAGMA application_id = 2").unwrap();
let profiled = PROFILED.lock().unwrap();
assert_eq!(profiled.len(), 1);
assert_eq!(profiled[0].0, "PRAGMA application_id = 1");
}
}

36
tests/config_log.rs Normal file
View File

@ -0,0 +1,36 @@
//! This file contains unit tests for rusqlite::trace::config_log. This function affects
//! SQLite process-wide and so is not safe to run as a normal #[test] in the library.
#[macro_use] extern crate lazy_static;
extern crate libc;
extern crate rusqlite;
#[cfg(feature = "trace")]
fn main() {
use libc::c_int;
use std::sync::Mutex;
lazy_static! {
static ref LOGS_RECEIVED: Mutex<Vec<(c_int, String)>> = Mutex::new(Vec::new());
}
fn log_handler(err: c_int, message: &str) {
let mut logs_received = LOGS_RECEIVED.lock().unwrap();
logs_received.push((err, message.to_owned()));
}
use rusqlite::trace;
unsafe { trace::config_log(Some(log_handler)) }.unwrap();
trace::log(10, "First message from rusqlite");
unsafe { trace::config_log(None) }.unwrap();
trace::log(11, "Second message from rusqlite");
let logs_received = LOGS_RECEIVED.lock().unwrap();
assert_eq!(logs_received.len(), 1);
assert_eq!(logs_received[0].0, 10);
assert_eq!(logs_received[0].1, "First message from rusqlite");
}
#[cfg(not(feature = "trace"))]
fn main() {}