use std::borrow::Cow;
use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};
use std::marker::PhantomData;
use std::mem;
use std::panic::catch_unwind;
use std::ptr;
use std::time::Duration;
use super::ffi;
use crate::{Connection, MAIN_DB, Result, StatementStatus};
#[cfg(not(feature = "loadable_extension"))]
pub unsafe fn config_log(callback: Option<fn(c_int, &str)>) -> Result<()> {
extern "C" fn log_callback(p_arg: *mut c_void, err: c_int, msg: *const c_char) {
let s = unsafe { CStr::from_ptr(msg).to_string_lossy() };
let callback: fn(c_int, &str) = unsafe { mem::transmute(p_arg) };
drop(catch_unwind(|| callback(err, &s)));
}
let rc = unsafe {
if let Some(f) = callback {
ffi::sqlite3_config(
ffi::SQLITE_CONFIG_LOG,
log_callback as extern "C" fn(_, _, _),
f as *mut c_void,
)
} else {
let nullptr: *mut c_void = ptr::null_mut();
ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, nullptr, nullptr)
}
};
if rc == ffi::SQLITE_OK {
Ok(())
} else {
Err(crate::error::error_from_sqlite_code(rc, None))
}
}
#[inline]
pub fn log(err_code: c_int, msg: &str) {
let msg = CString::new(msg).expect("SQLite log messages cannot contain embedded zeroes");
unsafe {
ffi::sqlite3_log(err_code, c"%s".as_ptr(), msg.as_ptr());
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
#[repr(C)]
pub struct TraceEventCodes: c_uint {
const SQLITE_TRACE_STMT = ffi::SQLITE_TRACE_STMT;
const SQLITE_TRACE_PROFILE = ffi::SQLITE_TRACE_PROFILE;
const SQLITE_TRACE_ROW = ffi::SQLITE_TRACE_ROW;
const SQLITE_TRACE_CLOSE = ffi::SQLITE_TRACE_CLOSE;
}
}
#[non_exhaustive]
pub enum TraceEvent<'s> {
Stmt(StmtRef<'s>, &'s str),
Profile(StmtRef<'s>, Duration),
Row(StmtRef<'s>),
Close(ConnRef<'s>),
}
pub struct StmtRef<'s> {
ptr: *mut ffi::sqlite3_stmt,
phantom: PhantomData<&'s ()>,
}
impl StmtRef<'_> {
fn new(ptr: *mut ffi::sqlite3_stmt) -> Self {
StmtRef {
ptr,
phantom: PhantomData,
}
}
#[must_use]
pub fn sql(&self) -> Cow<'_, str> {
let sql = unsafe { ffi::sqlite3_sql(self.ptr) };
if sql.is_null() {
return Cow::default();
}
unsafe { CStr::from_ptr(sql).to_string_lossy() }
}
#[must_use]
pub fn expanded_sql(&self) -> Option<String> {
unsafe {
crate::raw_statement::expanded_sql(self.ptr).map(|s| s.to_string_lossy().to_string())
}
}
#[must_use]
pub fn get_status(&self, status: StatementStatus) -> i32 {
unsafe { crate::raw_statement::stmt_status(self.ptr, status, false) }
}
}
pub struct ConnRef<'s> {
ptr: *mut ffi::sqlite3,
phantom: PhantomData<&'s ()>,
}
impl ConnRef<'_> {
#[must_use]
pub fn is_autocommit(&self) -> bool {
unsafe { crate::inner_connection::get_autocommit(self.ptr) }
}
#[must_use]
pub fn db_filename(&self) -> Option<&str> {
unsafe { crate::inner_connection::db_filename(self.phantom, self.ptr, MAIN_DB) }
}
}
impl Connection {
pub fn trace_v2<F>(&mut self, mask: TraceEventCodes, trace_fn: Option<F>) -> Result<()>
where
F: FnMut(TraceEvent<'_>) + Send + 'static,
{
unsafe extern "C" fn trace_callback<F>(
evt: c_uint,
ctx: *mut c_void,
p: *mut c_void,
x: *mut c_void,
) -> c_int
where
F: FnMut(TraceEvent<'_>),
{
unsafe {
drop(catch_unwind(|| {
let trace_fn: *mut F = ctx.cast::<F>();
match evt {
ffi::SQLITE_TRACE_STMT => {
let str = CStr::from_ptr(x as *const c_char).to_string_lossy();
(*trace_fn)(TraceEvent::Stmt(
StmtRef::new(p.cast::<ffi::sqlite3_stmt>()),
&str,
));
}
ffi::SQLITE_TRACE_PROFILE => {
let ns = *(x as *const i64);
(*trace_fn)(TraceEvent::Profile(
StmtRef::new(p.cast::<ffi::sqlite3_stmt>()),
Duration::from_nanos(u64::try_from(ns).unwrap_or_default()),
));
}
ffi::SQLITE_TRACE_ROW => {
(*trace_fn)(TraceEvent::Row(StmtRef::new(
p.cast::<ffi::sqlite3_stmt>(),
)));
}
ffi::SQLITE_TRACE_CLOSE => (*trace_fn)(TraceEvent::Close(ConnRef {
ptr: p.cast::<ffi::sqlite3>(),
phantom: PhantomData,
})),
_ => {}
}
}));
ffi::SQLITE_OK
}
}
let mut c = self.db.borrow_mut();
let x = trace_fn.as_ref().map(|_| trace_callback::<F> as _);
c.set_clientdata(c"sqlite3_trace_v2", trace_fn, |db, bh| unsafe {
ffi::sqlite3_trace_v2(db, mask.bits(), x, bh);
ffi::SQLITE_OK
})?;
Ok(())
}
}
#[cfg(all(test, not(miri)))]
mod test {
use std::cell::RefCell;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use wasm_bindgen_test::wasm_bindgen_test as test;
use std::time::Duration;
use super::{TraceEvent, TraceEventCodes};
use crate::{Connection, Result};
#[test]
pub fn trace_v2() -> Result<()> {
use std::borrow::Borrow as _;
use std::cmp::Ordering;
let mut db = Connection::open_in_memory()?;
db.trace_v2(
TraceEventCodes::all(),
Some(|e: TraceEvent<'_>| match e {
TraceEvent::Stmt(s, sql) => {
assert_eq!(s.sql(), sql);
}
TraceEvent::Profile(s, d) => {
assert_eq!(s.get_status(crate::StatementStatus::Sort), 0);
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
assert_eq!(d.cmp(&Duration::ZERO), Ordering::Greater);
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
std::assert_matches!(
d.cmp(&Duration::ZERO),
Ordering::Equal | Ordering::Greater
);
}
TraceEvent::Row(s) => {
assert_eq!(s.expanded_sql().as_deref(), Some(s.sql().borrow()));
}
TraceEvent::Close(db) => {
assert!(db.is_autocommit());
assert!(db.db_filename().is_none_or(|s| s.is_empty()));
}
}),
)?;
db.one_column::<u32, _>("PRAGMA user_version", [])?;
drop(db);
let mut db = Connection::open_in_memory()?;
db.trace_v2(TraceEventCodes::empty(), None::<fn(TraceEvent<'_>)>)
}
#[test]
#[cfg(feature = "blob")]
pub fn null_sql() -> Result<()> {
let mut db = Connection::open_in_memory()?;
let sql = "CREATE TABLE test (content BLOB);
INSERT INTO test VALUES (ZEROBLOB(10));";
db.execute_batch(sql)?;
let rowid = db.last_insert_rowid();
db.trace_v2(
TraceEventCodes::SQLITE_TRACE_ROW,
Some(|e: TraceEvent<'_>| {
if let TraceEvent::Row(s) = e {
assert_eq!(s.sql(), "");
}
}),
)?;
db.blob_open(crate::MAIN_DB, c"test", c"content", rowid, true)?;
Ok(())
}
#[test]
#[cfg_attr(all(target_family = "wasm", target_os = "unknown"), ignore)]
pub fn already_borrowed() {
thread_local! {
static CONN: RefCell<Option<Connection>> = const { RefCell::new(None) };
}
CONN.with_borrow_mut(|c| c.replace(Connection::open_in_memory().unwrap()));
let budget: Vec<u8> = vec![0];
CONN.with_borrow_mut(|c| {
c.as_mut()
.unwrap()
.trace_v2(
TraceEventCodes::SQLITE_TRACE_STMT,
Some(move |e: TraceEvent<'_>| {
if let TraceEvent::Stmt(_, sql) = e {
println!("{}", sql);
}
CONN.with_borrow_mut(|c| {
c.as_mut()
.unwrap()
.trace_v2(
TraceEventCodes::SQLITE_TRACE_STMT,
None::<fn(TraceEvent)>,
)
.unwrap();
});
println!("{}", budget[0]);
}),
)
.unwrap();
c.as_ref()
.unwrap()
.query_one("SELECT 1;", [], |r| r.get::<_, bool>(0))
.unwrap();
});
}
}