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    /// Register the provided logger with this Vfs.
146    /// This function is guaranteed to only be called once per
147    /// register_{static,dynamic} call.
148    fn register_logger(&self, logger: SqliteLogger);
149
150    /// construct a canonical version of the given path
151    fn canonical_path<'a>(&self, path: Cow<'a, str>) -> VfsResult<Cow<'a, str>> {
152        Ok(path)
153    }
154
155    // file system operations
156    fn open(&self, path: Option<&str>, opts: OpenOpts) -> VfsResult<Self::Handle>;
157    fn delete(&self, path: &str) -> VfsResult<()>;
158    fn access(&self, path: &str, flags: AccessFlags) -> VfsResult<bool>;
159
160    // file operations
161    fn file_size(&self, handle: &mut Self::Handle) -> VfsResult<usize>;
162    fn truncate(&self, handle: &mut Self::Handle, size: usize) -> VfsResult<()>;
163    fn write(&self, handle: &mut Self::Handle, offset: usize, data: &[u8]) -> VfsResult<usize>;
164    fn read(&self, handle: &mut Self::Handle, offset: usize, data: &mut [u8]) -> VfsResult<usize>;
165
166    fn lock(&self, handle: &mut Self::Handle, level: LockLevel) -> VfsResult<()> {
167        Ok(())
168    }
169
170    fn unlock(&self, handle: &mut Self::Handle, level: LockLevel) -> VfsResult<()> {
171        Ok(())
172    }
173
174    fn sync(&self, handle: &mut Self::Handle) -> VfsResult<()> {
175        Ok(())
176    }
177
178    fn close(&self, handle: Self::Handle) -> VfsResult<()>;
179
180    fn pragma(
181        &self,
182        handle: &mut Self::Handle,
183        pragma: Pragma<'_>,
184    ) -> Result<Option<String>, PragmaErr> {
185        Err(PragmaErr::NotFound)
186    }
187
188    // system queries
189    fn sector_size(&self) -> i32 {
190        DEFAULT_SECTOR_SIZE
191    }
192
193    fn device_characteristics(&self) -> i32 {
194        DEFAULT_DEVICE_CHARACTERISTICS
195    }
196}
197
198#[derive(Clone)]
199pub struct SqliteApi {
200    register: unsafe extern "C" fn(arg1: *mut ffi::sqlite3_vfs, arg2: c_int) -> c_int,
201    find: unsafe extern "C" fn(arg1: *const c_char) -> *mut ffi::sqlite3_vfs,
202    mprintf: unsafe extern "C" fn(arg1: *const c_char, ...) -> *mut c_char,
203    log: unsafe extern "C" fn(arg1: c_int, arg2: *const c_char, ...),
204    libversion_number: unsafe extern "C" fn() -> c_int,
205}
206
207impl SqliteApi {
208    #[cfg(feature = "static")]
209    pub fn new_static() -> Self {
210        Self {
211            register: ffi::sqlite3_vfs_register,
212            find: ffi::sqlite3_vfs_find,
213            mprintf: ffi::sqlite3_mprintf,
214            log: ffi::sqlite3_log,
215            libversion_number: ffi::sqlite3_libversion_number,
216        }
217    }
218
219    /// Initializes SqliteApi from a filled `sqlite3_api_routines` object.
220    /// # Safety
221    /// `api` must be a valid, aligned pointer to a `sqlite3_api_routines` struct
222    #[cfg(feature = "dynamic")]
223    pub unsafe fn new_dynamic(api: &ffi::sqlite3_api_routines) -> VfsResult<Self> {
224        Ok(Self {
225            register: api.vfs_register.ok_or(vars::SQLITE_INTERNAL)?,
226            find: api.vfs_find.ok_or(vars::SQLITE_INTERNAL)?,
227            mprintf: api.mprintf.ok_or(vars::SQLITE_INTERNAL)?,
228            log: api.log.ok_or(vars::SQLITE_INTERNAL)?,
229            libversion_number: api.libversion_number.ok_or(vars::SQLITE_INTERNAL)?,
230        })
231    }
232
233    /// Copies the provided string into a memory buffer allocated by sqlite3_mprintf.
234    /// Writes the pointer to the memory buffer to `out` if `out` is not null.
235    /// # Safety
236    /// 1. the out pointer must not be null
237    /// 2. it is the callers responsibility to eventually free the allocated buffer
238    pub unsafe fn mprintf(&self, s: &str, out: *mut *const c_char) -> VfsResult<()> {
239        let s = CString::new(s).map_err(|_| vars::SQLITE_INTERNAL)?;
240        let p = unsafe { (self.mprintf)(s.as_ptr()) };
241        if p.is_null() {
242            Err(vars::SQLITE_NOMEM)
243        } else {
244            unsafe {
245                *out = p;
246            }
247            Ok(())
248        }
249    }
250}
251
252pub struct RegisterOpts {
253    pub make_default: bool,
254}
255
256#[cfg(feature = "static")]
257pub fn register_static<T: Vfs>(name: CString, vfs: T, opts: RegisterOpts) -> VfsResult<()> {
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<()> {
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<()> {
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    vfs.register_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(())
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<
657    unsafe extern "C" fn(arg1: *mut ffi::sqlite3_vfs, arg2: *mut c_void, zSymbol: *const c_char),
658> {
659    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
660        if let Some(x_dlsym) = vfs.xDlSym {
661            return unsafe { x_dlsym(vfs, p_handle, z_symbol) };
662        }
663    }
664    None
665}
666
667unsafe extern "C" fn x_dlclose<T: Vfs>(p_vfs: *mut ffi::sqlite3_vfs, p_handle: *mut c_void) {
668    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
669        if let Some(x_dlclose) = vfs.xDlClose {
670            unsafe { x_dlclose(vfs, p_handle) };
671        }
672    }
673}
674
675unsafe extern "C" fn x_randomness<T: Vfs>(
676    p_vfs: *mut ffi::sqlite3_vfs,
677    n_byte: c_int,
678    z_out: *mut c_char,
679) -> c_int {
680    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
681        if let Some(x_randomness) = vfs.xRandomness {
682            return unsafe { x_randomness(vfs, n_byte, z_out) };
683        }
684    }
685    vars::SQLITE_INTERNAL
686}
687
688unsafe extern "C" fn x_sleep<T: Vfs>(p_vfs: *mut ffi::sqlite3_vfs, microseconds: c_int) -> c_int {
689    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
690        if let Some(x_sleep) = vfs.xSleep {
691            return unsafe { x_sleep(vfs, microseconds) };
692        }
693    }
694    vars::SQLITE_INTERNAL
695}
696
697unsafe extern "C" fn x_current_time<T: Vfs>(
698    p_vfs: *mut ffi::sqlite3_vfs,
699    p_time: *mut f64,
700) -> c_int {
701    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
702        if let Some(x_current_time) = vfs.xCurrentTime {
703            return unsafe { x_current_time(vfs, p_time) };
704        }
705    }
706    vars::SQLITE_INTERNAL
707}
708
709unsafe extern "C" fn x_current_time_int64<T: Vfs>(
710    p_vfs: *mut ffi::sqlite3_vfs,
711    p_time: *mut i64,
712) -> c_int {
713    if let Ok(vfs) = unwrap_base_vfs!(p_vfs, T) {
714        if let Some(x_current_time_int64) = vfs.xCurrentTimeInt64 {
715            return unsafe { x_current_time_int64(vfs, p_time) };
716        }
717    }
718    vars::SQLITE_INTERNAL
719}
720
721#[cfg(test)]
722mod tests {
723    // tests use std
724    extern crate std;
725
726    use super::*;
727    use crate::{
728        flags::{CreateMode, OpenKind, OpenMode},
729        mock::*,
730    };
731    use alloc::vec::Vec;
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 vfs = MockVfs::new(Box::new(H {}));
770        register_static(
771            CString::new("mock").unwrap(),
772            vfs,
773            RegisterOpts { make_default: true },
774        )
775        .map_err(|_| "failed to register vfs")?;
776
777        // create a sqlite connection using the mock vfs
778        let conn = Connection::open_with_flags_and_vfs(
779            "main.db",
780            OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE,
781            "mock",
782        )?;
783
784        conn.execute("create table t (val int)", [])?;
785        conn.execute("insert into t (val) values (1)", [])?;
786        conn.execute("insert into t (val) values (2)", [])?;
787
788        conn.execute("pragma mock_test", [])?;
789
790        let n: i64 = conn.query_row("select sum(val) from t", [], |row| row.get(0))?;
791        assert_eq!(n, 3);
792
793        // the blob api is interesting and stress tests reading/writing pages and journaling
794        conn.execute("create table b (data blob)", [])?;
795        println!("inserting zero blob");
796        conn.execute("insert into b values (zeroblob(8192))", [])?;
797        let rowid = conn.last_insert_rowid();
798        let mut blob = conn.blob_open(rusqlite::MAIN_DB, "b", "data", rowid, false)?;
799
800        // write some data to the blob
801        println!("writing to blob");
802        let n = blob.write(b"hello")?;
803        assert_eq!(n, 5);
804
805        // query the table for the blob and print it
806        let mut stmt = conn.prepare("select data from b")?;
807        let mut rows = stmt.query([])?;
808        while let Some(row) = rows.next()? {
809            let data: Vec<u8> = row.get(0)?;
810            assert_eq!(&data[0..5], b"hello");
811        }
812
813        Ok(())
814    }
815}