#![allow(unsafe_code)] #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
extern crate libsqlite3_sys as ffi;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use sqlite_wasm_rs as ffi;
use super::SqliteConnection;
use super::authorizer::{AuthorizerContext, AuthorizerDecision};
use super::collation_needed::{CollationNeededContext, SqliteTextRep};
use super::functions::{build_sql_function_args, process_sql_function_result};
use super::limits::SqliteLimit;
use super::serialized_database::SerializedDatabase;
use super::stmt::ensure_sqlite_ok;
use super::trace::{SqliteTraceEvent, SqliteTraceFlags, TRACE_PROFILE, TRACE_ROW, TRACE_STMT};
use super::update_hook::{SqliteChangeEvent, SqliteChangeOp};
use super::{BusyDecision, CommitDecision, ProgressDecision};
use super::{Sqlite, SqliteAggregateFunction};
use crate::deserialize::FromSqlRow;
use crate::result::Error::DatabaseError;
use crate::result::*;
use crate::serialize::ToSql;
use crate::sql_types::HasSqlType;
use crate::sqlite::{BorrowedSqliteConnection, SqliteFunctionBehavior};
use alloc::borrow::{Cow, ToOwned};
use alloc::boxed::Box;
use alloc::ffi::{CString, NulError};
use alloc::string::{String, ToString};
use core::ffi as libc;
use core::ffi::CStr;
use core::num::NonZeroU32;
use core::ptr::NonNull;
use core::{mem, ptr, slice, str};
pub(super) const SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE: i32 = 1020;
pub(super) const SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE: i32 = 1021;
pub(super) fn library_version_number() -> u32 {
let version = unsafe { ffi::sqlite3_libversion_number() };
version.cast_unsigned()
}
macro_rules! assert_fail {
($fmt:expr_2021 $(,$args:tt)*) => {
#[cfg(feature = "std")]
eprint!(concat!(
$fmt,
"If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\n",
"Source location: {}:{}\n",
), $($args,)* file!(), line!());
crate::util::std_compat::abort()
};
}
#[derive(Default)]
pub(super) struct Hooks {
update_hook: Option<Box<dyn FnMut(SqliteChangeEvent<'_>) + Send>>,
commit_hook: Option<Box<dyn FnMut() -> CommitDecision + Send>>,
rollback_hook: Option<Box<dyn FnMut() + Send>>,
progress_hook: Option<Box<dyn FnMut() -> ProgressDecision + Send>>,
wal_hook: Option<Box<dyn Fn(&'_ mut BorrowedSqliteConnection, &str, u32) + Send>>,
busy_handler: Option<Box<dyn FnMut(i32) -> BusyDecision + Send>>,
authorizer_hook: Option<Box<dyn FnMut(AuthorizerContext<'_>) -> AuthorizerDecision + Send>>,
trace_hook: Option<Box<dyn FnMut(SqliteTraceEvent<'_>) + Send>>,
collation_needed_hook:
Option<Box<dyn Fn(CollationNeededContext<'_>) -> Option<Collation> + Send>>,
}
#[allow(missing_debug_implementations, missing_copy_implementations)]
pub(super) struct RawConnection {
pub(super) internal_connection: NonNull<ffi::sqlite3>,
hooks: Hooks,
}
type Collation = Box<dyn Fn(&str, &str) -> core::cmp::Ordering + Send + core::panic::UnwindSafe>;
impl RawConnection {
pub(super) fn from_ptr(conn: NonNull<ffi::sqlite3>) -> Self {
RawConnection {
internal_connection: conn,
hooks: Hooks::default(),
}
}
pub(super) fn establish(database_url: &str) -> ConnectionResult<Self> {
let mut conn_pointer = ptr::null_mut();
let database_url = if database_url.starts_with("sqlite://") {
CString::new(database_url.replacen("sqlite://", "file:", 1))?
} else {
CString::new(database_url)?
};
let flags = ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE | ffi::SQLITE_OPEN_URI;
let connection_status = unsafe {
ffi::sqlite3_open_v2(database_url.as_ptr(), &mut conn_pointer, flags, ptr::null())
};
match connection_status {
ffi::SQLITE_OK => {
let conn_pointer = unsafe { NonNull::new_unchecked(conn_pointer) };
Ok(RawConnection {
internal_connection: conn_pointer,
hooks: Hooks::default(),
})
}
err_code => {
let message = super::error_message(err_code);
unsafe { ffi::sqlite3_close(conn_pointer) };
Err(ConnectionError::BadConnection(message.into()))
}
}
}
pub(super) fn exec(&self, query: &str) -> QueryResult<()> {
let query = CString::new(query)?;
let callback_fn = None;
let callback_arg = ptr::null_mut();
let result = unsafe {
ffi::sqlite3_exec(
self.internal_connection.as_ptr(),
query.as_ptr(),
callback_fn,
callback_arg,
ptr::null_mut(),
)
};
ensure_sqlite_ok(result, self.internal_connection.as_ptr())
}
pub(super) fn rows_affected_by_last_query(
&self,
) -> Result<usize, Box<dyn core::error::Error + Send + Sync>> {
let r = unsafe { ffi::sqlite3_changes(self.internal_connection.as_ptr()) };
Ok(r.try_into()?)
}
pub(super) fn last_insert_rowid(&self) -> i64 {
unsafe { ffi::sqlite3_last_insert_rowid(self.internal_connection.as_ptr()) }
}
pub(super) fn is_autocommit(&self) -> bool {
unsafe { ffi::sqlite3_get_autocommit(self.internal_connection.as_ptr()) != 0 }
}
pub(super) fn register_sql_function<F, Ret, RetSqlType>(
&self,
fn_name: &str,
num_args: usize,
behavior: SqliteFunctionBehavior,
f: F,
) -> QueryResult<()>
where
F: FnMut(&Self, &mut [*mut ffi::sqlite3_value]) -> QueryResult<Ret>
+ core::panic::UnwindSafe
+ Send
+ 'static,
Ret: ToSql<RetSqlType, Sqlite>,
Sqlite: HasSqlType<RetSqlType>,
{
let c_fn_name = Self::get_fn_name(fn_name)?;
let flags = behavior.to_flags();
let num_args = num_args
.try_into()
.map_err(|e| Error::SerializationError(Box::new(e)))?;
let callback_fn = Box::into_raw(Box::new(CustomFunctionUserPtr {
callback: f,
function_name: fn_name.to_owned(),
}));
let result = unsafe {
ffi::sqlite3_create_function_v2(
self.internal_connection.as_ptr(),
c_fn_name.as_ptr(),
num_args,
flags,
callback_fn as *mut _,
Some(run_custom_function::<F, Ret, RetSqlType>),
None,
None,
Some(destroy_boxed::<CustomFunctionUserPtr<F>>),
)
};
Self::process_sql_function_result(result)
}
pub(super) fn register_aggregate_function<ArgsSqlType, RetSqlType, Args, Ret, A>(
&self,
fn_name: &str,
num_args: usize,
behavior: SqliteFunctionBehavior,
) -> QueryResult<()>
where
A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send + core::panic::UnwindSafe,
Args: FromSqlRow<ArgsSqlType, Sqlite>,
Ret: ToSql<RetSqlType, Sqlite>,
Sqlite: HasSqlType<RetSqlType>,
{
let fn_name = Self::get_fn_name(fn_name)?;
let flags = behavior.to_flags();
let num_args = num_args
.try_into()
.map_err(|e| Error::SerializationError(Box::new(e)))?;
let result = unsafe {
ffi::sqlite3_create_function_v2(
self.internal_connection.as_ptr(),
fn_name.as_ptr(),
num_args,
flags,
core::ptr::null_mut(),
None,
Some(run_aggregator_step_function::<_, _, _, _, A>),
Some(run_aggregator_final_function::<_, _, _, _, A>),
None,
)
};
Self::process_sql_function_result(result)
}
pub(super) fn register_collation_function<F>(
&self,
collation_name: &str,
collation: F,
) -> QueryResult<()>
where
F: Fn(&str, &str) -> core::cmp::Ordering + core::panic::UnwindSafe + Send + 'static,
{
let c_collation_name = Self::get_fn_name(collation_name)?;
let callback_fn = Box::into_raw(Box::new(CollationUserPtr {
callback: collation,
collation_name: collation_name.to_owned(),
}));
let result = unsafe {
ffi::sqlite3_create_collation_v2(
self.internal_connection.as_ptr(),
c_collation_name.as_ptr(),
ffi::SQLITE_UTF8,
callback_fn as *mut _,
Some(run_collation_function::<F>),
Some(destroy_boxed::<CollationUserPtr<F>>),
)
};
let result = Self::process_sql_function_result(result);
if result.is_err() {
destroy_boxed::<CollationUserPtr<F>>(callback_fn as *mut _);
}
result
}
pub(super) fn serialize(&mut self) -> SerializedDatabase {
let mut size: ffi::sqlite3_int64 = 0;
let data_ptr = unsafe {
ffi::sqlite3_serialize(
self.internal_connection.as_ptr(),
core::ptr::null(),
&mut size as *mut _,
0,
)
};
let data = match core::ptr::NonNull::new(data_ptr) {
Some(data) => data,
None if size == 0 => return SerializedDatabase::empty(),
None => return SerializedDatabase::allocation_failed(),
};
unsafe { SerializedDatabase::new(data, size) }
}
pub(super) fn deserialize(&mut self, data: &[u8]) -> QueryResult<()> {
let db_size = data
.len()
.try_into()
.map_err(|e| Error::DeserializationError(Box::new(e)))?;
let buffer = unsafe {
ffi::sqlite3_malloc64(data.len() as u64).cast::<u8>()
};
if buffer.is_null() && !data.is_empty() {
return Err(Error::DeserializationError(
"Failed to allocate the memory for the database buffer".into(),
));
}
if !data.is_empty() {
unsafe {
core::ptr::copy_nonoverlapping(data.as_ptr(), buffer, data.len());
}
}
#[allow(clippy::unnecessary_cast)]
unsafe {
let result = ffi::sqlite3_deserialize(
self.internal_connection.as_ptr(),
core::ptr::null(),
buffer,
db_size,
db_size,
ffi::SQLITE_DESERIALIZE_READONLY as u32
| ffi::SQLITE_DESERIALIZE_FREEONCLOSE as u32,
);
ensure_sqlite_ok(result, self.internal_connection.as_ptr())
}
}
pub(super) fn set_limit(&self, limit: SqliteLimit, value: u32) -> u32 {
let value = i32::try_from(value).unwrap_or(i32::MAX);
let previous =
unsafe { ffi::sqlite3_limit(self.internal_connection.as_ptr(), limit.to_ffi(), value) };
u32::try_from(previous).unwrap_or(0)
}
pub(super) fn get_limit(&self, limit: SqliteLimit) -> u32 {
let current = unsafe {
ffi::sqlite3_limit(self.internal_connection.as_ptr(), limit.to_ffi(), -1)
};
u32::try_from(current).unwrap_or(0)
}
pub(super) fn set_db_config_bool(&self, op: i32, value: bool) -> QueryResult<()> {
let mut result_value: libc::c_int = 0;
let new_value: libc::c_int = if value { 1 } else { 0 };
let result = unsafe {
ffi::sqlite3_db_config(
self.internal_connection.as_ptr(),
op,
new_value,
&mut result_value as *mut libc::c_int,
)
};
ensure_sqlite_ok(result, self.internal_connection.as_ptr())
}
pub(super) fn get_db_config_bool(&self, op: i32) -> QueryResult<bool> {
let mut current_value: libc::c_int = 0;
let result = unsafe {
ffi::sqlite3_db_config(
self.internal_connection.as_ptr(),
op,
-1_i32, &mut current_value as *mut libc::c_int,
)
};
ensure_sqlite_ok(result, self.internal_connection.as_ptr())?;
Ok(current_value != 0)
}
fn get_fn_name(fn_name: &str) -> Result<CString, NulError> {
CString::new(fn_name)
}
fn process_sql_function_result(result: i32) -> Result<(), Error> {
if result == ffi::SQLITE_OK {
Ok(())
} else {
let error_message = super::error_message(result);
Err(DatabaseError(
DatabaseErrorKind::Unknown,
Box::new(error_message.to_string()),
))
}
}
pub(super) fn blob_open<'conn>(
&'conn self,
database_name: &str,
table_name: &str,
column_name: &str,
row_id: i64,
) -> Result<super::sqlite_blob::SqliteReadOnlyBlob<'conn>, Error> {
let database_name = alloc::ffi::CString::new(database_name)?;
let column_name = alloc::ffi::CString::new(column_name)?;
let table_name = alloc::ffi::CString::new(table_name)?;
let mut blob: *mut ffi::sqlite3_blob = core::ptr::null_mut();
let ret = unsafe {
ffi::sqlite3_blob_open(
self.internal_connection.as_ptr(),
database_name.as_c_str().as_ptr(),
table_name.as_c_str().as_ptr(),
column_name.as_c_str().as_ptr(),
row_id,
0,
&mut blob,
)
};
Self::process_sql_function_result(ret)?;
let blob = unsafe { core::ptr::NonNull::new_unchecked(blob) };
let blob_size = unsafe { ffi::sqlite3_blob_bytes(blob.as_ptr()) };
let blob_size = usize::try_from(blob_size).map_err(Error::IntegerConversion)?;
Ok(super::sqlite_blob::SqliteReadOnlyBlob {
blob: Some(blob),
read_index: 0,
blob_size,
_pd: core::marker::PhantomData,
})
}
pub(super) fn set_update_hook<F>(&mut self, hook: F)
where
F: FnMut(SqliteChangeEvent<'_>) + Send + 'static,
{
let mut boxed: Box<dyn FnMut(SqliteChangeEvent<'_>) + Send> = Box::new(hook);
let ptr = &raw mut *boxed as *mut libc::c_void;
unsafe {
ffi::sqlite3_update_hook(
self.internal_connection.as_ptr(),
Some(update_hook_trampoline::<F>),
ptr,
);
}
self.hooks.update_hook = Some(boxed);
}
pub(super) fn remove_update_hook(&mut self) {
unsafe {
ffi::sqlite3_update_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
}
self.hooks.update_hook = None;
}
pub(super) fn set_commit_hook<F>(&mut self, hook: F)
where
F: FnMut() -> CommitDecision + Send + 'static,
{
let mut boxed: Box<dyn FnMut() -> CommitDecision + Send> = Box::new(hook);
let ptr = &raw mut *boxed as *mut libc::c_void;
unsafe {
ffi::sqlite3_commit_hook(
self.internal_connection.as_ptr(),
Some(commit_hook_trampoline::<F>),
ptr,
);
}
self.hooks.commit_hook = Some(boxed);
}
pub(super) fn remove_commit_hook(&mut self) {
unsafe {
ffi::sqlite3_commit_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
}
self.hooks.commit_hook = None;
}
pub(super) fn set_rollback_hook<F>(&mut self, hook: F)
where
F: FnMut() + Send + 'static,
{
let mut boxed: Box<dyn FnMut() + Send> = Box::new(hook);
let ptr = &raw mut *boxed as *mut libc::c_void;
unsafe {
ffi::sqlite3_rollback_hook(
self.internal_connection.as_ptr(),
Some(rollback_hook_trampoline::<F>),
ptr,
);
}
self.hooks.rollback_hook = Some(boxed);
}
pub(super) fn remove_rollback_hook(&mut self) {
unsafe {
ffi::sqlite3_rollback_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
}
self.hooks.rollback_hook = None;
}
pub(super) fn set_progress_handler<F>(&mut self, n: NonZeroU32, hook: F)
where
F: FnMut() -> ProgressDecision + Send + 'static,
{
let mut boxed: Box<dyn FnMut() -> ProgressDecision + Send> = Box::new(hook);
let ptr = &raw mut *boxed as *mut libc::c_void;
let n = i32::try_from(n.get()).unwrap_or(i32::MAX);
unsafe {
ffi::sqlite3_progress_handler(
self.internal_connection.as_ptr(),
n,
Some(progress_handler_trampoline::<F>),
ptr,
);
}
self.hooks.progress_hook = Some(boxed);
}
pub(super) fn remove_progress_handler(&mut self) {
unsafe {
ffi::sqlite3_progress_handler(
self.internal_connection.as_ptr(),
0,
None,
ptr::null_mut(),
);
}
self.hooks.progress_hook = None;
}
pub(super) fn set_wal_hook<F>(&mut self, hook: F)
where
F: Fn(&'_ mut BorrowedSqliteConnection, &str, u32) + Send + 'static,
{
let boxed: Box<dyn Fn(&'_ mut BorrowedSqliteConnection, &str, u32) + Send> = Box::new(hook);
let ptr = &raw const *boxed as *mut libc::c_void;
unsafe {
ffi::sqlite3_wal_hook(
self.internal_connection.as_ptr(),
Some(wal_hook_trampoline::<F>),
ptr,
);
}
self.hooks.wal_hook = Some(boxed);
}
pub(super) fn remove_wal_hook(&mut self) {
unsafe {
ffi::sqlite3_wal_hook(self.internal_connection.as_ptr(), None, ptr::null_mut());
}
self.hooks.wal_hook = None;
}
pub(super) fn set_busy_handler<F>(&mut self, hook: F)
where
F: FnMut(i32) -> BusyDecision + Send + 'static,
{
let mut boxed: Box<dyn FnMut(i32) -> BusyDecision + Send> = Box::new(hook);
let ptr = &raw mut *boxed as *mut libc::c_void;
unsafe {
ffi::sqlite3_busy_handler(
self.internal_connection.as_ptr(),
Some(busy_handler_trampoline::<F>),
ptr,
);
}
self.hooks.busy_handler = Some(boxed);
}
pub(super) fn remove_busy_handler(&mut self) {
unsafe {
ffi::sqlite3_busy_handler(self.internal_connection.as_ptr(), None, ptr::null_mut());
}
self.hooks.busy_handler = None;
}
pub(super) fn set_busy_timeout(&mut self, timeout: core::time::Duration) {
let ms = i32::try_from(timeout.as_nanos().div_ceil(1_000_000)).unwrap_or(i32::MAX);
unsafe {
ffi::sqlite3_busy_timeout(self.internal_connection.as_ptr(), ms);
}
self.hooks.busy_handler = None;
}
pub(super) fn set_authorizer<F>(&mut self, hook: F)
where
F: FnMut(AuthorizerContext<'_>) -> AuthorizerDecision + Send + 'static,
{
let mut boxed: Box<dyn FnMut(AuthorizerContext<'_>) -> AuthorizerDecision + Send> =
Box::new(hook);
let ptr = &raw mut *boxed as *mut libc::c_void;
unsafe {
ffi::sqlite3_set_authorizer(
self.internal_connection.as_ptr(),
Some(authorizer_trampoline::<F>),
ptr,
);
}
self.hooks.authorizer_hook = Some(boxed);
}
pub(super) fn remove_authorizer(&mut self) {
unsafe {
ffi::sqlite3_set_authorizer(self.internal_connection.as_ptr(), None, ptr::null_mut());
}
self.hooks.authorizer_hook = None;
}
pub(super) fn set_trace<F>(&mut self, mask: SqliteTraceFlags, hook: F)
where
F: FnMut(SqliteTraceEvent<'_>) + Send + 'static,
{
let mut boxed: Box<dyn FnMut(SqliteTraceEvent<'_>) + Send> = Box::new(hook);
let ptr = &raw mut *boxed as *mut libc::c_void;
unsafe {
ffi::sqlite3_trace_v2(
self.internal_connection.as_ptr(),
SqliteTraceFlags::from_bits_truncate(mask.bits()).bits(),
Some(trace_trampoline::<F>),
ptr,
);
}
self.hooks.trace_hook = Some(boxed);
}
pub(super) fn remove_trace(&mut self) {
unsafe {
ffi::sqlite3_trace_v2(self.internal_connection.as_ptr(), 0, None, ptr::null_mut());
}
self.hooks.trace_hook = None;
}
pub(super) fn set_collation_needed_hook<F>(&mut self, hook: F)
where
F: Fn(CollationNeededContext<'_>) -> Option<Collation> + Send + 'static,
{
let boxed: Box<dyn Fn(CollationNeededContext<'_>) -> Option<Collation> + Send> =
Box::new(hook);
let ptr = &raw const *boxed as *mut libc::c_void;
unsafe {
ffi::sqlite3_collation_needed(
self.internal_connection.as_ptr(),
ptr,
Some(collation_needed_trampoline::<F>),
);
}
self.hooks.collation_needed_hook = Some(boxed);
}
pub(super) fn remove_collation_needed_hook(&mut self) {
unsafe {
ffi::sqlite3_collation_needed(self.internal_connection.as_ptr(), ptr::null_mut(), None);
}
self.hooks.collation_needed_hook = None;
}
pub(crate) fn reset_registered_hooks(&mut self) -> Hooks {
let Self {
internal_connection,
hooks,
} = self;
let db = internal_connection.as_ptr();
let hooks = core::mem::take(hooks);
unsafe {
if hooks.update_hook.is_some() {
ffi::sqlite3_update_hook(db, None, ptr::null_mut());
}
if hooks.commit_hook.is_some() {
ffi::sqlite3_commit_hook(db, None, ptr::null_mut());
}
if hooks.rollback_hook.is_some() {
ffi::sqlite3_rollback_hook(db, None, ptr::null_mut());
}
if hooks.progress_hook.is_some() {
ffi::sqlite3_progress_handler(db, 0, None, ptr::null_mut());
}
if hooks.wal_hook.is_some() {
ffi::sqlite3_wal_hook(db, None, ptr::null_mut());
}
if hooks.busy_handler.is_some() {
ffi::sqlite3_busy_handler(db, None, ptr::null_mut());
}
if hooks.authorizer_hook.is_some() {
ffi::sqlite3_set_authorizer(db, None, ptr::null_mut());
}
if hooks.trace_hook.is_some() {
ffi::sqlite3_trace_v2(db, 0, None, ptr::null_mut());
}
if hooks.collation_needed_hook.is_some() {
ffi::sqlite3_collation_needed(db, ptr::null_mut(), None);
}
}
hooks
}
}
impl Drop for RawConnection {
fn drop(&mut self) {
use crate::util::std_compat::panicking;
let _hooks = self.reset_registered_hooks();
let close_result = unsafe { ffi::sqlite3_close(self.internal_connection.as_ptr()) };
if close_result != ffi::SQLITE_OK {
let error_message = super::error_message(close_result);
if panicking() {
#[cfg(feature = "std")]
eprintln!("Error closing SQLite connection: {error_message}");
} else {
panic!("Error closing SQLite connection: {error_message}");
}
}
}
}
enum SqliteCallbackError {
Abort(&'static str),
DieselError(crate::result::Error),
Panic(String),
}
impl SqliteCallbackError {
fn emit(&self, ctx: *mut ffi::sqlite3_context) {
let s;
let msg = match self {
SqliteCallbackError::Abort(msg) => *msg,
SqliteCallbackError::DieselError(e) => {
s = e.to_string();
&s
}
SqliteCallbackError::Panic(msg) => msg,
};
unsafe {
context_error_str(ctx, msg);
}
}
}
impl From<crate::result::Error> for SqliteCallbackError {
fn from(e: crate::result::Error) -> Self {
Self::DieselError(e)
}
}
struct CustomFunctionUserPtr<F> {
callback: F,
function_name: String,
}
#[allow(warnings)]
extern "C" fn run_custom_function<F, Ret, RetSqlType>(
ctx: *mut ffi::sqlite3_context,
num_args: libc::c_int,
value_ptr: *mut *mut ffi::sqlite3_value,
) where
F: FnMut(&RawConnection, &mut [*mut ffi::sqlite3_value]) -> QueryResult<Ret>
+ core::panic::UnwindSafe
+ Send
+ 'static,
Ret: ToSql<RetSqlType, Sqlite>,
Sqlite: HasSqlType<RetSqlType>,
{
use core::ops::Deref;
static NULL_DATA_ERR: &str = "An unknown error occurred. sqlite3_user_data returned a null pointer. This should never happen.";
static NULL_CONN_ERR: &str = "An unknown error occurred. sqlite3_context_db_handle returned a null pointer. This should never happen.";
let conn = match unsafe { NonNull::new(ffi::sqlite3_context_db_handle(ctx)) } {
Some(conn) => mem::ManuallyDrop::new(RawConnection::from_ptr(conn)),
None => {
unsafe { context_error_str(ctx, NULL_CONN_ERR) };
return;
}
};
let data_ptr = unsafe { ffi::sqlite3_user_data(ctx) };
let mut data_ptr = match NonNull::new(data_ptr as *mut CustomFunctionUserPtr<F>) {
None => unsafe {
context_error_str(ctx, NULL_DATA_ERR);
return;
},
Some(mut f) => f,
};
let data_ptr = unsafe { data_ptr.as_mut() };
let callback = core::panic::AssertUnwindSafe(&mut data_ptr.callback);
let conn = core::panic::AssertUnwindSafe(conn);
let result = crate::util::std_compat::catch_unwind(move || {
let _ = &callback;
let args = unsafe { slice::from_raw_parts_mut(value_ptr, num_args as _) };
let res = (callback.0)(&*conn, args)?;
let value = process_sql_function_result(&res)?;
unsafe {
value.result_of(&mut *ctx);
}
Ok(())
})
.unwrap_or_else(|p| Err(SqliteCallbackError::Panic(data_ptr.function_name.clone())));
if let Err(e) = result {
e.emit(ctx);
}
}
#[allow(warnings)]
extern "C" fn run_aggregator_step_function<ArgsSqlType, RetSqlType, Args, Ret, A>(
ctx: *mut ffi::sqlite3_context,
num_args: libc::c_int,
value_ptr: *mut *mut ffi::sqlite3_value,
) where
A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send + core::panic::UnwindSafe,
Args: FromSqlRow<ArgsSqlType, Sqlite>,
Ret: ToSql<RetSqlType, Sqlite>,
Sqlite: HasSqlType<RetSqlType>,
{
let result = crate::util::std_compat::catch_unwind(move || {
let args = unsafe { slice::from_raw_parts_mut(value_ptr, num_args as _) };
run_aggregator_step::<A, Args, ArgsSqlType>(ctx, args)
})
.unwrap_or_else(|e| {
Err(SqliteCallbackError::Panic(alloc::format!(
"{}::step() panicked",
core::any::type_name::<A>()
)))
});
match result {
Ok(()) => {}
Err(e) => e.emit(ctx),
}
}
fn run_aggregator_step<A, Args, ArgsSqlType>(
ctx: *mut ffi::sqlite3_context,
args: &mut [*mut ffi::sqlite3_value],
) -> Result<(), SqliteCallbackError>
where
A: SqliteAggregateFunction<Args>,
Args: FromSqlRow<ArgsSqlType, Sqlite>,
{
let aggregator = unsafe {
const {
if core::mem::size_of::<*mut A>() == 0 {
panic!(
"The pointer size is zero, that's unexpected.\
If you ever see this error message open a issue\
describing your environment"
);
}
}
let ctx = ffi::sqlite3_aggregate_context(
ctx,
core::mem::size_of::<*mut A>()
.try_into()
.expect("Memory size of a pointer is smaller than i32::MAX"),
)
.cast::<*mut A>();
if ctx.is_null() {
return Err(SqliteCallbackError::Abort(
"sqlite3_aggregate_context failed to allocate memory for the aggregate state",
));
}
let inner = &mut *ctx;
if inner.is_null() {
let obj = Box::into_raw(Box::new(A::default()));
*inner = obj;
}
&mut **inner
};
let connection = unsafe { NonNull::new(ffi::sqlite3_context_db_handle(ctx)) };
let connection = connection.ok_or(SqliteCallbackError::Abort(
"sqlite3_context_db_handle returned a null pointer. This should never happen",
))?;
let args = build_sql_function_args::<ArgsSqlType, Args>(args, connection)?;
aggregator.step(args);
Ok(())
}
extern "C" fn run_aggregator_final_function<ArgsSqlType, RetSqlType, Args, Ret, A>(
ctx: *mut ffi::sqlite3_context,
) where
A: SqliteAggregateFunction<Args, Output = Ret> + 'static + Send,
Args: FromSqlRow<ArgsSqlType, Sqlite>,
Ret: ToSql<RetSqlType, Sqlite>,
Sqlite: HasSqlType<RetSqlType>,
{
let result = crate::util::std_compat::catch_unwind(|| {
let aggregator = unsafe {
let ctx = ffi::sqlite3_aggregate_context(
ctx,
0,
)
.cast::<*mut A>();
if ctx.is_null() {
None
} else {
let inner = &mut *ctx;
if inner.is_null() {
None
} else {
let value = Box::from_raw(*inner);
let value = Some(*value);
*inner = core::ptr::null_mut();
value
}
}
};
let res = A::finalize(aggregator);
let value = process_sql_function_result(&res)?;
let r = unsafe { value.result_of(&mut *ctx) };
r.map_err(|e| {
SqliteCallbackError::DieselError(crate::result::Error::SerializationError(Box::new(e)))
})?;
Ok(())
})
.unwrap_or_else(|_e| {
Err(SqliteCallbackError::Panic(alloc::format!(
"{}::finalize() panicked",
core::any::type_name::<A>()
)))
});
if let Err(e) = result {
e.emit(ctx);
}
}
unsafe fn context_error_str(ctx: *mut ffi::sqlite3_context, error: &str) {
let len: i32 = error.len().try_into().unwrap_or(i32::MAX);
unsafe {
ffi::sqlite3_result_error(ctx, error.as_ptr() as *const _, len);
}
}
struct CollationUserPtr<F> {
callback: F,
collation_name: String,
}
#[allow(warnings)]
extern "C" fn run_collation_function<F>(
user_ptr: *mut libc::c_void,
lhs_len: libc::c_int,
lhs_ptr: *const libc::c_void,
rhs_len: libc::c_int,
rhs_ptr: *const libc::c_void,
) -> libc::c_int
where
F: Fn(&str, &str) -> core::cmp::Ordering + Send + core::panic::UnwindSafe + 'static,
{
let user_ptr = user_ptr as *const CollationUserPtr<F>;
let user_ptr = core::panic::AssertUnwindSafe(unsafe { user_ptr.as_ref() });
let result = crate::util::std_compat::catch_unwind(|| {
let user_ptr = user_ptr.ok_or_else(|| {
SqliteCallbackError::Abort(
"Got a null pointer as data pointer. This should never happen",
)
})?;
for (ptr, len, side) in &[(rhs_ptr, rhs_len, "rhs"), (lhs_ptr, lhs_len, "lhs")] {
if *len < 0 {
assert_fail!(
"An unknown error occurred. {}_len is negative. This should never happen.",
side
);
}
if ptr.is_null() {
assert_fail!(
"An unknown error occurred. {}_ptr is a null pointer. This should never happen.",
side
);
}
}
let (rhs, lhs) = unsafe {
(
str::from_utf8(slice::from_raw_parts(rhs_ptr as *const u8, rhs_len as _)),
str::from_utf8(slice::from_raw_parts(lhs_ptr as *const u8, lhs_len as _)),
)
};
let rhs =
rhs.map_err(|_| SqliteCallbackError::Abort("Got an invalid UTF-8 string for rhs"))?;
let lhs =
lhs.map_err(|_| SqliteCallbackError::Abort("Got an invalid UTF-8 string for lhs"))?;
Ok((user_ptr.callback)(rhs, lhs))
})
.unwrap_or_else(|p| {
Err(SqliteCallbackError::Panic(
user_ptr
.map(|u| u.collation_name.clone())
.unwrap_or_default(),
))
});
match result {
Ok(core::cmp::Ordering::Less) => -1,
Ok(core::cmp::Ordering::Equal) => 0,
Ok(core::cmp::Ordering::Greater) => 1,
Err(SqliteCallbackError::Abort(a)) => {
#[cfg(feature = "std")]
eprintln!(
"Collation function {} failed with: {}",
user_ptr
.map(|c| &c.collation_name as &str)
.unwrap_or_default(),
a
);
crate::util::std_compat::abort()
}
Err(SqliteCallbackError::DieselError(e)) => {
#[cfg(feature = "std")]
eprintln!(
"Collation function {} failed with: {}",
user_ptr
.map(|c| &c.collation_name as &str)
.unwrap_or_default(),
e
);
crate::util::std_compat::abort()
}
Err(SqliteCallbackError::Panic(msg)) => {
#[cfg(feature = "std")]
eprintln!("Collation function {} panicked", msg);
crate::util::std_compat::abort()
}
}
}
extern "C" fn destroy_boxed<F>(data: *mut libc::c_void) {
let ptr = data as *mut F;
unsafe { core::mem::drop(Box::from_raw(ptr)) };
}
unsafe extern "C" fn update_hook_trampoline<F>(
user_data: *mut libc::c_void,
op: libc::c_int,
db_name: *const libc::c_char,
table_name: *const libc::c_char,
rowid: ffi::sqlite3_int64,
) where
F: FnMut(SqliteChangeEvent<'_>),
{
let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
let hook = unsafe { &mut *(user_data as *mut F) };
let db_name = unsafe { CStr::from_ptr(db_name) }.to_string_lossy();
let table_name = unsafe { CStr::from_ptr(table_name) }.to_string_lossy();
hook(SqliteChangeEvent {
op: SqliteChangeOp::from_ffi(op),
db_name: &db_name,
table_name: &table_name,
rowid,
});
}));
if result.is_err() {
assert_fail!("Panic in sqlite3_update_hook trampoline. ");
}
}
unsafe extern "C" fn commit_hook_trampoline<F>(user_data: *mut libc::c_void) -> libc::c_int
where
F: FnMut() -> CommitDecision,
{
let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
let f = unsafe { &mut *(user_data as *mut F) };
f()
}));
match result {
Ok(CommitDecision::Rollback) => 1,
Ok(CommitDecision::Proceed) => 0,
Err(_) => {
assert_fail!("Panic in sqlite3_commit_hook trampoline. ");
}
}
}
unsafe extern "C" fn rollback_hook_trampoline<F>(user_data: *mut libc::c_void)
where
F: FnMut(),
{
let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
let f = unsafe { &mut *(user_data as *mut F) };
f();
}));
if result.is_err() {
assert_fail!("Panic in sqlite3_rollback_hook trampoline. ");
}
}
unsafe extern "C" fn progress_handler_trampoline<F>(user_data: *mut libc::c_void) -> libc::c_int
where
F: FnMut() -> ProgressDecision,
{
let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
let f = unsafe { &mut *(user_data as *mut F) };
f()
}));
match result {
Ok(ProgressDecision::Interrupt) => 1,
Ok(ProgressDecision::Continue) => 0,
Err(_) => {
assert_fail!("Panic in sqlite3_progress_handler trampoline. ");
}
}
}
unsafe extern "C" fn wal_hook_trampoline<F>(
user_data: *mut libc::c_void,
db: *mut ffi::sqlite3,
db_name: *const libc::c_char,
n_pages: libc::c_int,
) -> libc::c_int
where
F: Fn(&'_ mut BorrowedSqliteConnection, &str, u32),
{
let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
let f = unsafe { &*(user_data as *const F) };
let db_name: String = if db_name.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(db_name) }
.to_string_lossy()
.into_owned()
};
let n_pages = u32::try_from(n_pages).unwrap_or(0);
let Some(db) = NonNull::new(db) else {
return;
};
unsafe {
SqliteConnection::with_borrowed_connection(db, |conn| f(conn, &db_name, n_pages));
}
}));
if result.is_err() {
assert_fail!("Panic in sqlite3_wal_hook trampoline. ");
}
ffi::SQLITE_OK
}
unsafe extern "C" fn busy_handler_trampoline<F>(
user_data: *mut libc::c_void,
retry_count: libc::c_int,
) -> libc::c_int
where
F: FnMut(i32) -> BusyDecision,
{
let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
let f = unsafe { &mut *(user_data as *mut F) };
f(retry_count)
}));
match result {
Ok(BusyDecision::Retry) => 1,
Ok(BusyDecision::GiveUp) => 0,
Err(_) => {
assert_fail!("Panic in sqlite3_busy_handler trampoline. ");
}
}
}
unsafe extern "C" fn authorizer_trampoline<F>(
user_data: *mut libc::c_void,
action_code: libc::c_int,
arg1: *const libc::c_char,
arg2: *const libc::c_char,
db_name: *const libc::c_char,
accessor: *const libc::c_char,
) -> libc::c_int
where
F: FnMut(AuthorizerContext<'_>) -> AuthorizerDecision,
{
fn to_str<'a>(ptr: *const libc::c_char) -> Result<Option<&'a str>, core::str::Utf8Error> {
if ptr.is_null() {
Ok(None)
} else {
unsafe { CStr::from_ptr(ptr) }.to_str().map(Some)
}
}
let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
let (Ok(arg1), Ok(arg2), Ok(db_name), Ok(accessor)) = (
to_str(arg1),
to_str(arg2),
to_str(db_name),
to_str(accessor),
) else {
return AuthorizerDecision::Deny;
};
let f = unsafe { &mut *(user_data as *mut F) };
f(AuthorizerContext::from_ffi(
action_code,
arg1,
arg2,
db_name,
accessor,
))
}));
match result {
Ok(decision) => decision.to_ffi(),
Err(_) => {
assert_fail!("Panic in sqlite3_set_authorizer trampoline. ");
}
}
}
unsafe extern "C" fn trace_trampoline<F>(
event_code: libc::c_uint,
user_data: *mut libc::c_void,
p: *mut libc::c_void,
x: *mut libc::c_void,
) -> libc::c_int
where
F: FnMut(SqliteTraceEvent<'_>) + Send + 'static,
{
let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
let f = unsafe { &mut *(user_data as *mut F) };
match event_code {
TRACE_STMT => {
let stmt_ptr = p as *mut ffi::sqlite3_stmt;
let sql_ptr = x as *const libc::c_char;
if sql_ptr.is_null() {
return;
}
let sql = unsafe { CStr::from_ptr(sql_ptr) }.to_string_lossy();
let readonly =
!stmt_ptr.is_null() && unsafe { ffi::sqlite3_stmt_readonly(stmt_ptr) != 0 };
f(SqliteTraceEvent::Statement {
sql: &sql,
readonly,
});
}
TRACE_PROFILE => {
let stmt_ptr = p as *mut ffi::sqlite3_stmt;
let duration_ns = unsafe {
(x as *const ffi::sqlite3_int64).as_ref()
}
.copied()
.unwrap_or_default()
.cast_unsigned();
let readonly =
!stmt_ptr.is_null() && unsafe { ffi::sqlite3_stmt_readonly(stmt_ptr) != 0 };
let sql_ptr = if stmt_ptr.is_null() {
core::ptr::null()
} else {
unsafe { ffi::sqlite3_sql(stmt_ptr) }
};
let sql = if sql_ptr.is_null() {
Cow::Borrowed("")
} else {
unsafe { CStr::from_ptr(sql_ptr) }.to_string_lossy()
};
f(SqliteTraceEvent::Profile {
sql: &sql,
duration_ns,
readonly,
});
}
TRACE_ROW => f(SqliteTraceEvent::Row),
_ => {}
}
}));
if result.is_err() {
assert_fail!("Panic in sqlite3_trace_v2 trampoline. ");
}
0 }
unsafe extern "C" fn collation_needed_trampoline<F>(
user_data: *mut libc::c_void,
db: *mut ffi::sqlite3,
e_text_rep: libc::c_int,
name: *const libc::c_char,
) where
F: Fn(CollationNeededContext<'_>) -> Option<Collation>,
{
let result = crate::util::std_compat::catch_unwind(core::panic::AssertUnwindSafe(|| {
let f = unsafe { &*(user_data as *const F) };
let name: Cow<'_, str> = if name.is_null() {
Cow::Borrowed("")
} else {
unsafe { CStr::from_ptr(name) }.to_string_lossy()
};
let Some(db) = NonNull::new(db) else {
return;
};
let ctx = CollationNeededContext {
name: &name,
text_rep: SqliteTextRep::from_ffi(e_text_rep),
};
unsafe {
SqliteConnection::with_borrowed_connection(db, |conn| {
if let Some(collation) = f(ctx) {
let _ = conn.inner_mut().register_collation(ctx.name, collation);
}
});
}
}));
if result.is_err() {
assert_fail!("Panic in sqlite3_collation_needed trampoline. ");
}
}
#[cfg(all(test, not(miri)))]
mod tests {
use super::super::update_hook::SqliteChangeOp;
use super::*;
use std::sync::{Arc, Mutex};
fn test_connection() -> RawConnection {
RawConnection::establish(":memory:").expect("failed to establish :memory: connection")
}
#[test]
fn insert_event_dispatched_directly() {
let mut conn = test_connection();
conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
.unwrap();
let fired = Arc::new(Mutex::new(Vec::new()));
let f2 = fired.clone();
conn.set_update_hook(move |e| {
f2.lock().unwrap().push((e.op, e.rowid));
});
conn.exec("INSERT INTO t VALUES (1)").unwrap();
let events = fired.lock().unwrap();
assert_eq!(events.len(), 1);
assert_eq!(events[0].0, SqliteChangeOp::Insert);
assert_eq!(events[0].1, 1);
}
#[test]
fn consecutive_inserts_dispatch_immediately() {
let mut conn = test_connection();
conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
.unwrap();
let fired = Arc::new(Mutex::new(Vec::new()));
let f2 = fired.clone();
conn.set_update_hook(move |e| {
f2.lock().unwrap().push(e.rowid);
});
conn.exec("INSERT INTO t VALUES (2); INSERT INTO t VALUES (3)")
.unwrap();
let events = fired.lock().unwrap();
assert_eq!(events.len(), 2);
assert_eq!(events[0], 2);
assert_eq!(events[1], 3);
}
#[test]
fn update_event() {
let mut conn = test_connection();
conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")
.unwrap();
conn.exec("INSERT INTO t VALUES (1, 'a')").unwrap();
let fired = Arc::new(Mutex::new(Vec::new()));
let f2 = fired.clone();
conn.set_update_hook(move |e| {
f2.lock().unwrap().push((e.op, e.rowid));
});
conn.exec("UPDATE t SET v = 'b' WHERE id = 1").unwrap();
let events = fired.lock().unwrap();
assert_eq!(events.len(), 1);
assert_eq!(events[0].0, SqliteChangeOp::Update);
assert_eq!(events[0].1, 1);
}
#[test]
fn delete_event() {
let mut conn = test_connection();
conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
.unwrap();
conn.exec("INSERT INTO t VALUES (1)").unwrap();
let fired = Arc::new(Mutex::new(Vec::new()));
let f2 = fired.clone();
conn.set_update_hook(move |e| {
f2.lock().unwrap().push((e.op, e.rowid));
});
conn.exec("DELETE FROM t WHERE id = 1").unwrap();
let events = fired.lock().unwrap();
assert_eq!(events.len(), 1);
assert_eq!(events[0].0, SqliteChangeOp::Delete);
assert_eq!(events[0].1, 1);
}
#[test]
fn remove_stops_events() {
let mut conn = test_connection();
conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
.unwrap();
let fired = Arc::new(Mutex::new(Vec::new()));
let f2 = fired.clone();
conn.set_update_hook(move |e| {
f2.lock().unwrap().push(e.rowid);
});
conn.exec("INSERT INTO t VALUES (1)").unwrap();
assert_eq!(fired.lock().unwrap().len(), 1);
conn.remove_update_hook();
conn.exec("INSERT INTO t VALUES (2)").unwrap();
assert_eq!(fired.lock().unwrap().len(), 1); }
#[test]
fn replacing_hook_drops_old() {
let mut conn = test_connection();
conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
.unwrap();
let first = Arc::new(Mutex::new(Vec::new()));
let f1 = first.clone();
conn.set_update_hook(move |e| {
f1.lock().unwrap().push(e.rowid);
});
conn.exec("INSERT INTO t VALUES (1)").unwrap();
assert_eq!(first.lock().unwrap().len(), 1);
let second = Arc::new(Mutex::new(Vec::new()));
let f2 = second.clone();
conn.set_update_hook(move |e| {
f2.lock().unwrap().push(e.rowid);
});
conn.exec("INSERT INTO t VALUES (2)").unwrap();
assert_eq!(first.lock().unwrap().len(), 1); assert_eq!(*second.lock().unwrap(), vec![2]);
}
#[test]
fn drop_does_not_panic() {
let mut conn = test_connection();
conn.exec("CREATE TABLE t (id INTEGER PRIMARY KEY)")
.unwrap();
conn.set_update_hook(|_| {});
conn.exec("INSERT INTO t VALUES (1)").unwrap();
drop(conn);
}
}