sqlite_plugin/
vfs.rs

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