rusqlite/
trace.rs

1//! Tracing and profiling functions. Error and warning log.
2
3use std::borrow::Cow;
4use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString};
5use std::marker::PhantomData;
6use std::mem;
7use std::panic::catch_unwind;
8use std::ptr;
9use std::time::Duration;
10
11use super::ffi;
12use crate::{Connection, StatementStatus, MAIN_DB};
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).
28#[cfg(not(feature = "loadable_extension"))]
29pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> crate::Result<()> {
30    extern "C" fn log_callback(p_arg: *mut c_void, err: c_int, msg: *const c_char) {
31        let s = unsafe { CStr::from_ptr(msg).to_string_lossy() };
32        let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
33
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(crate::error::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
65bitflags::bitflags! {
66    /// Trace event codes
67    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
68    #[non_exhaustive]
69    #[repr(C)]
70    pub struct TraceEventCodes: c_uint {
71        /// when a prepared statement first begins running and possibly at other times during the execution
72        /// of the prepared statement, such as at the start of each trigger subprogram
73        const SQLITE_TRACE_STMT = ffi::SQLITE_TRACE_STMT;
74        /// when the statement finishes
75        const SQLITE_TRACE_PROFILE = ffi::SQLITE_TRACE_PROFILE;
76        /// whenever a prepared statement generates a single row of result
77        const SQLITE_TRACE_ROW = ffi::SQLITE_TRACE_ROW;
78        /// when a database connection closes
79        const SQLITE_TRACE_CLOSE = ffi::SQLITE_TRACE_CLOSE;
80    }
81}
82
83/// Trace event
84#[non_exhaustive]
85pub enum TraceEvent<'s> {
86    /// when a prepared statement first begins running and possibly at other times during the execution
87    /// of the prepared statement, such as at the start of each trigger subprogram
88    Stmt(StmtRef<'s>, &'s str),
89    /// when the statement finishes
90    Profile(StmtRef<'s>, Duration),
91    /// whenever a prepared statement generates a single row of result
92    Row(StmtRef<'s>),
93    /// when a database connection closes
94    Close(ConnRef<'s>),
95}
96
97/// Statement reference
98pub struct StmtRef<'s> {
99    ptr: *mut ffi::sqlite3_stmt,
100    phantom: PhantomData<&'s ()>,
101}
102
103impl StmtRef<'_> {
104    fn new(ptr: *mut ffi::sqlite3_stmt) -> Self {
105        StmtRef {
106            ptr,
107            phantom: PhantomData,
108        }
109    }
110    /// SQL text
111    pub fn sql(&self) -> Cow<'_, str> {
112        unsafe { CStr::from_ptr(ffi::sqlite3_sql(self.ptr)).to_string_lossy() }
113    }
114    /// Expanded SQL text
115    pub fn expanded_sql(&self) -> Option<String> {
116        unsafe {
117            crate::raw_statement::expanded_sql(self.ptr).map(|s| s.to_string_lossy().to_string())
118        }
119    }
120    /// Get the value for one of the status counters for this statement.
121    pub fn get_status(&self, status: StatementStatus) -> i32 {
122        unsafe { crate::raw_statement::stmt_status(self.ptr, status, false) }
123    }
124}
125
126/// Connection reference
127pub struct ConnRef<'s> {
128    ptr: *mut ffi::sqlite3,
129    phantom: PhantomData<&'s ()>,
130}
131
132impl ConnRef<'_> {
133    /// Test for auto-commit mode.
134    pub fn is_autocommit(&self) -> bool {
135        unsafe { crate::inner_connection::get_autocommit(self.ptr) }
136    }
137    /// the path to the database file, if one exists and is known.
138    pub fn db_filename(&self) -> Option<&str> {
139        unsafe { crate::inner_connection::db_filename(self.phantom, self.ptr, MAIN_DB) }
140    }
141}
142
143impl Connection {
144    /// Register or clear a callback function that can be
145    /// used for tracing the execution of SQL statements.
146    ///
147    /// Prepared statement placeholders are replaced/logged with their assigned
148    /// values. There can only be a single tracer defined for each database
149    /// connection. Setting a new tracer clears the old one.
150    #[deprecated(since = "0.33.0", note = "use trace_v2 instead")]
151    pub fn trace(&mut self, trace_fn: Option<fn(&str)>) {
152        unsafe extern "C" fn trace_callback(p_arg: *mut c_void, z_sql: *const c_char) {
153            let trace_fn: fn(&str) = mem::transmute(p_arg);
154            let s = CStr::from_ptr(z_sql).to_string_lossy();
155            drop(catch_unwind(|| trace_fn(&s)));
156        }
157
158        let c = self.db.borrow_mut();
159        match trace_fn {
160            Some(f) => unsafe {
161                ffi::sqlite3_trace(c.db(), Some(trace_callback), f as *mut c_void);
162            },
163            None => unsafe {
164                ffi::sqlite3_trace(c.db(), None, ptr::null_mut());
165            },
166        }
167    }
168
169    /// Register or clear a callback function that can be
170    /// used for profiling the execution of SQL statements.
171    ///
172    /// There can only be a single profiler defined for each database
173    /// connection. Setting a new profiler clears the old one.
174    #[deprecated(since = "0.33.0", note = "use trace_v2 instead")]
175    pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
176        unsafe extern "C" fn profile_callback(
177            p_arg: *mut c_void,
178            z_sql: *const c_char,
179            nanoseconds: u64,
180        ) {
181            let profile_fn: fn(&str, Duration) = mem::transmute(p_arg);
182            let s = CStr::from_ptr(z_sql).to_string_lossy();
183
184            let duration = Duration::from_nanos(nanoseconds);
185            drop(catch_unwind(|| profile_fn(&s, duration)));
186        }
187
188        let c = self.db.borrow_mut();
189        match profile_fn {
190            Some(f) => unsafe {
191                ffi::sqlite3_profile(c.db(), Some(profile_callback), f as *mut c_void)
192            },
193            None => unsafe { ffi::sqlite3_profile(c.db(), None, ptr::null_mut()) },
194        };
195    }
196
197    /// Register or clear a trace callback function
198    pub fn trace_v2(&self, mask: TraceEventCodes, trace_fn: Option<fn(TraceEvent<'_>)>) {
199        unsafe extern "C" fn trace_callback(
200            evt: c_uint,
201            ctx: *mut c_void,
202            p: *mut c_void,
203            x: *mut c_void,
204        ) -> c_int {
205            let trace_fn: fn(TraceEvent<'_>) = mem::transmute(ctx);
206            drop(catch_unwind(|| match evt {
207                ffi::SQLITE_TRACE_STMT => {
208                    let str = CStr::from_ptr(x as *const c_char).to_string_lossy();
209                    trace_fn(TraceEvent::Stmt(
210                        StmtRef::new(p as *mut ffi::sqlite3_stmt),
211                        &str,
212                    ))
213                }
214                ffi::SQLITE_TRACE_PROFILE => {
215                    let ns = *(x as *const i64);
216                    trace_fn(TraceEvent::Profile(
217                        StmtRef::new(p as *mut ffi::sqlite3_stmt),
218                        Duration::from_nanos(u64::try_from(ns).unwrap_or_default()),
219                    ))
220                }
221                ffi::SQLITE_TRACE_ROW => {
222                    trace_fn(TraceEvent::Row(StmtRef::new(p as *mut ffi::sqlite3_stmt)))
223                }
224                ffi::SQLITE_TRACE_CLOSE => trace_fn(TraceEvent::Close(ConnRef {
225                    ptr: p as *mut ffi::sqlite3,
226                    phantom: PhantomData,
227                })),
228                _ => {}
229            }));
230            // The integer return value from the callback is currently ignored, though this may change in future releases.
231            // Callback implementations should return zero to ensure future compatibility.
232            ffi::SQLITE_OK
233        }
234        let c = self.db.borrow_mut();
235        if let Some(f) = trace_fn {
236            unsafe {
237                ffi::sqlite3_trace_v2(c.db(), mask.bits(), Some(trace_callback), f as *mut c_void);
238            }
239        } else {
240            unsafe {
241                ffi::sqlite3_trace_v2(c.db(), 0, None, ptr::null_mut());
242            }
243        }
244    }
245}
246
247#[cfg(test)]
248mod test {
249    use std::sync::{LazyLock, Mutex};
250    use std::time::Duration;
251
252    use crate::{Connection, Result};
253
254    #[test]
255    #[allow(deprecated)]
256    fn test_trace() -> Result<()> {
257        static TRACED_STMTS: LazyLock<Mutex<Vec<String>>> =
258            LazyLock::new(|| Mutex::new(Vec::new()));
259        fn tracer(s: &str) {
260            let mut traced_stmts = TRACED_STMTS.lock().unwrap();
261            traced_stmts.push(s.to_owned());
262        }
263
264        let mut db = Connection::open_in_memory()?;
265        db.trace(Some(tracer));
266        {
267            let _ = db.query_row("SELECT ?1", [1i32], |_| Ok(()));
268            let _ = db.query_row("SELECT ?1", ["hello"], |_| Ok(()));
269        }
270        db.trace(None);
271        {
272            let _ = db.query_row("SELECT ?1", [2i32], |_| Ok(()));
273            let _ = db.query_row("SELECT ?1", ["goodbye"], |_| Ok(()));
274        }
275
276        let traced_stmts = TRACED_STMTS.lock().unwrap();
277        assert_eq!(traced_stmts.len(), 2);
278        assert_eq!(traced_stmts[0], "SELECT 1");
279        assert_eq!(traced_stmts[1], "SELECT 'hello'");
280        Ok(())
281    }
282
283    #[test]
284    #[allow(deprecated)]
285    fn test_profile() -> Result<()> {
286        static PROFILED: LazyLock<Mutex<Vec<(String, Duration)>>> =
287            LazyLock::new(|| Mutex::new(Vec::new()));
288        fn profiler(s: &str, d: Duration) {
289            let mut profiled = PROFILED.lock().unwrap();
290            profiled.push((s.to_owned(), d));
291        }
292
293        let mut db = Connection::open_in_memory()?;
294        db.profile(Some(profiler));
295        db.execute_batch("PRAGMA application_id = 1")?;
296        db.profile(None);
297        db.execute_batch("PRAGMA application_id = 2")?;
298
299        let profiled = PROFILED.lock().unwrap();
300        assert_eq!(profiled.len(), 1);
301        assert_eq!(profiled[0].0, "PRAGMA application_id = 1");
302        Ok(())
303    }
304
305    #[test]
306    pub fn trace_v2() -> Result<()> {
307        use super::{TraceEvent, TraceEventCodes};
308        use std::borrow::Borrow;
309        use std::cmp::Ordering;
310
311        let db = Connection::open_in_memory()?;
312        db.trace_v2(
313            TraceEventCodes::all(),
314            Some(|e| match e {
315                TraceEvent::Stmt(s, sql) => {
316                    assert_eq!(s.sql(), sql);
317                }
318                TraceEvent::Profile(s, d) => {
319                    assert_eq!(s.get_status(crate::StatementStatus::Sort), 0);
320                    assert_eq!(d.cmp(&Duration::ZERO), Ordering::Greater)
321                }
322                TraceEvent::Row(s) => {
323                    assert_eq!(s.expanded_sql().as_deref(), Some(s.sql().borrow()));
324                }
325                TraceEvent::Close(db) => {
326                    assert!(db.is_autocommit());
327                    assert!(db.db_filename().is_none());
328                }
329            }),
330        );
331
332        db.one_column::<u32, _>("PRAGMA user_version", [])?;
333        drop(db);
334
335        let db = Connection::open_in_memory()?;
336        db.trace_v2(TraceEventCodes::empty(), None);
337        Ok(())
338    }
339}