1use 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#[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#[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 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
68 #[non_exhaustive]
69 #[repr(C)]
70 pub struct TraceEventCodes: c_uint {
71 const SQLITE_TRACE_STMT = ffi::SQLITE_TRACE_STMT;
74 const SQLITE_TRACE_PROFILE = ffi::SQLITE_TRACE_PROFILE;
76 const SQLITE_TRACE_ROW = ffi::SQLITE_TRACE_ROW;
78 const SQLITE_TRACE_CLOSE = ffi::SQLITE_TRACE_CLOSE;
80 }
81}
82
83#[non_exhaustive]
85pub enum TraceEvent<'s> {
86 Stmt(StmtRef<'s>, &'s str),
89 Profile(StmtRef<'s>, Duration),
91 Row(StmtRef<'s>),
93 Close(ConnRef<'s>),
95}
96
97pub 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 pub fn sql(&self) -> Cow<'_, str> {
112 let sql = unsafe { ffi::sqlite3_sql(self.ptr) };
113
114 if sql.is_null() {
115 return Cow::default();
116 }
117
118 unsafe { CStr::from_ptr(sql).to_string_lossy() }
120 }
121 pub fn expanded_sql(&self) -> Option<String> {
123 unsafe {
124 crate::raw_statement::expanded_sql(self.ptr).map(|s| s.to_string_lossy().to_string())
125 }
126 }
127 pub fn get_status(&self, status: StatementStatus) -> i32 {
129 unsafe { crate::raw_statement::stmt_status(self.ptr, status, false) }
130 }
131}
132
133pub struct ConnRef<'s> {
135 ptr: *mut ffi::sqlite3,
136 phantom: PhantomData<&'s ()>,
137}
138
139impl ConnRef<'_> {
140 pub fn is_autocommit(&self) -> bool {
142 unsafe { crate::inner_connection::get_autocommit(self.ptr) }
143 }
144 pub fn db_filename(&self) -> Option<&str> {
146 unsafe { crate::inner_connection::db_filename(self.phantom, self.ptr, MAIN_DB) }
147 }
148}
149
150impl Connection {
151 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
158 #[deprecated(since = "0.33.0", note = "use trace_v2 instead")]
159 pub fn trace(&mut self, trace_fn: Option<fn(&str)>) {
160 unsafe extern "C" fn trace_callback(p_arg: *mut c_void, z_sql: *const c_char) {
161 let trace_fn: fn(&str) = mem::transmute(p_arg);
162 let s = CStr::from_ptr(z_sql).to_string_lossy();
163 drop(catch_unwind(|| trace_fn(&s)));
164 }
165
166 let c = self.db.borrow_mut();
167 unsafe {
168 ffi::sqlite3_trace(
169 c.db(),
170 trace_fn.as_ref().map(|_| trace_callback as _),
171 trace_fn.map_or_else(ptr::null_mut, |f| f as *mut c_void),
172 );
173 }
174 }
175
176 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
182 #[deprecated(since = "0.33.0", note = "use trace_v2 instead")]
183 pub fn profile(&mut self, profile_fn: Option<fn(&str, Duration)>) {
184 unsafe extern "C" fn profile_callback(
185 p_arg: *mut c_void,
186 z_sql: *const c_char,
187 nanoseconds: u64,
188 ) {
189 let profile_fn: fn(&str, Duration) = mem::transmute(p_arg);
190 let s = CStr::from_ptr(z_sql).to_string_lossy();
191
192 let duration = Duration::from_nanos(nanoseconds);
193 drop(catch_unwind(|| profile_fn(&s, duration)));
194 }
195
196 let c = self.db.borrow_mut();
197 unsafe {
198 ffi::sqlite3_profile(
199 c.db(),
200 profile_fn.as_ref().map(|_| profile_callback as _),
201 profile_fn.map_or_else(ptr::null_mut, |f| f as *mut c_void),
202 );
203 }
204 }
205
206 pub fn trace_v2(&self, mask: TraceEventCodes, trace_fn: Option<fn(TraceEvent<'_>)>) {
208 unsafe extern "C" fn trace_callback(
209 evt: c_uint,
210 ctx: *mut c_void,
211 p: *mut c_void,
212 x: *mut c_void,
213 ) -> c_int {
214 let trace_fn: fn(TraceEvent<'_>) = mem::transmute(ctx);
215 drop(catch_unwind(|| match evt {
216 ffi::SQLITE_TRACE_STMT => {
217 let str = CStr::from_ptr(x as *const c_char).to_string_lossy();
218 trace_fn(TraceEvent::Stmt(
219 StmtRef::new(p as *mut ffi::sqlite3_stmt),
220 &str,
221 ));
222 }
223 ffi::SQLITE_TRACE_PROFILE => {
224 let ns = *(x as *const i64);
225 trace_fn(TraceEvent::Profile(
226 StmtRef::new(p as *mut ffi::sqlite3_stmt),
227 Duration::from_nanos(u64::try_from(ns).unwrap_or_default()),
228 ));
229 }
230 ffi::SQLITE_TRACE_ROW => {
231 trace_fn(TraceEvent::Row(StmtRef::new(p as *mut ffi::sqlite3_stmt)));
232 }
233 ffi::SQLITE_TRACE_CLOSE => trace_fn(TraceEvent::Close(ConnRef {
234 ptr: p as *mut ffi::sqlite3,
235 phantom: PhantomData,
236 })),
237 _ => {}
238 }));
239 ffi::SQLITE_OK
242 }
243 let c = self.db.borrow_mut();
244 unsafe {
245 ffi::sqlite3_trace_v2(
246 c.db(),
247 mask.bits(),
248 trace_fn.as_ref().map(|_| trace_callback as _),
249 trace_fn.map_or_else(ptr::null_mut, |f| f as *mut c_void),
250 );
251 }
252 }
253}
254
255#[cfg(all(test, not(miri)))]
256mod test {
257 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
258 use wasm_bindgen_test::wasm_bindgen_test as test;
259
260 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
261 use std::sync::{LazyLock, Mutex};
262 use std::time::Duration;
263
264 use super::{TraceEvent, TraceEventCodes};
265 use crate::{Connection, Result, MAIN_DB};
266
267 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
268 #[test]
269 #[allow(deprecated)]
270 fn test_trace() -> Result<()> {
271 static TRACED_STMTS: LazyLock<Mutex<Vec<String>>> =
272 LazyLock::new(|| Mutex::new(Vec::new()));
273 fn tracer(s: &str) {
274 let mut traced_stmts = TRACED_STMTS.lock().unwrap();
275 traced_stmts.push(s.to_owned());
276 }
277
278 let mut db = Connection::open_in_memory()?;
279 db.trace(Some(tracer));
280 {
281 let _ = db.query_row("SELECT ?1", [1i32], |_| Ok(()));
282 let _ = db.query_row("SELECT ?1", ["hello"], |_| Ok(()));
283 }
284 db.trace(None);
285 {
286 let _ = db.query_row("SELECT ?1", [2i32], |_| Ok(()));
287 let _ = db.query_row("SELECT ?1", ["goodbye"], |_| Ok(()));
288 }
289
290 let traced_stmts = TRACED_STMTS.lock().unwrap();
291 assert_eq!(traced_stmts.len(), 2);
292 assert_eq!(traced_stmts[0], "SELECT 1");
293 assert_eq!(traced_stmts[1], "SELECT 'hello'");
294 Ok(())
295 }
296
297 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
298 #[test]
299 #[allow(deprecated)]
300 fn test_profile() -> Result<()> {
301 static PROFILED: LazyLock<Mutex<Vec<(String, Duration)>>> =
302 LazyLock::new(|| Mutex::new(Vec::new()));
303 fn profiler(s: &str, d: Duration) {
304 let mut profiled = PROFILED.lock().unwrap();
305 profiled.push((s.to_owned(), d));
306 }
307
308 let mut db = Connection::open_in_memory()?;
309 db.profile(Some(profiler));
310 db.execute_batch("PRAGMA application_id = 1")?;
311 db.profile(None);
312 db.execute_batch("PRAGMA application_id = 2")?;
313
314 let profiled = PROFILED.lock().unwrap();
315 assert_eq!(profiled.len(), 1);
316 assert_eq!(profiled[0].0, "PRAGMA application_id = 1");
317 Ok(())
318 }
319
320 #[test]
321 pub fn trace_v2() -> Result<()> {
322 use std::borrow::Borrow as _;
323 use std::cmp::Ordering;
324
325 let db = Connection::open_in_memory()?;
326 db.trace_v2(
327 TraceEventCodes::all(),
328 Some(|e| match e {
329 TraceEvent::Stmt(s, sql) => {
330 assert_eq!(s.sql(), sql);
331 }
332 TraceEvent::Profile(s, d) => {
333 assert_eq!(s.get_status(crate::StatementStatus::Sort), 0);
334 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
335 assert_eq!(d.cmp(&Duration::ZERO), Ordering::Greater);
336 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
338 assert!(matches!(
339 d.cmp(&Duration::ZERO),
340 Ordering::Equal | Ordering::Greater
341 ));
342 }
343 TraceEvent::Row(s) => {
344 assert_eq!(s.expanded_sql().as_deref(), Some(s.sql().borrow()));
345 }
346 TraceEvent::Close(db) => {
347 assert!(db.is_autocommit());
348 assert!(db.db_filename().is_none_or(|s| s.is_empty()));
352 }
353 }),
354 );
355
356 db.one_column::<u32, _>("PRAGMA user_version", [])?;
357 drop(db);
358
359 let db = Connection::open_in_memory()?;
360 db.trace_v2(TraceEventCodes::empty(), None);
361 Ok(())
362 }
363
364 #[test]
365 #[cfg(feature = "blob")]
366 pub fn null_sql() -> Result<()> {
367 let db = Connection::open_in_memory()?;
368 let sql = "CREATE TABLE test (content BLOB);
369 INSERT INTO test VALUES (ZEROBLOB(10));";
370 db.execute_batch(sql)?;
371 let rowid = db.last_insert_rowid();
372
373 db.trace_v2(
374 TraceEventCodes::SQLITE_TRACE_ROW,
375 Some(|e| {
376 if let TraceEvent::Row(s) = e {
377 assert_eq!(s.sql(), "");
378 }
379 }),
380 );
381 db.blob_open(MAIN_DB, c"test", c"content", rowid, true)?;
382
383 Ok(())
384 }
385}