rusqlite/
trace.rs

1//! Tracing and profiling functions. Error and warning log.
2
3use std::ffi::{CStr, CString};
4use std::mem;
5use std::os::raw::{c_char, c_int, c_void};
6use std::panic::catch_unwind;
7use std::ptr;
8use std::time::Duration;
9
10use super::ffi;
11use crate::error::error_from_sqlite_code;
12use crate::{Connection, Result};
13
14/// Set up the process-wide SQLite error logging callback.
15///
16/// # Safety
17///
18/// This function is marked unsafe for two reasons:
19///
20/// * The function is not threadsafe. No other SQLite calls may be made while
21///   `config_log` is running, and multiple threads may not call `config_log`
22///   simultaneously.
23/// * The provided `callback` itself function has two requirements:
24///     * It must not invoke any SQLite calls.
25///     * It must be threadsafe if SQLite is used in a multithreaded way.
26///
27/// cf [The Error And Warning Log](http://sqlite.org/errlog.html).
28pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
29    extern "C" fn log_callback(p_arg: *mut c_void, err: c_int, msg: *const c_char) {
30        let c_slice = unsafe { CStr::from_ptr(msg).to_bytes() };
31        let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
32
33        let s = String::from_utf8_lossy(c_slice);
34        drop(catch_unwind(|| callback(err, &s)));
35    }
36
37    let rc = if let Some(f) = callback {
38        ffi::sqlite3_config(
39            ffi::SQLITE_CONFIG_LOG,
40            log_callback as extern "C" fn(_, _, _),
41            f as *mut c_void,
42        )
43    } else {
44        let nullptr: *mut c_void = ptr::null_mut();
45        ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
46    };
47
48    if rc == ffi::SQLITE_OK {
49        Ok(())
50    } else {
51        Err(error_from_sqlite_code(rc, None))
52    }
53}
54
55/// Write a message into the error log established by
56/// `config_log`.
57#[inline]
58pub fn log(err_code: c_int, msg: &str) {
59    let msg = CString::new(msg).expect("SQLite log messages cannot contain embedded zeroes");
60    unsafe {
61        ffi::sqlite3_log(err_code, b"%s\0" as *const _ as *const c_char, msg.as_ptr());
62    }
63}
64
65impl Connection {
66    /// Register or clear a callback function that can be
67    /// used for tracing the execution of SQL statements.
68    ///
69    /// Prepared statement placeholders are replaced/logged with their assigned
70    /// values. There can only be a single tracer defined for each database
71    /// connection. Setting a new tracer clears the old one.
72    pub fn trace(&mut self, trace_fn: Option<fn(&str)>) {
73        unsafe extern "C" fn trace_callback(p_arg: *mut c_void, z_sql: *const c_char) {
74            let trace_fn: fn(&str) = mem::transmute(p_arg);
75            let c_slice = CStr::from_ptr(z_sql).to_bytes();
76            let s = String::from_utf8_lossy(c_slice);
77            drop(catch_unwind(|| trace_fn(&s)));
78        }
79
80        let c = self.db.borrow_mut();
81        match trace_fn {
82            Some(f) => unsafe {
83                ffi::sqlite3_trace(c.db(), Some(trace_callback), f as *mut c_void);
84            },
85            None => unsafe {
86                ffi::sqlite3_trace(c.db(), None, ptr::null_mut());
87            },
88        }
89    }
90
91    /// Register or clear a callback function that can be
92    /// used for profiling the execution of SQL statements.
93    ///
94    /// There can only be a single profiler defined for each database
95    /// connection. Setting a new profiler clears the old one.
96    pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
97        unsafe extern "C" fn profile_callback(
98            p_arg: *mut c_void,
99            z_sql: *const c_char,
100            nanoseconds: u64,
101        ) {
102            let profile_fn: fn(&str, Duration) = mem::transmute(p_arg);
103            let c_slice = CStr::from_ptr(z_sql).to_bytes();
104            let s = String::from_utf8_lossy(c_slice);
105            const NANOS_PER_SEC: u64 = 1_000_000_000;
106
107            let duration = Duration::new(
108                nanoseconds / NANOS_PER_SEC,
109                (nanoseconds % NANOS_PER_SEC) as u32,
110            );
111            drop(catch_unwind(|| profile_fn(&s, duration)));
112        }
113
114        let c = self.db.borrow_mut();
115        match profile_fn {
116            Some(f) => unsafe {
117                ffi::sqlite3_profile(c.db(), Some(profile_callback), f as *mut c_void)
118            },
119            None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) },
120        };
121    }
122
123    // TODO sqlite3_trace_v2 (https://sqlite.org/c3ref/trace_v2.html) // 3.14.0, #977
124}
125
126#[cfg(test)]
127mod test {
128    use lazy_static::lazy_static;
129    use std::sync::Mutex;
130    use std::time::Duration;
131
132    use crate::{Connection, Result};
133
134    #[test]
135    fn test_trace() -> Result<()> {
136        lazy_static! {
137            static ref TRACED_STMTS: Mutex<Vec<String>> = Mutex::new(Vec::new());
138        }
139        fn tracer(s: &str) {
140            let mut traced_stmts = TRACED_STMTS.lock().unwrap();
141            traced_stmts.push(s.to_owned());
142        }
143
144        let mut db = Connection::open_in_memory()?;
145        db.trace(Some(tracer));
146        {
147            let _ = db.query_row("SELECT ?1", [1i32], |_| Ok(()));
148            let _ = db.query_row("SELECT ?1", ["hello"], |_| Ok(()));
149        }
150        db.trace(None);
151        {
152            let _ = db.query_row("SELECT ?1", [2i32], |_| Ok(()));
153            let _ = db.query_row("SELECT ?1", ["goodbye"], |_| Ok(()));
154        }
155
156        let traced_stmts = TRACED_STMTS.lock().unwrap();
157        assert_eq!(traced_stmts.len(), 2);
158        assert_eq!(traced_stmts[0], "SELECT 1");
159        assert_eq!(traced_stmts[1], "SELECT 'hello'");
160        Ok(())
161    }
162
163    #[test]
164    fn test_profile() -> Result<()> {
165        lazy_static! {
166            static ref PROFILED: Mutex<Vec<(String, Duration)>> = Mutex::new(Vec::new());
167        }
168        fn profiler(s: &str, d: Duration) {
169            let mut profiled = PROFILED.lock().unwrap();
170            profiled.push((s.to_owned(), d));
171        }
172
173        let mut db = Connection::open_in_memory()?;
174        db.profile(Some(profiler));
175        db.execute_batch("PRAGMA application_id = 1")?;
176        db.profile(None);
177        db.execute_batch("PRAGMA application_id = 2")?;
178
179        let profiled = PROFILED.lock().unwrap();
180        assert_eq!(profiled.last().unwrap().0, "PRAGMA application_id = 1");
181        Ok(())
182    }
183}