sqlite_plugin/
vfs.rs

1use crate::flags::{AccessFlags, LockLevel, OpenOpts};
2use crate::logger::SqliteLogger;
3use crate::{ffi, vars};
4use alloc::borrow::Cow;
5use alloc::boxed::Box;
6use alloc::ffi::CString;
7use alloc::format;
8use alloc::string::String;
9use core::fmt::Display;
10use core::mem::{self, ManuallyDrop, MaybeUninit, size_of};
11use core::slice;
12use core::{
13    ffi::{CStr, c_char, c_int, c_void},
14    ptr::null_mut,
15};
16
17/// The minimim supported `SQLite` version.
18// If you need to make this earlier, make sure the tests are testing the earlier version
19pub const MIN_SQLITE_VERSION_NUMBER: i32 = 3044000;
20
21const DEFAULT_MAX_PATH_LEN: i32 = 512;
22pub const DEFAULT_SECTOR_SIZE: i32 = 4096;
23
24pub const DEFAULT_DEVICE_CHARACTERISTICS: i32 =
25    // writes of any size are atomic
26    vars::SQLITE_IOCAP_ATOMIC |
27    // after reboot following a crash or power loss, the only bytes in a file that were written
28    // at the application level might have changed and that adjacent bytes, even bytes within
29    // the same sector are guaranteed to be unchanged
30    vars::SQLITE_IOCAP_POWERSAFE_OVERWRITE |
31    // when data is appended to a file, the data is appended first then the size of the file is
32    // extended, never the other way around
33    vars::SQLITE_IOCAP_SAFE_APPEND |
34    // information is written to disk in the same order as calls to xWrite()
35    vars::SQLITE_IOCAP_SEQUENTIAL;
36
37/// A `SQLite3` extended error code
38pub type SqliteErr = i32;
39
40pub type VfsResult<T> = Result<T, SqliteErr>;
41
42// FileWrapper needs to be repr(C) and have sqlite3_file as it's first member
43// because it's a "subclass" of sqlite3_file
44#[repr(C)]
45struct FileWrapper<Handle> {
46    file: ffi::sqlite3_file,
47    vfs: *mut ffi::sqlite3_vfs,
48    handle: MaybeUninit<Handle>,
49}
50
51struct AppData<Vfs> {
52    base_vfs: *mut ffi::sqlite3_vfs,
53    vfs: Vfs,
54    io_methods: ffi::sqlite3_io_methods,
55    sqlite_api: SqliteApi,
56}
57
58#[derive(Debug)]
59pub struct Pragma<'a> {
60    pub name: &'a str,
61    pub arg: Option<&'a str>,
62}
63
64#[derive(Debug)]
65pub enum PragmaErr {
66    NotFound,
67    Fail(String),
68}
69
70impl PragmaErr {
71    pub fn required_arg(p: &Pragma<'_>) -> Self {
72        PragmaErr::Fail(format!(
73            "argument required (e.g. `pragma {} = ...`)",
74            p.name
75        ))
76    }
77}
78
79impl<T: Display> From<T> for PragmaErr {
80    fn from(value: T) -> Self {
81        PragmaErr::Fail(format!("{value}"))
82    }
83}
84
85fn fallible(mut cb: impl FnMut() -> Result<i32, SqliteErr>) -> i32 {
86    cb().unwrap_or_else(|err| err)
87}
88
89unsafe fn lossy_cstr<'a>(p: *const c_char) -> VfsResult<Cow<'a, str>> {
90    unsafe {
91        p.as_ref()
92            .map(|p| CStr::from_ptr(p).to_string_lossy())
93            .ok_or(vars::SQLITE_INTERNAL)
94    }
95}
96
97// uses sqlite3_mprintf to allocate memory for the string using sqlite's memory allocator
98// returns a pointer to the sqlite3 allocated string
99// # Safety
100// the returned pointer must be freed using sqlite3_free
101fn sqlite3_mprintf(api: &SqliteApi, s: &str) -> VfsResult<*mut c_char> {
102    let s = CString::new(s).map_err(|_| vars::SQLITE_INTERNAL)?;
103    let p = unsafe { (api.mprintf)(s.as_ptr()) };
104    if p.is_null() {
105        Err(vars::SQLITE_NOMEM)
106    } else {
107        Ok(p)
108    }
109}
110
111macro_rules! unwrap_appdata {
112    ($p_vfs:expr, $t_vfs:ty) => {
113        unsafe {
114            let out: VfsResult<&AppData<$t_vfs>> = (*$p_vfs)
115                .pAppData
116                .cast::<AppData<$t_vfs>>()
117                .as_ref()
118                .ok_or(vars::SQLITE_INTERNAL);
119            out
120        }
121    };
122}
123
124macro_rules! unwrap_vfs {
125    ($p_vfs:expr, $t_vfs:ty) => {{
126        let out: VfsResult<&$t_vfs> = unwrap_appdata!($p_vfs, $t_vfs).map(|app_data| &app_data.vfs);
127        out
128    }};
129}
130
131macro_rules! unwrap_base_vfs {
132    ($p_vfs:expr, $t_vfs:ty) => {{
133        let out: VfsResult<&mut ffi::sqlite3_vfs> =
134            unwrap_appdata!($p_vfs, $t_vfs).and_then(|app_data| {
135                unsafe { app_data.base_vfs.as_mut() }.ok_or(vars::SQLITE_INTERNAL)
136            });
137        out
138    }};
139}
140
141macro_rules! unwrap_file {
142    ($p_file:expr, $t_vfs:ty) => {
143        unsafe {
144            let out: VfsResult<&mut FileWrapper<<$t_vfs>::Handle>> = $p_file
145                .cast::<FileWrapper<<$t_vfs>::Handle>>()
146                .as_mut()
147                .ok_or(vars::SQLITE_INTERNAL);
148            out
149        }
150    };
151}
152
153pub trait VfsHandle: Send {
154    fn readonly(&self) -> bool;
155    fn in_memory(&self) -> bool;
156}
157
158#[allow(unused_variables)]
159pub trait Vfs: Send + Sync {
160    type Handle: VfsHandle;
161
162    /// Register the provided logger with this Vfs.
163    /// This function is guaranteed to only be called once per
164    /// register_{static,dynamic} call.
165    fn register_logger(&self, logger: SqliteLogger);
166
167    /// construct a canonical version of the given path
168    fn canonical_path<'a>(&self, path: Cow<'a, str>) -> VfsResult<Cow<'a, str>> {
169        Ok(path)
170    }
171
172    // file system operations
173    fn open(&self, path: Option<&str>, opts: OpenOpts) -> VfsResult<Self::Handle>;
174    fn delete(&self, path: &str) -> VfsResult<()>;
175    fn access(&self, path: &str, flags: AccessFlags) -> VfsResult<bool>;
176
177    // file operations
178    fn file_size(&self, handle: &mut Self::Handle) -> VfsResult<usize>;
179    fn truncate(&self, handle: &mut Self::Handle, size: usize) -> VfsResult<()>;
180    fn write(&self, handle: &mut Self::Handle, offset: usize, data: &[u8]) -> VfsResult<usize>;
181    fn read(&self, handle: &mut Self::Handle, offset: usize, data: &mut [u8]) -> VfsResult<usize>;
182
183    fn lock(&self, handle: &mut Self::Handle, level: LockLevel) -> VfsResult<()> {
184        Ok(())
185    }
186
187    fn unlock(&self, handle: &mut Self::Handle, level: LockLevel) -> VfsResult<()> {
188        Ok(())
189    }
190
191    fn sync(&self, handle: &mut Self::Handle) -> VfsResult<()> {
192        Ok(())
193    }
194
195    fn close(&self, handle: Self::Handle) -> VfsResult<()>;
196
197    fn pragma(
198        &self,
199        handle: &mut Self::Handle,
200        pragma: Pragma<'_>,
201    ) -> Result<Option<String>, PragmaErr> {
202        Err(PragmaErr::NotFound)
203    }
204
205    // system queries
206    fn sector_size(&self) -> i32 {
207        DEFAULT_SECTOR_SIZE
208    }
209
210    fn device_characteristics(&self) -> i32 {
211        DEFAULT_DEVICE_CHARACTERISTICS
212    }
213}
214
215#[derive(Clone)]
216pub(crate) struct SqliteApi {
217    register: unsafe extern "C" fn(arg1: *mut ffi::sqlite3_vfs, arg2: c_int) -> c_int,
218    find: unsafe extern "C" fn(arg1: *const c_char) -> *mut ffi::sqlite3_vfs,
219    mprintf: unsafe extern "C" fn(arg1: *const c_char, ...) -> *mut c_char,
220    log: unsafe extern "C" fn(arg1: c_int, arg2: *const c_char, ...),
221    libversion_number: unsafe extern "C" fn() -> c_int,
222}
223
224impl SqliteApi {
225    #[cfg(feature = "static")]
226    fn new_static() -> Self {
227        Self {
228            register: ffi::sqlite3_vfs_register,
229            find: ffi::sqlite3_vfs_find,
230            mprintf: ffi::sqlite3_mprintf,
231            log: ffi::sqlite3_log,
232            libversion_number: ffi::sqlite3_libversion_number,
233        }
234    }
235
236    #[cfg(feature = "dynamic")]
237    fn new_dynamic(api: &ffi::sqlite3_api_routines) -> VfsResult<Self> {
238        Ok(Self {
239            register: api.vfs_register.ok_or(vars::SQLITE_INTERNAL)?,
240            find: api.vfs_find.ok_or(vars::SQLITE_INTERNAL)?,
241            mprintf: api.mprintf.ok_or(vars::SQLITE_INTERNAL)?,
242            log: api.log.ok_or(vars::SQLITE_INTERNAL)?,
243            libversion_number: api.libversion_number.ok_or(vars::SQLITE_INTERNAL)?,
244        })
245    }
246}
247
248pub struct RegisterOpts {
249    pub make_default: bool,
250}
251
252#[cfg(feature = "static")]
253pub fn register_static<T: Vfs>(name: &str, vfs: T, opts: RegisterOpts) -> VfsResult<()> {
254    register_inner(SqliteApi::new_static(), name, vfs, opts)
255}
256
257/// Register a vfs with `SQLite` using the dynamic API. This API is available when
258/// `SQLite` is initializing extensions.
259/// # Safety
260/// `p_api` must be a valid, aligned pointer to a `sqlite3_api_routines` struct
261#[cfg(feature = "dynamic")]
262pub unsafe fn register_dynamic<T: Vfs>(
263    p_api: *mut ffi::sqlite3_api_routines,
264    name: &str,
265    vfs: T,
266    opts: RegisterOpts,
267) -> VfsResult<()> {
268    let api = unsafe { p_api.as_ref() }.ok_or(vars::SQLITE_INTERNAL)?;
269    let sqlite_api = SqliteApi::new_dynamic(api)?;
270    register_inner(sqlite_api, name, vfs, opts)
271}
272
273fn register_inner<T: Vfs>(
274    sqlite_api: SqliteApi,
275    name: &str,
276    vfs: T,
277    opts: RegisterOpts,
278) -> VfsResult<()> {
279    let version = unsafe { (sqlite_api.libversion_number)() };
280    if version < MIN_SQLITE_VERSION_NUMBER {
281        panic!(
282            "sqlite3 must be at least version {}, found version {}",
283            MIN_SQLITE_VERSION_NUMBER, version
284        );
285    }
286
287    let io_methods = ffi::sqlite3_io_methods {
288        iVersion: 3,
289        xClose: Some(x_close::<T>),
290        xRead: Some(x_read::<T>),
291        xWrite: Some(x_write::<T>),
292        xTruncate: Some(x_truncate::<T>),
293        xSync: Some(x_sync::<T>),
294        xFileSize: Some(x_file_size::<T>),
295        xLock: Some(x_lock::<T>),
296        xUnlock: Some(x_unlock::<T>),
297        xCheckReservedLock: None,
298        xFileControl: Some(x_file_control::<T>),
299        xSectorSize: Some(x_sector_size::<T>),
300        xDeviceCharacteristics: Some(x_device_characteristics::<T>),
301        xShmMap: None,
302        xShmLock: None,
303        xShmBarrier: None,
304        xShmUnmap: None,
305        xFetch: None,
306        xUnfetch: None,
307    };
308
309    vfs.register_logger(SqliteLogger::new(sqlite_api.log));
310
311    let p_name = ManuallyDrop::new(CString::new(name).map_err(|_| vars::SQLITE_INTERNAL)?).as_ptr();
312    let base_vfs = unsafe { (sqlite_api.find)(null_mut()) };
313    let vfs_register = sqlite_api.register;
314    let p_appdata = Box::into_raw(Box::new(AppData { base_vfs, vfs, io_methods, sqlite_api }));
315
316    let filewrapper_size: c_int = size_of::<FileWrapper<T::Handle>>()
317        .try_into()
318        .map_err(|_| vars::SQLITE_INTERNAL)?;
319
320    let p_vfs = Box::into_raw(Box::new(ffi::sqlite3_vfs {
321        iVersion: 3,
322        szOsFile: filewrapper_size,
323        mxPathname: DEFAULT_MAX_PATH_LEN,
324        pNext: null_mut(),
325        zName: p_name,
326        pAppData: p_appdata.cast(),
327        xOpen: Some(x_open::<T>),
328        xDelete: Some(x_delete::<T>),
329        xAccess: Some(x_access::<T>),
330        xFullPathname: Some(x_full_pathname::<T>),
331        xDlOpen: Some(x_dlopen::<T>),
332        xDlError: Some(x_dlerror::<T>),
333        xDlSym: Some(x_dlsym::<T>),
334        xDlClose: Some(x_dlclose::<T>),
335        xRandomness: Some(x_randomness::<T>),
336        xSleep: Some(x_sleep::<T>),
337        xCurrentTime: Some(x_current_time::<T>),
338        xGetLastError: None,
339        xCurrentTimeInt64: Some(x_current_time_int64::<T>),
340        xSetSystemCall: None,
341        xGetSystemCall: None,
342        xNextSystemCall: None,
343    }));
344
345    let result = unsafe { vfs_register(p_vfs, opts.make_default.into()) };
346    if result != vars::SQLITE_OK {
347        // cleanup memory
348        unsafe {
349            drop(Box::from_raw(p_vfs));
350            drop(Box::from_raw(p_appdata));
351            drop(CString::from_raw(p_name as *mut c_char));
352        };
353        Err(result)
354    } else {
355        Ok(())
356    }
357}
358
359unsafe extern "C" fn x_open<T: Vfs>(
360    p_vfs: *mut ffi::sqlite3_vfs,
361    z_name: ffi::sqlite3_filename,
362    p_file: *mut ffi::sqlite3_file,
363    flags: c_int,
364    p_out_flags: *mut c_int,
365) -> c_int {
366    fallible(|| {
367        let opts = flags.into();
368        let name = unsafe { lossy_cstr(z_name) }.ok();
369        let vfs = unwrap_vfs!(p_vfs, T)?;
370        let handle = vfs.open(name.as_ref().map(|s| s.as_ref()), opts)?;
371
372        let out_file = unwrap_file!(p_file, T)?;
373        let appdata = unwrap_appdata!(p_vfs, T)?;
374
375        if let Some(p_out_flags) = unsafe { p_out_flags.as_mut() } {
376            let mut out_flags = flags;
377            if handle.readonly() {
378                out_flags |= vars::SQLITE_OPEN_READONLY;
379            }
380            if handle.in_memory() {
381                out_flags |= vars::SQLITE_OPEN_MEMORY;
382            }
383            *p_out_flags = out_flags;
384        }
385
386        out_file.file.pMethods = &appdata.io_methods;
387        out_file.vfs = p_vfs;
388        out_file.handle.write(handle);
389
390        Ok(vars::SQLITE_OK)
391    })
392}
393
394unsafe extern "C" fn x_delete<T: Vfs>(
395    p_vfs: *mut ffi::sqlite3_vfs,
396    z_name: ffi::sqlite3_filename,
397    _sync_dir: c_int,
398) -> c_int {
399    fallible(|| {
400        let name = unsafe { lossy_cstr(z_name)? };
401        let vfs = unwrap_vfs!(p_vfs, T)?;
402        vfs.delete(&name)?;
403        Ok(vars::SQLITE_OK)
404    })
405}
406
407unsafe extern "C" fn x_access<T: Vfs>(
408    p_vfs: *mut ffi::sqlite3_vfs,
409    z_name: ffi::sqlite3_filename,
410    flags: c_int,
411    p_res_out: *mut c_int,
412) -> c_int {
413    fallible(|| {
414        let name = unsafe { lossy_cstr(z_name)? };
415        let vfs = unwrap_vfs!(p_vfs, T)?;
416        let result = vfs.access(&name, flags.into())?;
417        let out = unsafe { p_res_out.as_mut() }.ok_or(vars::SQLITE_IOERR_ACCESS)?;
418        *out = result as i32;
419        Ok(vars::SQLITE_OK)
420    })
421}
422
423unsafe extern "C" fn x_full_pathname<T: Vfs>(
424    p_vfs: *mut ffi::sqlite3_vfs,
425    z_name: ffi::sqlite3_filename,
426    n_out: c_int,
427    z_out: *mut c_char,
428) -> c_int {
429    fallible(|| {
430        let name = unsafe { lossy_cstr(z_name)? };
431        let vfs = unwrap_vfs!(p_vfs, T)?;
432        let full_name = vfs.canonical_path(name)?;
433        let n_out = n_out.try_into().map_err(|_| vars::SQLITE_INTERNAL)?;
434        let out = unsafe { slice::from_raw_parts_mut(z_out as *mut u8, n_out) };
435        let from = &full_name.as_bytes()[..full_name.len().min(n_out - 1)];
436        // copy the name into the output buffer
437        out[..from.len()].copy_from_slice(from);
438        // add the trailing null byte
439        out[from.len()] = 0;
440        Ok(vars::SQLITE_OK)
441    })
442}
443
444// file operations
445
446unsafe extern "C" fn x_close<T: Vfs>(p_file: *mut ffi::sqlite3_file) -> c_int {
447    fallible(|| {
448        let file = unwrap_file!(p_file, T)?;
449        let vfs = unwrap_vfs!(file.vfs, T)?;
450        let handle = mem::replace(&mut file.handle, MaybeUninit::uninit());
451        let handle = unsafe { handle.assume_init() };
452        vfs.close(handle)?;
453        Ok(vars::SQLITE_OK)
454    })
455}
456
457unsafe extern "C" fn x_read<T: Vfs>(
458    p_file: *mut ffi::sqlite3_file,
459    buf: *mut c_void,
460    i_amt: c_int,
461    i_ofst: ffi::sqlite_int64,
462) -> c_int {
463    fallible(|| {
464        let file = unwrap_file!(p_file, T)?;
465        let vfs = unwrap_vfs!(file.vfs, T)?;
466        let buf_len: usize = i_amt.try_into().map_err(|_| vars::SQLITE_IOERR_READ)?;
467        let offset: usize = i_ofst.try_into().map_err(|_| vars::SQLITE_IOERR_READ)?;
468        let buf = unsafe { slice::from_raw_parts_mut(buf.cast::<u8>(), buf_len) };
469        vfs.read(unsafe { file.handle.assume_init_mut() }, offset, buf)?;
470        Ok(vars::SQLITE_OK)
471    })
472}
473
474unsafe extern "C" fn x_write<T: Vfs>(
475    p_file: *mut ffi::sqlite3_file,
476    buf: *const c_void,
477    i_amt: c_int,
478    i_ofst: ffi::sqlite_int64,
479) -> c_int {
480    fallible(|| {
481        let file = unwrap_file!(p_file, T)?;
482        let vfs = unwrap_vfs!(file.vfs, T)?;
483        let buf_len: usize = i_amt.try_into().map_err(|_| vars::SQLITE_IOERR_WRITE)?;
484        let offset: usize = i_ofst.try_into().map_err(|_| vars::SQLITE_IOERR_WRITE)?;
485        let buf = unsafe { slice::from_raw_parts(buf.cast::<u8>(), buf_len) };
486        let n = vfs.write(unsafe { file.handle.assume_init_mut() }, offset, buf)?;
487        if n != buf_len {
488            return Err(vars::SQLITE_IOERR_WRITE);
489        }
490        Ok(vars::SQLITE_OK)
491    })
492}
493
494unsafe extern "C" fn x_truncate<T: Vfs>(
495    p_file: *mut ffi::sqlite3_file,
496    size: ffi::sqlite_int64,
497) -> c_int {
498    fallible(|| {
499        let file = unwrap_file!(p_file, T)?;
500        let vfs = unwrap_vfs!(file.vfs, T)?;
501        let size: usize = size.try_into().map_err(|_| vars::SQLITE_IOERR_TRUNCATE)?;
502        vfs.truncate(unsafe { file.handle.assume_init_mut() }, size)?;
503        Ok(vars::SQLITE_OK)
504    })
505}
506
507unsafe extern "C" fn x_sync<T: Vfs>(p_file: *mut ffi::sqlite3_file, _flags: c_int) -> c_int {
508    fallible(|| {
509        let file = unwrap_file!(p_file, T)?;
510        let vfs = unwrap_vfs!(file.vfs, T)?;
511        vfs.sync(unsafe { file.handle.assume_init_mut() })?;
512        Ok(vars::SQLITE_OK)
513    })
514}
515
516unsafe extern "C" fn x_file_size<T: Vfs>(
517    p_file: *mut ffi::sqlite3_file,
518    p_size: *mut ffi::sqlite3_int64,
519) -> c_int {
520    fallible(|| {
521        let file = unwrap_file!(p_file, T)?;
522        let vfs = unwrap_vfs!(file.vfs, T)?;
523        let size = vfs.file_size(unsafe { file.handle.assume_init_mut() })?;
524        let p_size = unsafe { p_size.as_mut() }.ok_or(vars::SQLITE_INTERNAL)?;
525        *p_size = size.try_into().map_err(|_| vars::SQLITE_IOERR_FSTAT)?;
526        Ok(vars::SQLITE_OK)
527    })
528}
529
530unsafe extern "C" fn x_lock<T: Vfs>(p_file: *mut ffi::sqlite3_file, raw_lock: c_int) -> c_int {
531    fallible(|| {
532        let level: LockLevel = raw_lock.into();
533        let file = unwrap_file!(p_file, T)?;
534        let vfs = unwrap_vfs!(file.vfs, T)?;
535        vfs.lock(unsafe { file.handle.assume_init_mut() }, level)?;
536        Ok(vars::SQLITE_OK)
537    })
538}
539
540unsafe extern "C" fn x_unlock<T: Vfs>(p_file: *mut ffi::sqlite3_file, raw_lock: c_int) -> c_int {
541    fallible(|| {
542        let level: LockLevel = raw_lock.into();
543        let file = unwrap_file!(p_file, T)?;
544        let vfs = unwrap_vfs!(file.vfs, T)?;
545        vfs.unlock(unsafe { file.handle.assume_init_mut() }, level)?;
546        Ok(vars::SQLITE_OK)
547    })
548}
549
550unsafe extern "C" fn x_file_control<T: Vfs>(
551    p_file: *mut ffi::sqlite3_file,
552    op: c_int,
553    p_arg: *mut c_void,
554) -> c_int {
555    /*
556    Other interesting ops:
557    SIZE_HINT: hint of how large the database will grow during the current transaction
558    COMMIT_PHASETWO: after transaction commits before file unlocks (only used in WAL mode)
559    VFS_NAME: should return this vfs's name + / + base vfs's name
560
561    Atomic write support: (requires SQLITE_IOCAP_BATCH_ATOMIC device characteristic)
562    Docs: https://www3.sqlite.org/cgi/src/technote/714f6cbbf78c8a1351cbd48af2b438f7f824b336
563    BEGIN_ATOMIC_WRITE: start an atomic write operation
564    COMMIT_ATOMIC_WRITE: commit an atomic write operation
565    ROLLBACK_ATOMIC_WRITE: rollback an atomic write operation
566    */
567
568    if op == vars::SQLITE_FCNTL_PRAGMA {
569        return fallible(|| {
570            let file = unwrap_file!(p_file, T)?;
571            let vfs = unwrap_vfs!(file.vfs, T)?;
572
573            // p_arg is a pointer to an array of strings
574            // the second value is the pragma name
575            // the third value is either null or the pragma arg
576            let args = p_arg.cast::<*const c_char>();
577            let name = unsafe { lossy_cstr(*args.add(1)) }?;
578            let arg = unsafe {
579                (*args.add(2))
580                    .as_ref()
581                    .map(|p| CStr::from_ptr(p).to_string_lossy())
582            };
583            let pragma = Pragma { name: &name, arg: arg.as_deref() };
584
585            let (result, msg) = match vfs.pragma(unsafe { file.handle.assume_init_mut() }, pragma) {
586                Ok(msg) => (Ok(vars::SQLITE_OK), msg),
587                Err(PragmaErr::NotFound) => (Err(vars::SQLITE_NOTFOUND), None),
588                Err(PragmaErr::Fail(msg)) => (Err(vars::SQLITE_ERROR), Some(msg)),
589            };
590
591            if let Some(msg) = msg {
592                // write the msg back to the first element of the args array.
593                // SQLite is responsible for eventually freeing the result
594                let appdata = unwrap_appdata!(file.vfs, T)?;
595                unsafe { *args = sqlite3_mprintf(&appdata.sqlite_api, &msg)? };
596            }
597
598            result
599        });
600    }
601    vars::SQLITE_NOTFOUND
602}
603
604// system queries
605
606unsafe extern "C" fn x_sector_size<T: Vfs>(p_file: *mut ffi::sqlite3_file) -> c_int {
607    fallible(|| {
608        let file = unwrap_file!(p_file, T)?;
609        let vfs = unwrap_vfs!(file.vfs, T)?;
610        Ok(vfs.sector_size())
611    })
612}
613
614unsafe extern "C" fn x_device_characteristics<T: Vfs>(p_file: *mut ffi::sqlite3_file) -> c_int {
615    fallible(|| {
616        let file = unwrap_file!(p_file, T)?;
617        let vfs = unwrap_vfs!(file.vfs, T)?;
618        Ok(vfs.device_characteristics())
619    })
620}
621
622// the following functions are wrappers around the base vfs functions
623
624unsafe extern "C" fn x_dlopen<T: Vfs>(
625    p_vfs: *mut ffi::sqlite3_vfs,
626    z_path: *const c_char,
627) -> *mut c_void {
628    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
629        if let Some(x_dlopen) = vfs.xDlOpen {
630            return unsafe { x_dlopen(vfs, z_path) };
631        }
632    }
633    null_mut()
634}
635
636unsafe extern "C" fn x_dlerror<T: Vfs>(
637    p_vfs: *mut ffi::sqlite3_vfs,
638    n_byte: c_int,
639    z_err_msg: *mut c_char,
640) {
641    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
642        if let Some(x_dlerror) = vfs.xDlError {
643            unsafe { x_dlerror(vfs, n_byte, z_err_msg) };
644        }
645    }
646}
647
648unsafe extern "C" fn x_dlsym<T: Vfs>(
649    p_vfs: *mut ffi::sqlite3_vfs,
650    p_handle: *mut c_void,
651    z_symbol: *const c_char,
652) -> Option<
653    unsafe extern "C" fn(arg1: *mut ffi::sqlite3_vfs, arg2: *mut c_void, zSymbol: *const c_char),
654> {
655    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
656        if let Some(x_dlsym) = vfs.xDlSym {
657            return unsafe { x_dlsym(vfs, p_handle, z_symbol) };
658        }
659    }
660    None
661}
662
663unsafe extern "C" fn x_dlclose<T: Vfs>(p_vfs: *mut ffi::sqlite3_vfs, p_handle: *mut c_void) {
664    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
665        if let Some(x_dlclose) = vfs.xDlClose {
666            unsafe { x_dlclose(vfs, p_handle) };
667        }
668    }
669}
670
671unsafe extern "C" fn x_randomness<T: Vfs>(
672    p_vfs: *mut ffi::sqlite3_vfs,
673    n_byte: c_int,
674    z_out: *mut c_char,
675) -> c_int {
676    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
677        if let Some(x_randomness) = vfs.xRandomness {
678            return unsafe { x_randomness(vfs, n_byte, z_out) };
679        }
680    }
681    vars::SQLITE_INTERNAL
682}
683
684unsafe extern "C" fn x_sleep<T: Vfs>(p_vfs: *mut ffi::sqlite3_vfs, microseconds: c_int) -> c_int {
685    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
686        if let Some(x_sleep) = vfs.xSleep {
687            return unsafe { x_sleep(vfs, microseconds) };
688        }
689    }
690    vars::SQLITE_INTERNAL
691}
692
693unsafe extern "C" fn x_current_time<T: Vfs>(
694    p_vfs: *mut ffi::sqlite3_vfs,
695    p_time: *mut f64,
696) -> c_int {
697    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
698        if let Some(x_current_time) = vfs.xCurrentTime {
699            return unsafe { x_current_time(vfs, p_time) };
700        }
701    }
702    vars::SQLITE_INTERNAL
703}
704
705unsafe extern "C" fn x_current_time_int64<T: Vfs>(
706    p_vfs: *mut ffi::sqlite3_vfs,
707    p_time: *mut i64,
708) -> c_int {
709    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
710        if let Some(x_current_time_int64) = vfs.xCurrentTimeInt64 {
711            return unsafe { x_current_time_int64(vfs, p_time) };
712        }
713    }
714    vars::SQLITE_INTERNAL
715}
716
717#[cfg(test)]
718mod tests {
719    // tests use std
720    extern crate std;
721
722    use super::*;
723    use crate::{
724        flags::{CreateMode, OpenKind, OpenMode},
725        mock::*,
726    };
727    use alloc::vec::Vec;
728    use rusqlite::{Connection, OpenFlags};
729    use std::{boxed::Box, io::Write, println};
730
731    fn log_handler(_: i32, arg2: &str) {
732        println!("{arg2}");
733    }
734
735    #[test]
736    fn sanity() -> Result<(), Box<dyn std::error::Error>> {
737        unsafe {
738            rusqlite::trace::config_log(Some(log_handler)).unwrap();
739        }
740
741        struct H {}
742        impl Hooks for H {
743            fn open(&mut self, path: &Option<&str>, opts: &OpenOpts) {
744                let path = path.unwrap();
745                if path == "main.db" {
746                    assert!(!opts.delete_on_close());
747                    assert_eq!(opts.kind(), OpenKind::MainDb);
748                    assert_eq!(
749                        opts.mode(),
750                        OpenMode::ReadWrite { create: CreateMode::Create }
751                    );
752                } else if path == "main.db-journal" {
753                    assert!(!opts.delete_on_close());
754                    assert_eq!(opts.kind(), OpenKind::MainJournal);
755                    assert_eq!(
756                        opts.mode(),
757                        OpenMode::ReadWrite { create: CreateMode::Create }
758                    );
759                } else {
760                    panic!("unexpected path: {}", path);
761                }
762            }
763        }
764
765        let vfs = MockVfs::new(Box::new(H {}));
766        register_static("mock", vfs, RegisterOpts { make_default: true })
767            .map_err(|_| "failed to register vfs")?;
768
769        // create a sqlite connection using the mock vfs
770        let conn = Connection::open_with_flags_and_vfs(
771            "main.db",
772            OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE,
773            "mock",
774        )?;
775
776        conn.execute("create table t (val int)", [])?;
777        conn.execute("insert into t (val) values (1)", [])?;
778        conn.execute("insert into t (val) values (2)", [])?;
779
780        conn.execute("pragma mock_test", [])?;
781
782        let n: i64 = conn.query_row("select sum(val) from t", [], |row| row.get(0))?;
783        assert_eq!(n, 3);
784
785        // the blob api is interesting and stress tests reading/writing pages and journaling
786        conn.execute("create table b (data blob)", [])?;
787        println!("inserting zero blob");
788        conn.execute("insert into b values (zeroblob(8192))", [])?;
789        let rowid = conn.last_insert_rowid();
790        let mut blob = conn.blob_open(rusqlite::DatabaseName::Main, "b", "data", rowid, false)?;
791
792        // write some data to the blob
793        println!("writing to blob");
794        let n = blob.write(b"hello")?;
795        assert_eq!(n, 5);
796
797        // query the table for the blob and print it
798        let mut stmt = conn.prepare("select data from b")?;
799        let mut rows = stmt.query([])?;
800        while let Some(row) = rows.next()? {
801            let data: Vec<u8> = row.get(0)?;
802            assert_eq!(&data[0..5], b"hello");
803        }
804
805        Ok(())
806    }
807}