Skip to main content

rusqlite/
trace.rs

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