Skip to main content

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