diff --git a/Cargo.toml b/Cargo.toml index 68e7347..673044c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ name = "rusqlite" [features] load_extension = ["libsqlite3-sys/load_extension"] +trace = [] [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 b46232f..d11b98a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,7 @@ pub use transaction::{SqliteTransactionBehavior, pub mod types; mod transaction; #[cfg(feature = "load_extension")] mod load_extension_guard; +#[cfg(feature = "trace")] pub mod trace; /// A typedef of the result returned by many methods. pub type SqliteResult = Result; diff --git a/src/trace.rs b/src/trace.rs new file mode 100644 index 0000000..b024f13 --- /dev/null +++ b/src/trace.rs @@ -0,0 +1,101 @@ +//! Tracing and profiling functions. Error and warning log. +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()); + } +} + +/// The trace callback function signature. +pub type TraceCallback = + Option; +/// The profile callback function signature. +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] #[ignore] // To avoid freezing tests + fn test_log() { + 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(); + } +}