diff --git a/Cargo.toml b/Cargo.toml index 5d05ed5..772b4cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ name = "rusqlite" [features] load_extension = ["libsqlite3-sys/load_extension"] +trace_extension = [] [dependencies] time = "~0.1.0" diff --git a/libsqlite3-sys/src/lib.rs b/libsqlite3-sys/src/lib.rs index 5d37276..51f33c5 100644 --- a/libsqlite3-sys/src/lib.rs +++ b/libsqlite3-sys/src/lib.rs @@ -92,3 +92,5 @@ pub fn code_to_str(code: c_int) -> &'static str { _ => "Unknown error code", } } + +pub const SQLITE_CONFIG_LOG : c_int = 16; diff --git a/src/lib.rs b/src/lib.rs index f98780b..d96fdfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,7 @@ //! } //! } //! ``` +#![cfg_attr(test, feature(duration))] extern crate libc; extern crate libsqlite3_sys as ffi; #[macro_use] extern crate bitflags; @@ -79,6 +80,7 @@ pub use transaction::{SqliteTransactionBehavior, pub mod types; mod transaction; #[cfg(feature = "load_extension")] mod load_extension_guard; +#[cfg(feature = "trace_extension")] pub mod trace_extension; /// A typedef of the result returned by many methods. pub type SqliteResult = Result; @@ -664,7 +666,7 @@ impl<'conn> SqliteStatement<'conn> { } /// Executes the prepared statement and maps a function over the resulting - /// rows. + /// rows. /// /// Unlike the iterator produced by `query`, the returned iterator does not expose the possibility /// for accessing stale rows. diff --git a/src/trace_extension.rs b/src/trace_extension.rs new file mode 100644 index 0000000..cde1d75 --- /dev/null +++ b/src/trace_extension.rs @@ -0,0 +1,98 @@ +use libc::{c_char, c_int, c_void}; +use std::ffi::CString; +use std::ptr; + +use super::ffi; +use {SqliteError, SqliteResult, SqliteConnection}; + +pub type LogCallback = + Option; + +/// Set up the error logging callback +/// +/// cf [The Error And Warning Log](http://sqlite.org/errlog.html). +pub fn config_log(cb: LogCallback) -> SqliteResult<()> { + let rc = unsafe { + let p_arg: *mut c_void = ptr::null_mut(); + ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, cb, p_arg) + }; + 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).unwrap(); + unsafe { + ffi::sqlite3_log(err_code, msg.as_ptr()); + } +} + +pub type TraceCallback = + Option; +pub type ProfileCallback = + Option; +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, x_trace: TraceCallback) { + let c = self.db.borrow_mut(); + unsafe { ffi::sqlite3_trace(c.db(), x_trace, 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, x_profile: ProfileCallback) { + let c = self.db.borrow_mut(); + unsafe { ffi::sqlite3_profile(c.db(), x_profile, ptr::null_mut()); } + } +} + +#[cfg(test)] +mod test { + use libc::{c_char, c_int, c_void}; + use std::ffi::CStr; + use std::io::Write; + use std::str; + + use ffi; + use SqliteConnection; + + extern "C" fn log_callback(_: *mut c_void, err: c_int, msg: *const c_char) { + unsafe { + let c_slice = CStr::from_ptr(msg).to_bytes(); + let _ = writeln!(::std::io::stderr(), "{}: {:?}", err, str::from_utf8(c_slice)); + } + } + + #[test] + fn test_log() { + if true { // To avoid freezing tests + return + } + unsafe { ffi::sqlite3_shutdown() }; + super::config_log(Some(log_callback)).unwrap(); + //super::log(ffi::SQLITE_NOTICE, "message from rusqlite"); + super::config_log(None).unwrap(); + } + + extern "C" fn profile_callback(_: *mut ::libc::c_void, sql: *const ::libc::c_char, nanoseconds: u64) { + use std::time::Duration; + unsafe { + let c_slice = ::std::ffi::CStr::from_ptr(sql).to_bytes(); + let d = Duration::from_millis(nanoseconds / 1_000_000); + let _ = writeln!(::std::io::stderr(), "PROFILE: {:?} ({})", ::std::str::from_utf8(c_slice), d); + } + } + + #[test] + fn test_profile() { + let mut db = SqliteConnection::open_in_memory().unwrap(); + db.profile(Some(profile_callback)); + db.execute_batch("PRAGMA application_id = 1").unwrap(); + } +} \ No newline at end of file