use crate::flags::{AccessFlags, LockLevel, OpenOpts, ShmLockMode};
use crate::logger::SqliteLogger;
use crate::vars::SQLITE_ERROR;
use crate::{ffi, vars};
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::ffi::CString;
use alloc::format;
use alloc::string::String;
use core::mem::{ManuallyDrop, size_of};
use core::slice;
use core::{
ffi::{CStr, c_char, c_int, c_void},
ptr::{NonNull, null_mut},
};
pub const MIN_SQLITE_VERSION_NUMBER: i32 = 3043000;
const DEFAULT_MAX_PATH_LEN: i32 = 512;
pub const DEFAULT_SECTOR_SIZE: i32 = 4096;
pub const DEFAULT_DEVICE_CHARACTERISTICS: i32 =
vars::SQLITE_IOCAP_ATOMIC |
vars::SQLITE_IOCAP_POWERSAFE_OVERWRITE |
vars::SQLITE_IOCAP_SAFE_APPEND |
vars::SQLITE_IOCAP_SEQUENTIAL;
pub type SqliteErr = i32;
pub type VfsResult<T> = Result<T, SqliteErr>;
#[repr(C)]
struct FileWrapper<Handle> {
file: ffi::sqlite3_file,
vfs: *mut ffi::sqlite3_vfs,
handle: Handle,
}
struct AppData<Vfs> {
base_vfs: *mut ffi::sqlite3_vfs,
vfs: Vfs,
io_methods: ffi::sqlite3_io_methods,
sqlite_api: SqliteApi,
}
#[derive(Debug)]
pub struct Pragma<'a> {
pub name: &'a str,
pub arg: Option<&'a str>,
}
#[derive(Debug)]
pub enum PragmaErr {
NotFound,
Fail(SqliteErr, Option<String>),
}
impl PragmaErr {
pub fn required_arg(p: &Pragma<'_>) -> Self {
PragmaErr::Fail(
SQLITE_ERROR,
Some(format!(
"argument required (e.g. `pragma {} = ...`)",
p.name
)),
)
}
}
fn fallible(mut cb: impl FnMut() -> Result<i32, SqliteErr>) -> i32 {
cb().unwrap_or_else(|err| err)
}
unsafe fn lossy_cstr<'a>(p: *const c_char) -> VfsResult<Cow<'a, str>> {
unsafe {
p.as_ref()
.map(|p| CStr::from_ptr(p).to_string_lossy())
.ok_or(vars::SQLITE_INTERNAL)
}
}
macro_rules! unwrap_appdata {
($p_vfs:expr, $t_vfs:ty) => {
unsafe {
let out: VfsResult<&AppData<$t_vfs>> = (*$p_vfs)
.pAppData
.cast::<AppData<$t_vfs>>()
.as_ref()
.ok_or(vars::SQLITE_INTERNAL);
out
}
};
}
macro_rules! unwrap_vfs {
($p_vfs:expr, $t_vfs:ty) => {{
let out: VfsResult<&$t_vfs> = unwrap_appdata!($p_vfs, $t_vfs).map(|app_data| &app_data.vfs);
out
}};
}
macro_rules! unwrap_base_vfs {
($p_vfs:expr, $t_vfs:ty) => {{
let out: VfsResult<&mut ffi::sqlite3_vfs> =
unwrap_appdata!($p_vfs, $t_vfs).and_then(|app_data| {
unsafe { app_data.base_vfs.as_mut() }.ok_or(vars::SQLITE_INTERNAL)
});
out
}};
}
macro_rules! unwrap_file {
($p_file:expr, $t_vfs:ty) => {
unsafe {
let out: VfsResult<&mut FileWrapper<<$t_vfs>::Handle>> = $p_file
.cast::<FileWrapper<<$t_vfs>::Handle>>()
.as_mut()
.ok_or(vars::SQLITE_INTERNAL);
out
}
};
}
pub trait VfsHandle: Send {
fn readonly(&self) -> bool;
fn in_memory(&self) -> bool;
}
#[allow(unused_variables)]
pub trait Vfs: Send + Sync {
type Handle: VfsHandle;
fn canonical_path<'a>(&self, path: Cow<'a, str>) -> VfsResult<Cow<'a, str>> {
Ok(path)
}
fn open(&self, path: Option<&str>, opts: OpenOpts) -> VfsResult<Self::Handle>;
fn delete(&self, path: &str) -> VfsResult<()>;
fn access(&self, path: &str, flags: AccessFlags) -> VfsResult<bool>;
fn file_size(&self, handle: &mut Self::Handle) -> VfsResult<usize>;
fn truncate(&self, handle: &mut Self::Handle, size: usize) -> VfsResult<()>;
fn write(&self, handle: &mut Self::Handle, offset: usize, data: &[u8]) -> VfsResult<usize>;
fn read(&self, handle: &mut Self::Handle, offset: usize, data: &mut [u8]) -> VfsResult<usize>;
fn lock(&self, handle: &mut Self::Handle, level: LockLevel) -> VfsResult<()>;
fn unlock(&self, handle: &mut Self::Handle, level: LockLevel) -> VfsResult<()>;
fn check_reserved_lock(&self, handle: &mut Self::Handle) -> VfsResult<bool>;
fn sync(&self, handle: &mut Self::Handle) -> VfsResult<()> {
Ok(())
}
fn close(&self, handle: Self::Handle) -> VfsResult<()>;
fn pragma(
&self,
handle: &mut Self::Handle,
pragma: Pragma<'_>,
) -> Result<Option<String>, PragmaErr> {
Err(PragmaErr::NotFound)
}
fn sector_size(&self, handle: &mut Self::Handle) -> VfsResult<i32> {
Ok(DEFAULT_SECTOR_SIZE)
}
fn device_characteristics(&self, handle: &mut Self::Handle) -> VfsResult<i32> {
Ok(DEFAULT_DEVICE_CHARACTERISTICS)
}
fn shm_map(
&self,
handle: &mut Self::Handle,
region_idx: usize,
region_size: usize,
extend: bool,
) -> VfsResult<Option<NonNull<u8>>> {
Err(vars::SQLITE_READONLY_CANTINIT)
}
fn shm_lock(
&self,
handle: &mut Self::Handle,
offset: u32,
count: u32,
mode: ShmLockMode,
) -> VfsResult<()> {
Err(vars::SQLITE_IOERR)
}
fn shm_barrier(&self, handle: &mut Self::Handle) {}
fn shm_unmap(&self, handle: &mut Self::Handle, delete: bool) -> VfsResult<()> {
Err(vars::SQLITE_IOERR)
}
}
#[derive(Clone)]
pub struct SqliteApi {
register: unsafe extern "C" fn(arg1: *mut ffi::sqlite3_vfs, arg2: c_int) -> c_int,
find: unsafe extern "C" fn(arg1: *const c_char) -> *mut ffi::sqlite3_vfs,
mprintf: unsafe extern "C" fn(arg1: *const c_char, ...) -> *mut c_char,
log: unsafe extern "C" fn(arg1: c_int, arg2: *const c_char, ...),
libversion_number: unsafe extern "C" fn() -> c_int,
}
impl SqliteApi {
#[cfg(feature = "static")]
pub fn new_static() -> Self {
Self {
register: ffi::sqlite3_vfs_register,
find: ffi::sqlite3_vfs_find,
mprintf: ffi::sqlite3_mprintf,
log: ffi::sqlite3_log,
libversion_number: ffi::sqlite3_libversion_number,
}
}
#[cfg(feature = "dynamic")]
pub unsafe fn new_dynamic(api: &ffi::sqlite3_api_routines) -> VfsResult<Self> {
Ok(Self {
register: api.vfs_register.ok_or(vars::SQLITE_INTERNAL)?,
find: api.vfs_find.ok_or(vars::SQLITE_INTERNAL)?,
mprintf: api.mprintf.ok_or(vars::SQLITE_INTERNAL)?,
log: api.log.ok_or(vars::SQLITE_INTERNAL)?,
libversion_number: api.libversion_number.ok_or(vars::SQLITE_INTERNAL)?,
})
}
pub unsafe fn mprintf(&self, s: &str, out: *mut *const c_char) -> VfsResult<()> {
let s = CString::new(s).map_err(|_| vars::SQLITE_INTERNAL)?;
let p = unsafe { (self.mprintf)(s.as_ptr()) };
if p.is_null() {
Err(vars::SQLITE_NOMEM)
} else {
unsafe {
*out = p;
}
Ok(())
}
}
}
pub struct RegisterOpts {
pub make_default: bool,
}
#[cfg(feature = "static")]
pub fn register_static<T: Vfs>(
name: CString,
vfs: T,
opts: RegisterOpts,
) -> VfsResult<SqliteLogger> {
register_inner(SqliteApi::new_static(), name, vfs, opts)
}
#[cfg(feature = "dynamic")]
pub unsafe fn register_dynamic<T: Vfs>(
p_api: *mut ffi::sqlite3_api_routines,
name: CString,
vfs: T,
opts: RegisterOpts,
) -> VfsResult<SqliteLogger> {
let api = unsafe { p_api.as_ref() }.ok_or(vars::SQLITE_INTERNAL)?;
let sqlite_api = unsafe { SqliteApi::new_dynamic(api)? };
register_inner(sqlite_api, name, vfs, opts)
}
fn register_inner<T: Vfs>(
sqlite_api: SqliteApi,
name: CString,
vfs: T,
opts: RegisterOpts,
) -> VfsResult<SqliteLogger> {
let version = unsafe { (sqlite_api.libversion_number)() };
if version < MIN_SQLITE_VERSION_NUMBER {
panic!(
"sqlite3 must be at least version {}, found version {}",
MIN_SQLITE_VERSION_NUMBER, version
);
}
let io_methods = ffi::sqlite3_io_methods {
iVersion: 3,
xClose: Some(x_close::<T>),
xRead: Some(x_read::<T>),
xWrite: Some(x_write::<T>),
xTruncate: Some(x_truncate::<T>),
xSync: Some(x_sync::<T>),
xFileSize: Some(x_file_size::<T>),
xLock: Some(x_lock::<T>),
xUnlock: Some(x_unlock::<T>),
xCheckReservedLock: Some(x_check_reserved_lock::<T>),
xFileControl: Some(x_file_control::<T>),
xSectorSize: Some(x_sector_size::<T>),
xDeviceCharacteristics: Some(x_device_characteristics::<T>),
xShmMap: Some(x_shm_map::<T>),
xShmLock: Some(x_shm_lock::<T>),
xShmBarrier: Some(x_shm_barrier::<T>),
xShmUnmap: Some(x_shm_unmap::<T>),
xFetch: None,
xUnfetch: None,
};
let logger = SqliteLogger::new(sqlite_api.log);
let p_name = ManuallyDrop::new(name).as_ptr();
let base_vfs = unsafe { (sqlite_api.find)(null_mut()) };
let vfs_register = sqlite_api.register;
let p_appdata = Box::into_raw(Box::new(AppData { base_vfs, vfs, io_methods, sqlite_api }));
let filewrapper_size: c_int = size_of::<FileWrapper<T::Handle>>()
.try_into()
.map_err(|_| vars::SQLITE_INTERNAL)?;
let p_vfs = Box::into_raw(Box::new(ffi::sqlite3_vfs {
iVersion: 3,
szOsFile: filewrapper_size,
mxPathname: DEFAULT_MAX_PATH_LEN,
pNext: null_mut(),
zName: p_name,
pAppData: p_appdata.cast(),
xOpen: Some(x_open::<T>),
xDelete: Some(x_delete::<T>),
xAccess: Some(x_access::<T>),
xFullPathname: Some(x_full_pathname::<T>),
xDlOpen: Some(x_dlopen::<T>),
xDlError: Some(x_dlerror::<T>),
xDlSym: Some(x_dlsym::<T>),
xDlClose: Some(x_dlclose::<T>),
xRandomness: Some(x_randomness::<T>),
xSleep: Some(x_sleep::<T>),
xCurrentTime: Some(x_current_time::<T>),
xGetLastError: None,
xCurrentTimeInt64: Some(x_current_time_int64::<T>),
xSetSystemCall: None,
xGetSystemCall: None,
xNextSystemCall: None,
}));
let result = unsafe { vfs_register(p_vfs, opts.make_default.into()) };
if result != vars::SQLITE_OK {
unsafe {
drop(Box::from_raw(p_vfs));
drop(Box::from_raw(p_appdata));
drop(CString::from_raw(p_name as *mut c_char));
};
Err(result)
} else {
Ok(logger)
}
}
unsafe extern "C" fn x_open<T: Vfs>(
p_vfs: *mut ffi::sqlite3_vfs,
z_name: ffi::sqlite3_filename,
p_file: *mut ffi::sqlite3_file,
flags: c_int,
p_out_flags: *mut c_int,
) -> c_int {
fallible(|| {
let opts = flags.into();
let name = unsafe { lossy_cstr(z_name) }.ok();
let vfs = unwrap_vfs!(p_vfs, T)?;
let handle = vfs.open(name.as_ref().map(|s| s.as_ref()), opts)?;
let appdata = unwrap_appdata!(p_vfs, T)?;
if let Some(p_out_flags) = unsafe { p_out_flags.as_mut() } {
let mut out_flags = flags;
if handle.readonly() {
out_flags |= vars::SQLITE_OPEN_READONLY;
}
if handle.in_memory() {
out_flags |= vars::SQLITE_OPEN_MEMORY;
}
*p_out_flags = out_flags;
}
let out_file = p_file.cast::<FileWrapper<T::Handle>>();
unsafe {
core::ptr::write(
out_file,
FileWrapper {
file: ffi::sqlite3_file { pMethods: &appdata.io_methods },
vfs: p_vfs,
handle,
},
);
}
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_delete<T: Vfs>(
p_vfs: *mut ffi::sqlite3_vfs,
z_name: ffi::sqlite3_filename,
_sync_dir: c_int,
) -> c_int {
fallible(|| {
let name = unsafe { lossy_cstr(z_name)? };
let vfs = unwrap_vfs!(p_vfs, T)?;
vfs.delete(&name)?;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_access<T: Vfs>(
p_vfs: *mut ffi::sqlite3_vfs,
z_name: ffi::sqlite3_filename,
flags: c_int,
p_res_out: *mut c_int,
) -> c_int {
fallible(|| {
let name = unsafe { lossy_cstr(z_name)? };
let vfs = unwrap_vfs!(p_vfs, T)?;
let result = vfs.access(&name, flags.into())?;
let out = unsafe { p_res_out.as_mut() }.ok_or(vars::SQLITE_IOERR_ACCESS)?;
*out = result as i32;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_full_pathname<T: Vfs>(
p_vfs: *mut ffi::sqlite3_vfs,
z_name: ffi::sqlite3_filename,
n_out: c_int,
z_out: *mut c_char,
) -> c_int {
fallible(|| {
let name = unsafe { lossy_cstr(z_name)? };
let vfs = unwrap_vfs!(p_vfs, T)?;
let full_name = vfs.canonical_path(name)?;
let n_out = n_out.try_into().map_err(|_| vars::SQLITE_INTERNAL)?;
let out = unsafe { slice::from_raw_parts_mut(z_out as *mut u8, n_out) };
let from = &full_name.as_bytes()[..full_name.len().min(n_out - 1)];
out[..from.len()].copy_from_slice(from);
out[from.len()] = 0;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_close<T: Vfs>(p_file: *mut ffi::sqlite3_file) -> c_int {
fallible(|| {
let (vfs, handle) = unsafe {
let p_file_ref = p_file.as_mut().ok_or(vars::SQLITE_INTERNAL)?;
p_file_ref.pMethods = core::ptr::null();
let file = core::ptr::read(p_file.cast::<FileWrapper<T::Handle>>());
(file.vfs, file.handle)
};
let vfs = unwrap_vfs!(vfs, T)?;
vfs.close(handle)?;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_read<T: Vfs>(
p_file: *mut ffi::sqlite3_file,
buf: *mut c_void,
i_amt: c_int,
i_ofst: ffi::sqlite_int64,
) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
let buf_len: usize = i_amt.try_into().map_err(|_| vars::SQLITE_IOERR_READ)?;
let offset: usize = i_ofst.try_into().map_err(|_| vars::SQLITE_IOERR_READ)?;
let buf = unsafe { slice::from_raw_parts_mut(buf.cast::<u8>(), buf_len) };
vfs.read(&mut file.handle, offset, buf)?;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_write<T: Vfs>(
p_file: *mut ffi::sqlite3_file,
buf: *const c_void,
i_amt: c_int,
i_ofst: ffi::sqlite_int64,
) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
let buf_len: usize = i_amt.try_into().map_err(|_| vars::SQLITE_IOERR_WRITE)?;
let offset: usize = i_ofst.try_into().map_err(|_| vars::SQLITE_IOERR_WRITE)?;
let buf = unsafe { slice::from_raw_parts(buf.cast::<u8>(), buf_len) };
let n = vfs.write(&mut file.handle, offset, buf)?;
if n != buf_len {
return Err(vars::SQLITE_IOERR_WRITE);
}
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_truncate<T: Vfs>(
p_file: *mut ffi::sqlite3_file,
size: ffi::sqlite_int64,
) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
let size: usize = size.try_into().map_err(|_| vars::SQLITE_IOERR_TRUNCATE)?;
vfs.truncate(&mut file.handle, size)?;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_sync<T: Vfs>(p_file: *mut ffi::sqlite3_file, _flags: c_int) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
vfs.sync(&mut file.handle)?;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_file_size<T: Vfs>(
p_file: *mut ffi::sqlite3_file,
p_size: *mut ffi::sqlite3_int64,
) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
let size = vfs.file_size(&mut file.handle)?;
let p_size = unsafe { p_size.as_mut() }.ok_or(vars::SQLITE_INTERNAL)?;
*p_size = size.try_into().map_err(|_| vars::SQLITE_IOERR_FSTAT)?;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_lock<T: Vfs>(p_file: *mut ffi::sqlite3_file, raw_lock: c_int) -> c_int {
fallible(|| {
let level: LockLevel = raw_lock.into();
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
vfs.lock(&mut file.handle, level)?;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_unlock<T: Vfs>(p_file: *mut ffi::sqlite3_file, raw_lock: c_int) -> c_int {
fallible(|| {
let level: LockLevel = raw_lock.into();
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
vfs.unlock(&mut file.handle, level)?;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_check_reserved_lock<T: Vfs>(
p_file: *mut ffi::sqlite3_file,
p_out: *mut c_int,
) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
unsafe {
*p_out = vfs.check_reserved_lock(&mut file.handle)? as c_int;
}
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_file_control<T: Vfs>(
p_file: *mut ffi::sqlite3_file,
op: c_int,
p_arg: *mut c_void,
) -> c_int {
if op == vars::SQLITE_FCNTL_PRAGMA {
return fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
let args = p_arg.cast::<*const c_char>();
let name = unsafe { lossy_cstr(*args.add(1)) }?;
let arg = unsafe {
(*args.add(2))
.as_ref()
.map(|p| CStr::from_ptr(p).to_string_lossy())
};
let pragma = Pragma { name: &name, arg: arg.as_deref() };
let (result, msg) = match vfs.pragma(&mut file.handle, pragma) {
Ok(msg) => (Ok(vars::SQLITE_OK), msg),
Err(PragmaErr::NotFound) => (Err(vars::SQLITE_NOTFOUND), None),
Err(PragmaErr::Fail(err, msg)) => (Err(err), msg),
};
if let Some(msg) = msg {
let appdata = unwrap_appdata!(file.vfs, T)?;
unsafe { appdata.sqlite_api.mprintf(&msg, args)? };
}
result
});
}
vars::SQLITE_NOTFOUND
}
unsafe extern "C" fn x_sector_size<T: Vfs>(p_file: *mut ffi::sqlite3_file) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
vfs.sector_size(&mut file.handle)
})
}
unsafe extern "C" fn x_device_characteristics<T: Vfs>(p_file: *mut ffi::sqlite3_file) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
vfs.device_characteristics(&mut file.handle)
})
}
unsafe extern "C" fn x_shm_map<T: Vfs>(
p_file: *mut ffi::sqlite3_file,
pg: c_int,
pgsz: c_int,
extend: c_int,
p_page: *mut *mut c_void,
) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
if let Some(region) = vfs.shm_map(
&mut file.handle,
pg.try_into().map_err(|_| vars::SQLITE_IOERR)?,
pgsz.try_into().map_err(|_| vars::SQLITE_IOERR)?,
extend != 0,
)? {
unsafe { *p_page = region.as_ptr() as *mut c_void }
} else {
unsafe { *p_page = null_mut() }
}
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_shm_lock<T: Vfs>(
p_file: *mut ffi::sqlite3_file,
offset: c_int,
n: c_int,
flags: c_int,
) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
vfs.shm_lock(
&mut file.handle,
offset.try_into().map_err(|_| vars::SQLITE_IOERR)?,
n.try_into().map_err(|_| vars::SQLITE_IOERR)?,
ShmLockMode::try_from(flags)?,
)?;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_shm_barrier<T: Vfs>(p_file: *mut ffi::sqlite3_file) {
if let Ok(file) = unwrap_file!(p_file, T) {
if let Ok(vfs) = unwrap_vfs!(file.vfs, T) {
vfs.shm_barrier(&mut file.handle)
}
}
}
unsafe extern "C" fn x_shm_unmap<T: Vfs>(
p_file: *mut ffi::sqlite3_file,
delete_flag: c_int,
) -> c_int {
fallible(|| {
let file = unwrap_file!(p_file, T)?;
let vfs = unwrap_vfs!(file.vfs, T)?;
vfs.shm_unmap(&mut file.handle, delete_flag != 0)?;
Ok(vars::SQLITE_OK)
})
}
unsafe extern "C" fn x_dlopen<T: Vfs>(
p_vfs: *mut ffi::sqlite3_vfs,
z_path: *const c_char,
) -> *mut c_void {
if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
if let Some(x_dlopen) = vfs.xDlOpen {
return unsafe { x_dlopen(vfs, z_path) };
}
}
null_mut()
}
unsafe extern "C" fn x_dlerror<T: Vfs>(
p_vfs: *mut ffi::sqlite3_vfs,
n_byte: c_int,
z_err_msg: *mut c_char,
) {
if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
if let Some(x_dlerror) = vfs.xDlError {
unsafe { x_dlerror(vfs, n_byte, z_err_msg) };
}
}
}
unsafe extern "C" fn x_dlsym<T: Vfs>(
p_vfs: *mut ffi::sqlite3_vfs,
p_handle: *mut c_void,
z_symbol: *const c_char,
) -> Option<unsafe extern "C" fn(arg1: *mut ffi::sqlite3_vfs, arg2: *mut c_void, arg3: *const c_char)>
{
if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
if let Some(x_dlsym) = vfs.xDlSym {
return unsafe { x_dlsym(vfs, p_handle, z_symbol) };
}
}
None
}
unsafe extern "C" fn x_dlclose<T: Vfs>(p_vfs: *mut ffi::sqlite3_vfs, p_handle: *mut c_void) {
if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
if let Some(x_dlclose) = vfs.xDlClose {
unsafe { x_dlclose(vfs, p_handle) };
}
}
}
unsafe extern "C" fn x_randomness<T: Vfs>(
p_vfs: *mut ffi::sqlite3_vfs,
n_byte: c_int,
z_out: *mut c_char,
) -> c_int {
if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
if let Some(x_randomness) = vfs.xRandomness {
return unsafe { x_randomness(vfs, n_byte, z_out) };
}
}
vars::SQLITE_INTERNAL
}
unsafe extern "C" fn x_sleep<T: Vfs>(p_vfs: *mut ffi::sqlite3_vfs, microseconds: c_int) -> c_int {
if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
if let Some(x_sleep) = vfs.xSleep {
return unsafe { x_sleep(vfs, microseconds) };
}
}
vars::SQLITE_INTERNAL
}
unsafe extern "C" fn x_current_time<T: Vfs>(
p_vfs: *mut ffi::sqlite3_vfs,
p_time: *mut f64,
) -> c_int {
if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
if let Some(x_current_time) = vfs.xCurrentTime {
return unsafe { x_current_time(vfs, p_time) };
}
}
vars::SQLITE_INTERNAL
}
unsafe extern "C" fn x_current_time_int64<T: Vfs>(
p_vfs: *mut ffi::sqlite3_vfs,
p_time: *mut i64,
) -> c_int {
if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
if let Some(x_current_time_int64) = vfs.xCurrentTimeInt64 {
return unsafe { x_current_time_int64(vfs, p_time) };
}
}
vars::SQLITE_INTERNAL
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use crate::{
flags::{CreateMode, OpenKind, OpenMode},
mock::*,
};
use alloc::{sync::Arc, vec::Vec};
use parking_lot::Mutex;
use rusqlite::{Connection, OpenFlags};
use std::{boxed::Box, io::Write, println};
fn log_handler(_: i32, arg2: &str) {
println!("{arg2}");
}
#[test]
fn sanity() -> Result<(), Box<dyn std::error::Error>> {
unsafe {
rusqlite::trace::config_log(Some(log_handler)).unwrap();
}
struct H {}
impl Hooks for H {
fn open(&mut self, path: &Option<&str>, opts: &OpenOpts) {
let path = path.unwrap();
if path == "main.db" {
assert!(!opts.delete_on_close());
assert_eq!(opts.kind(), OpenKind::MainDb);
assert_eq!(
opts.mode(),
OpenMode::ReadWrite { create: CreateMode::Create }
);
} else if path == "main.db-journal" {
assert!(!opts.delete_on_close());
assert_eq!(opts.kind(), OpenKind::MainJournal);
assert_eq!(
opts.mode(),
OpenMode::ReadWrite { create: CreateMode::Create }
);
} else {
panic!("unexpected path: {}", path);
}
}
}
let shared = Arc::new(Mutex::new(MockState::new(Box::new(H {}))));
let vfs = MockVfs::new(shared.clone());
let logger = register_static(
CString::new("mock").unwrap(),
vfs,
RegisterOpts { make_default: true },
)
.map_err(|_| "failed to register vfs")?;
shared.lock().setup_logger(logger);
let conn = Connection::open_with_flags_and_vfs(
"main.db",
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE,
"mock",
)?;
conn.execute("create table t (val int)", [])?;
conn.execute("insert into t (val) values (1)", [])?;
conn.execute("insert into t (val) values (2)", [])?;
conn.execute("pragma mock_test", [])?;
let n: i64 = conn.query_row("select sum(val) from t", [], |row| row.get(0))?;
assert_eq!(n, 3);
conn.execute("create table b (data blob)", [])?;
println!("inserting zero blob");
conn.execute("insert into b values (zeroblob(8192))", [])?;
let rowid = conn.last_insert_rowid();
let mut blob = conn.blob_open(rusqlite::MAIN_DB, "b", "data", rowid, false)?;
println!("writing to blob");
let n = blob.write(b"hello")?;
assert_eq!(n, 5);
blob.close()?;
let mut stmt = conn.prepare("select data from b")?;
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
let data: Vec<u8> = row.get(0)?;
assert_eq!(&data[0..5], b"hello");
}
drop(rows);
drop(stmt);
conn.close().expect("failed to close connection");
Ok(())
}
}