1use 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#[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#[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 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 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}