sqlite_wasm_rs/
utils.rs

1//! Low-level utilities, traits, and macros for implementing custom SQLite Virtual File Systems (VFS).
2
3use crate::bindings::*;
4
5use alloc::string::String;
6use alloc::vec::Vec;
7use alloc::{boxed::Box, ffi::CString};
8use alloc::{format, vec};
9use core::{cell::RefCell, ffi::CStr, ops::Deref};
10use js_sys::{Date, Math, Number};
11
12/// A macro to return a specific SQLite error code if a condition is true.
13///
14/// The default error code is SQLITE_ERROR.
15#[macro_export]
16macro_rules! bail {
17    ($ex:expr) => {
18        bail!($ex, SQLITE_ERROR);
19    };
20    ($ex:expr, $code: expr) => {
21        if $ex {
22            return $code;
23        }
24    };
25}
26
27/// A macro to safely unwrap an `Option<T>`, returning a SQLite error code on `None`.
28///
29/// The default error code is SQLITE_ERROR.
30#[macro_export]
31macro_rules! check_option {
32    ($ex:expr) => {
33        check_option!($ex, SQLITE_ERROR)
34    };
35    ($ex:expr, $code: expr) => {
36        if let Some(v) = $ex {
37            v
38        } else {
39            return $code;
40        }
41    };
42}
43
44/// A macro to safely unwrap a `Result<T, E>`, returning a SQLite error code on `Err`.
45///
46/// The default err code is SQLITE_ERROR.
47#[macro_export]
48macro_rules! check_result {
49    ($ex:expr) => {
50        check_result!($ex, SQLITE_ERROR)
51    };
52    ($ex:expr, $code: expr) => {
53        if let Ok(v) = $ex {
54            v
55        } else {
56            return $code;
57        }
58    };
59}
60
61/// A macro to explicitly mark a parameter as unused, suppressing compiler warnings.
62#[macro_export]
63macro_rules! unused {
64    ($ex:expr) => {
65        let _ = $ex;
66    };
67}
68
69/// The header of the SQLite file is used to determine whether the imported file is legal.
70pub const SQLITE3_HEADER: &str = "SQLite format 3";
71
72/// Generates a random, temporary filename, typically used when SQLite requests a file with a NULL name.
73pub fn random_name() -> String {
74    let random = Number::from(Math::random()).to_string(36).unwrap();
75    random.slice(2, random.length()).as_string().unwrap()
76}
77
78/// An in-memory file implementation that stores data in fixed-size chunks. Suitable for temporary files.
79pub struct MemChunksFile {
80    chunks: Vec<Vec<u8>>,
81    chunk_size: Option<usize>,
82    file_size: usize,
83}
84
85impl Default for MemChunksFile {
86    fn default() -> Self {
87        Self::new(512)
88    }
89}
90
91impl MemChunksFile {
92    /// Creates a new `MemChunksFile` with a specified chunk size.
93    pub fn new(chunk_size: usize) -> Self {
94        assert!(chunk_size != 0, "chunk size can't be zero");
95        MemChunksFile {
96            chunks: Vec::new(),
97            chunk_size: Some(chunk_size),
98            file_size: 0,
99        }
100    }
101
102    /// Creates a `MemChunksFile` where the chunk size is determined by the size of the first write operation.
103    ///
104    /// This is often used for the main DB file implementation.
105    pub fn waiting_for_write() -> Self {
106        MemChunksFile {
107            chunks: Vec::new(),
108            chunk_size: None,
109            file_size: 0,
110        }
111    }
112}
113
114impl VfsFile for MemChunksFile {
115    fn read(&self, buf: &mut [u8], offset: usize) -> VfsResult<bool> {
116        let Some(chunk_size) = self.chunk_size else {
117            buf.fill(0);
118            return Ok(false);
119        };
120
121        if self.file_size <= offset {
122            buf.fill(0);
123            return Ok(false);
124        }
125
126        if chunk_size == buf.len() && offset % chunk_size == 0 {
127            buf.copy_from_slice(&self.chunks[offset / chunk_size]);
128            Ok(true)
129        } else {
130            let mut size = buf.len();
131            let chunk_idx = offset / chunk_size;
132            let mut remaining_idx = offset % chunk_size;
133            let mut offset = 0;
134
135            for chunk in &self.chunks[chunk_idx..] {
136                let n = core::cmp::min(chunk_size.min(self.file_size) - remaining_idx, size);
137                buf[offset..offset + n].copy_from_slice(&chunk[remaining_idx..remaining_idx + n]);
138                offset += n;
139                size -= n;
140                remaining_idx = 0;
141                if size == 0 {
142                    break;
143                }
144            }
145
146            if offset < buf.len() {
147                buf[offset..].fill(0);
148                Ok(false)
149            } else {
150                Ok(true)
151            }
152        }
153    }
154
155    fn write(&mut self, buf: &[u8], offset: usize) -> VfsResult<()> {
156        if buf.is_empty() {
157            return Ok(());
158        }
159
160        let chunk_size = if let Some(chunk_size) = self.chunk_size {
161            chunk_size
162        } else {
163            let size = buf.len();
164            self.chunk_size = Some(size);
165            size
166        };
167
168        let new_length = self.file_size.max(offset + buf.len());
169
170        if chunk_size == buf.len() && offset % chunk_size == 0 {
171            for _ in self.chunks.len()..offset / chunk_size {
172                self.chunks.push(vec![0; chunk_size]);
173            }
174            if let Some(buffer) = self.chunks.get_mut(offset / chunk_size) {
175                buffer.copy_from_slice(buf);
176            } else {
177                self.chunks.push(buf.to_vec());
178            }
179        } else {
180            let mut size = buf.len();
181            let chunk_start_idx = offset / chunk_size;
182            let chunk_end_idx = (offset + size - 1) / chunk_size;
183            let chunks_length = self.chunks.len();
184
185            for _ in chunks_length..=chunk_end_idx {
186                self.chunks.push(vec![0; chunk_size]);
187            }
188
189            let mut remaining_idx = offset % chunk_size;
190            let mut offset = 0;
191
192            for idx in chunk_start_idx..=chunk_end_idx {
193                let n = core::cmp::min(chunk_size - remaining_idx, size);
194                self.chunks[idx][remaining_idx..remaining_idx + n]
195                    .copy_from_slice(&buf[offset..offset + n]);
196                offset += n;
197                size -= n;
198                remaining_idx = 0;
199                if size == 0 {
200                    break;
201                }
202            }
203        }
204
205        self.file_size = new_length;
206
207        Ok(())
208    }
209
210    fn truncate(&mut self, size: usize) -> VfsResult<()> {
211        if let Some(chunk_size) = self.chunk_size {
212            if size == 0 {
213                core::mem::take(&mut self.chunks);
214            } else {
215                let idx = ((size - 1) / chunk_size) + 1;
216                self.chunks.drain(idx..);
217            }
218        } else if size != 0 {
219            assert_eq!(self.file_size, 0);
220            return Err(VfsError::new(SQLITE_IOERR, "Failed to truncate".into()));
221        }
222        self.file_size = size;
223        Ok(())
224    }
225
226    fn flush(&mut self) -> VfsResult<()> {
227        Ok(())
228    }
229
230    fn size(&self) -> VfsResult<usize> {
231        Ok(self.file_size)
232    }
233}
234
235/// The core file-handle structure for a custom VFS, designed to be compatible with SQLite's C interface.
236///
237/// `szOsFile` must be set to the size of `SQLiteVfsFile`.
238#[repr(C)]
239pub struct SQLiteVfsFile {
240    /// The first field must be of type sqlite_file.
241    /// In C layout, the pointer to SQLiteVfsFile is the pointer to io_methods.
242    pub io_methods: sqlite3_file,
243    /// The vfs where the file is located, usually used to manage files.
244    pub vfs: *mut sqlite3_vfs,
245    /// Flags used to open the database.
246    pub flags: i32,
247    /// The pointer to the file name.
248    /// If it is a leaked static pointer, you need to drop it manually when xClose it.
249    pub name_ptr: *const u8,
250    /// Length of the file name, on wasm32 platform, usize is u32.
251    pub name_length: usize,
252}
253
254impl SQLiteVfsFile {
255    /// Convert a `sqlite3_file` pointer to a `SQLiteVfsFile` pointer.
256    ///
257    /// # Safety
258    ///
259    /// You must ensure that the pointer passed in is `SQLiteVfsFile`
260    pub unsafe fn from_file(file: *mut sqlite3_file) -> &'static SQLiteVfsFile {
261        &*file.cast::<Self>()
262    }
263
264    /// Get the file name.
265    ///
266    /// # Safety
267    ///
268    /// When xClose, you can free the memory by `drop(Box::from_raw(ptr));`.
269    ///
270    /// Do not use again after free.
271    pub unsafe fn name(&self) -> &'static mut str {
272        // emm, `from_raw_parts_mut` is unstable
273        core::str::from_utf8_unchecked_mut(core::slice::from_raw_parts_mut(
274            self.name_ptr.cast_mut(),
275            self.name_length,
276        ))
277    }
278
279    /// Converts a reference to this VFS file structure into a raw `*mut sqlite3_file` pointer that can be passed to SQLite.
280    pub fn sqlite3_file(&'static self) -> *mut sqlite3_file {
281        self as *const SQLiteVfsFile as *mut sqlite3_file
282    }
283}
284
285/// Represents errors that can occur during the VFS registration process.
286#[derive(thiserror::Error, Debug)]
287pub enum RegisterVfsError {
288    #[error("An error occurred converting the given vfs name to a CStr")]
289    ToCStr,
290    #[error("An error occurred while registering vfs with sqlite")]
291    RegisterVfs,
292}
293
294/// Checks if a VFS with the given name is already registered with SQLite and returns a pointer to it if found.
295pub fn registered_vfs(vfs_name: &str) -> Result<Option<*mut sqlite3_vfs>, RegisterVfsError> {
296    let name = CString::new(vfs_name).map_err(|_| RegisterVfsError::ToCStr)?;
297    let vfs = unsafe { sqlite3_vfs_find(name.as_ptr()) };
298    Ok((!vfs.is_null()).then_some(vfs))
299}
300
301/// A generic function to register a custom VFS implementation with SQLite.
302pub fn register_vfs<IO: SQLiteIoMethods, V: SQLiteVfs<IO>>(
303    vfs_name: &str,
304    app_data: IO::AppData,
305    default_vfs: bool,
306) -> Result<*mut sqlite3_vfs, RegisterVfsError> {
307    let name = CString::new(vfs_name).map_err(|_| RegisterVfsError::ToCStr)?;
308    let name_ptr = name.into_raw();
309
310    let app_data = VfsAppData::new(app_data).leak();
311    let vfs = Box::leak(Box::new(V::vfs(name_ptr, app_data.cast())));
312    let ret = unsafe { sqlite3_vfs_register(vfs, i32::from(default_vfs)) };
313
314    if ret != SQLITE_OK {
315        unsafe {
316            drop(Box::from_raw(vfs));
317            drop(CString::from_raw(name_ptr));
318            drop(VfsAppData::from_raw(app_data));
319        }
320        return Err(RegisterVfsError::RegisterVfs);
321    }
322
323    Ok(vfs as *mut sqlite3_vfs)
324}
325
326/// A container for VFS-specific errors, holding both an error code and a descriptive message.
327#[derive(Debug)]
328pub struct VfsError {
329    code: i32,
330    message: String,
331}
332
333impl VfsError {
334    pub fn new(code: i32, message: String) -> Self {
335        VfsError { code, message }
336    }
337}
338
339/// A specialized `Result` type for VFS operations.
340pub type VfsResult<T> = Result<T, VfsError>;
341
342/// A wrapper for the `pAppData` pointer in `sqlite3_vfs`, providing a safe way
343/// to manage VFS-specific application data and error states.
344pub struct VfsAppData<T> {
345    data: T,
346    last_err: RefCell<Option<(i32, String)>>,
347}
348
349impl<T> VfsAppData<T> {
350    pub fn new(t: T) -> Self {
351        VfsAppData {
352            data: t,
353            last_err: RefCell::new(None),
354        }
355    }
356
357    /// Leak, then pAppData can be set to VFS
358    pub fn leak(self) -> *mut Self {
359        Box::into_raw(Box::new(self))
360    }
361
362    /// # Safety
363    ///
364    /// You have to make sure the pointer is correct
365    pub unsafe fn from_raw(t: *mut Self) -> VfsAppData<T> {
366        *Box::from_raw(t)
367    }
368
369    /// Retrieves and clears the last error recorded for the VFS.
370    pub fn pop_err(&self) -> Option<(i32, String)> {
371        self.last_err.borrow_mut().take()
372    }
373
374    /// Stores an error code and message for the VFS, to be retrieved later by `xGetLastError`.
375    pub fn store_err(&self, err: VfsError) -> i32 {
376        let VfsError { code, message } = err;
377        self.last_err.borrow_mut().replace((code, message));
378        code
379    }
380}
381
382/// Deref only, returns immutable reference, avoids race conditions
383impl<T> Deref for VfsAppData<T> {
384    type Target = T;
385
386    fn deref(&self) -> &Self::Target {
387        &self.data
388    }
389}
390
391/// A trait defining the basic I/O capabilities required for a VFS file implementation.
392pub trait VfsFile {
393    /// Abstraction of `xRead`, returns true for `SQLITE_OK` and false for `SQLITE_IOERR_SHORT_READ`
394    fn read(&self, buf: &mut [u8], offset: usize) -> VfsResult<bool>;
395    /// Abstraction of `xWrite`
396    fn write(&mut self, buf: &[u8], offset: usize) -> VfsResult<()>;
397    /// Abstraction of `xTruncate`
398    fn truncate(&mut self, size: usize) -> VfsResult<()>;
399    /// Abstraction of `xSync`
400    fn flush(&mut self) -> VfsResult<()>;
401    /// Abstraction of `xFileSize`
402    fn size(&self) -> VfsResult<usize>;
403}
404
405/// Make changes to files
406pub trait VfsStore<File, AppData> {
407    /// Convert pAppData to the type we need
408    ///
409    /// # Safety
410    ///
411    /// As long as it is set through the abstract VFS interface, it is safe
412    unsafe fn app_data(vfs: *mut sqlite3_vfs) -> &'static VfsAppData<AppData> {
413        &*(*vfs).pAppData.cast()
414    }
415    /// Adding files to the Store, use for `xOpen` and `xAccess`
416    fn add_file(vfs: *mut sqlite3_vfs, file: &str, flags: i32) -> VfsResult<()>;
417    /// Checks if the specified file exists in the Store, use for `xOpen` and `xAccess`
418    fn contains_file(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<bool>;
419    /// Delete the specified file in the Store, use for `xClose` and `xDelete`
420    fn delete_file(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<()>;
421    /// Read the file contents, use for `xRead`, `xFileSize`
422    fn with_file<F: Fn(&File) -> VfsResult<i32>>(vfs_file: &SQLiteVfsFile, f: F) -> VfsResult<i32>;
423    /// Write the file contents, use for `xWrite`, `xTruncate` and `xSync`
424    fn with_file_mut<F: Fn(&mut File) -> VfsResult<i32>>(
425        vfs_file: &SQLiteVfsFile,
426        f: F,
427    ) -> VfsResult<i32>;
428}
429
430/// A trait that abstracts the `sqlite3_vfs` struct, allowing for a more idiomatic Rust implementation.
431#[allow(clippy::missing_safety_doc)]
432pub trait SQLiteVfs<IO: SQLiteIoMethods> {
433    const VERSION: ::core::ffi::c_int;
434    const MAX_PATH_SIZE: ::core::ffi::c_int = 1024;
435
436    fn vfs(
437        vfs_name: *const ::core::ffi::c_char,
438        app_data: *mut VfsAppData<IO::AppData>,
439    ) -> sqlite3_vfs {
440        sqlite3_vfs {
441            iVersion: Self::VERSION,
442            szOsFile: core::mem::size_of::<SQLiteVfsFile>() as i32,
443            mxPathname: Self::MAX_PATH_SIZE,
444            pNext: core::ptr::null_mut(),
445            zName: vfs_name,
446            pAppData: app_data.cast(),
447            xOpen: Some(Self::xOpen),
448            xDelete: Some(Self::xDelete),
449            xAccess: Some(Self::xAccess),
450            xFullPathname: Some(Self::xFullPathname),
451            xDlOpen: None,
452            xDlError: None,
453            xDlSym: None,
454            xDlClose: None,
455            xRandomness: Some(x_methods_shim::xRandomness),
456            xSleep: Some(x_methods_shim::xSleep),
457            xCurrentTime: Some(x_methods_shim::xCurrentTime),
458            xGetLastError: Some(Self::xGetLastError),
459            xCurrentTimeInt64: Some(x_methods_shim::xCurrentTimeInt64),
460            xSetSystemCall: None,
461            xGetSystemCall: None,
462            xNextSystemCall: None,
463        }
464    }
465
466    unsafe extern "C" fn xOpen(
467        pVfs: *mut sqlite3_vfs,
468        zName: sqlite3_filename,
469        pFile: *mut sqlite3_file,
470        flags: ::core::ffi::c_int,
471        pOutFlags: *mut ::core::ffi::c_int,
472    ) -> ::core::ffi::c_int {
473        Self::xOpenImpl(pVfs, zName, pFile, flags, pOutFlags)
474    }
475
476    unsafe extern "C" fn xOpenImpl(
477        pVfs: *mut sqlite3_vfs,
478        zName: sqlite3_filename,
479        pFile: *mut sqlite3_file,
480        flags: ::core::ffi::c_int,
481        pOutFlags: *mut ::core::ffi::c_int,
482    ) -> ::core::ffi::c_int {
483        let app_data = IO::Store::app_data(pVfs);
484
485        let name = if zName.is_null() {
486            random_name()
487        } else {
488            check_result!(CStr::from_ptr(zName).to_str()).into()
489        };
490
491        let exist = match IO::Store::contains_file(pVfs, &name) {
492            Ok(exist) => exist,
493            Err(err) => return app_data.store_err(err),
494        };
495
496        if !exist {
497            if flags & SQLITE_OPEN_CREATE == 0 {
498                return app_data.store_err(VfsError::new(
499                    SQLITE_CANTOPEN,
500                    format!("file not found: {name}"),
501                ));
502            }
503            if let Err(err) = IO::Store::add_file(pVfs, &name, flags) {
504                return app_data.store_err(err);
505            }
506        }
507
508        let leak = name.leak();
509        let vfs_file = pFile.cast::<SQLiteVfsFile>();
510        (*vfs_file).vfs = pVfs;
511        (*vfs_file).flags = flags;
512        (*vfs_file).name_ptr = leak.as_ptr();
513        (*vfs_file).name_length = leak.len();
514
515        (*pFile).pMethods = &IO::METHODS;
516
517        if !pOutFlags.is_null() {
518            *pOutFlags = flags;
519        }
520
521        SQLITE_OK
522    }
523
524    unsafe extern "C" fn xDelete(
525        pVfs: *mut sqlite3_vfs,
526        zName: *const ::core::ffi::c_char,
527        syncDir: ::core::ffi::c_int,
528    ) -> ::core::ffi::c_int {
529        unused!(syncDir);
530
531        let app_data = IO::Store::app_data(pVfs);
532        bail!(zName.is_null(), SQLITE_IOERR_DELETE);
533        let s = check_result!(CStr::from_ptr(zName).to_str());
534        if let Err(err) = IO::Store::delete_file(pVfs, s) {
535            app_data.store_err(err)
536        } else {
537            SQLITE_OK
538        }
539    }
540
541    unsafe extern "C" fn xAccess(
542        pVfs: *mut sqlite3_vfs,
543        zName: *const ::core::ffi::c_char,
544        flags: ::core::ffi::c_int,
545        pResOut: *mut ::core::ffi::c_int,
546    ) -> ::core::ffi::c_int {
547        unused!(flags);
548
549        *pResOut = if zName.is_null() {
550            0
551        } else {
552            let app_data = IO::Store::app_data(pVfs);
553            let file = check_result!(CStr::from_ptr(zName).to_str());
554            let exist = match IO::Store::contains_file(pVfs, file) {
555                Ok(exist) => exist,
556                Err(err) => return app_data.store_err(err),
557            };
558            i32::from(exist)
559        };
560
561        SQLITE_OK
562    }
563
564    unsafe extern "C" fn xFullPathname(
565        pVfs: *mut sqlite3_vfs,
566        zName: *const ::core::ffi::c_char,
567        nOut: ::core::ffi::c_int,
568        zOut: *mut ::core::ffi::c_char,
569    ) -> ::core::ffi::c_int {
570        unused!(pVfs);
571        bail!(zName.is_null() || zOut.is_null(), SQLITE_CANTOPEN);
572        let len = CStr::from_ptr(zName).to_bytes_with_nul().len();
573        bail!(len > nOut as usize, SQLITE_CANTOPEN);
574        zName.copy_to(zOut, len);
575        SQLITE_OK
576    }
577
578    unsafe extern "C" fn xGetLastError(
579        pVfs: *mut sqlite3_vfs,
580        nOut: ::core::ffi::c_int,
581        zOut: *mut ::core::ffi::c_char,
582    ) -> ::core::ffi::c_int {
583        let app_data = IO::Store::app_data(pVfs);
584        let Some((code, msg)) = app_data.pop_err() else {
585            return SQLITE_OK;
586        };
587        if !zOut.is_null() {
588            let nOut = nOut as usize;
589            let count = msg.len().min(nOut);
590            msg.as_ptr().copy_to(zOut.cast(), count);
591            let zero = match nOut.cmp(&msg.len()) {
592                core::cmp::Ordering::Less | core::cmp::Ordering::Equal => nOut,
593                core::cmp::Ordering::Greater => msg.len() + 1,
594            };
595            if zero > 0 {
596                core::ptr::write(zOut.add(zero - 1), 0);
597            }
598        }
599        code
600    }
601}
602
603/// A trait that abstracts the `sqlite3_io_methods` struct, allowing for a more idiomatic Rust implementation.
604#[allow(clippy::missing_safety_doc)]
605pub trait SQLiteIoMethods {
606    type File: VfsFile;
607    type AppData: 'static;
608    type Store: VfsStore<Self::File, Self::AppData>;
609
610    const VERSION: ::core::ffi::c_int;
611
612    const METHODS: sqlite3_io_methods = sqlite3_io_methods {
613        iVersion: Self::VERSION,
614        xClose: Some(Self::xClose),
615        xRead: Some(Self::xRead),
616        xWrite: Some(Self::xWrite),
617        xTruncate: Some(Self::xTruncate),
618        xSync: Some(Self::xSync),
619        xFileSize: Some(Self::xFileSize),
620        xLock: Some(Self::xLock),
621        xUnlock: Some(Self::xUnlock),
622        xCheckReservedLock: Some(Self::xCheckReservedLock),
623        xFileControl: Some(Self::xFileControl),
624        xSectorSize: Some(Self::xSectorSize),
625        xDeviceCharacteristics: Some(Self::xDeviceCharacteristics),
626        xShmMap: None,
627        xShmLock: None,
628        xShmBarrier: None,
629        xShmUnmap: None,
630        xFetch: None,
631        xUnfetch: None,
632    };
633
634    unsafe extern "C" fn xClose(pFile: *mut sqlite3_file) -> ::core::ffi::c_int {
635        Self::xCloseImpl(pFile)
636    }
637
638    unsafe extern "C" fn xCloseImpl(pFile: *mut sqlite3_file) -> ::core::ffi::c_int {
639        let vfs_file = SQLiteVfsFile::from_file(pFile);
640        let app_data = Self::Store::app_data(vfs_file.vfs);
641
642        if vfs_file.flags & SQLITE_OPEN_DELETEONCLOSE != 0 {
643            if let Err(err) = Self::Store::delete_file(vfs_file.vfs, vfs_file.name()) {
644                return app_data.store_err(err);
645            }
646        }
647
648        drop(Box::from_raw(vfs_file.name()));
649
650        SQLITE_OK
651    }
652
653    unsafe extern "C" fn xRead(
654        pFile: *mut sqlite3_file,
655        zBuf: *mut ::core::ffi::c_void,
656        iAmt: ::core::ffi::c_int,
657        iOfst: sqlite3_int64,
658    ) -> ::core::ffi::c_int {
659        let vfs_file = SQLiteVfsFile::from_file(pFile);
660        let app_data = Self::Store::app_data(vfs_file.vfs);
661
662        let f = |file: &Self::File| {
663            let size = iAmt as usize;
664            let offset = iOfst as usize;
665            let slice = core::slice::from_raw_parts_mut(zBuf.cast::<u8>(), size);
666            let code = if file.read(slice, offset)? {
667                SQLITE_OK
668            } else {
669                SQLITE_IOERR_SHORT_READ
670            };
671            Ok(code)
672        };
673
674        match Self::Store::with_file(vfs_file, f) {
675            Ok(code) => code,
676            Err(err) => app_data.store_err(err),
677        }
678    }
679
680    unsafe extern "C" fn xWrite(
681        pFile: *mut sqlite3_file,
682        zBuf: *const ::core::ffi::c_void,
683        iAmt: ::core::ffi::c_int,
684        iOfst: sqlite3_int64,
685    ) -> ::core::ffi::c_int {
686        let vfs_file = SQLiteVfsFile::from_file(pFile);
687        let app_data = Self::Store::app_data(vfs_file.vfs);
688
689        let f = |file: &mut Self::File| {
690            let (offset, size) = (iOfst as usize, iAmt as usize);
691            let slice = core::slice::from_raw_parts(zBuf.cast::<u8>(), size);
692            file.write(slice, offset)?;
693            Ok(SQLITE_OK)
694        };
695
696        match Self::Store::with_file_mut(vfs_file, f) {
697            Ok(code) => code,
698            Err(err) => app_data.store_err(err),
699        }
700    }
701
702    unsafe extern "C" fn xTruncate(
703        pFile: *mut sqlite3_file,
704        size: sqlite3_int64,
705    ) -> ::core::ffi::c_int {
706        let vfs_file = SQLiteVfsFile::from_file(pFile);
707        let app_data = Self::Store::app_data(vfs_file.vfs);
708
709        let f = |file: &mut Self::File| {
710            file.truncate(size as usize)?;
711            Ok(SQLITE_OK)
712        };
713
714        match Self::Store::with_file_mut(vfs_file, f) {
715            Ok(code) => code,
716            Err(err) => app_data.store_err(err),
717        }
718    }
719
720    unsafe extern "C" fn xSync(
721        pFile: *mut sqlite3_file,
722        flags: ::core::ffi::c_int,
723    ) -> ::core::ffi::c_int {
724        unused!(flags);
725
726        let vfs_file = SQLiteVfsFile::from_file(pFile);
727        let app_data = Self::Store::app_data(vfs_file.vfs);
728
729        let f = |file: &mut Self::File| {
730            file.flush()?;
731            Ok(SQLITE_OK)
732        };
733
734        match Self::Store::with_file_mut(vfs_file, f) {
735            Ok(code) => code,
736            Err(err) => app_data.store_err(err),
737        }
738    }
739
740    unsafe extern "C" fn xFileSize(
741        pFile: *mut sqlite3_file,
742        pSize: *mut sqlite3_int64,
743    ) -> ::core::ffi::c_int {
744        let vfs_file = SQLiteVfsFile::from_file(pFile);
745        let app_data = Self::Store::app_data(vfs_file.vfs);
746
747        let f = |file: &Self::File| {
748            file.size().map(|size| {
749                *pSize = size as sqlite3_int64;
750            })?;
751            Ok(SQLITE_OK)
752        };
753
754        match Self::Store::with_file(vfs_file, f) {
755            Ok(code) => code,
756            Err(err) => app_data.store_err(err),
757        }
758    }
759
760    unsafe extern "C" fn xLock(
761        pFile: *mut sqlite3_file,
762        eLock: ::core::ffi::c_int,
763    ) -> ::core::ffi::c_int {
764        unused!((pFile, eLock));
765        SQLITE_OK
766    }
767
768    unsafe extern "C" fn xUnlock(
769        pFile: *mut sqlite3_file,
770        eLock: ::core::ffi::c_int,
771    ) -> ::core::ffi::c_int {
772        unused!((pFile, eLock));
773        SQLITE_OK
774    }
775
776    unsafe extern "C" fn xCheckReservedLock(
777        pFile: *mut sqlite3_file,
778        pResOut: *mut ::core::ffi::c_int,
779    ) -> ::core::ffi::c_int {
780        unused!(pFile);
781        *pResOut = 0;
782        SQLITE_OK
783    }
784
785    unsafe extern "C" fn xFileControl(
786        pFile: *mut sqlite3_file,
787        op: ::core::ffi::c_int,
788        pArg: *mut ::core::ffi::c_void,
789    ) -> ::core::ffi::c_int {
790        unused!((pFile, op, pArg));
791        SQLITE_NOTFOUND
792    }
793
794    unsafe extern "C" fn xSectorSize(pFile: *mut sqlite3_file) -> ::core::ffi::c_int {
795        unused!(pFile);
796        512
797    }
798
799    unsafe extern "C" fn xDeviceCharacteristics(pFile: *mut sqlite3_file) -> ::core::ffi::c_int {
800        unused!(pFile);
801        0
802    }
803}
804
805/// A module containing shims for VFS methods that are implemented using JavaScript interoperability.
806#[allow(clippy::missing_safety_doc)]
807pub mod x_methods_shim {
808    use super::*;
809
810    /// thread::sleep is available when atomics is enabled
811    #[cfg(target_feature = "atomics")]
812    pub unsafe extern "C" fn xSleep(
813        _pVfs: *mut sqlite3_vfs,
814        microseconds: ::core::ffi::c_int,
815    ) -> ::core::ffi::c_int {
816        use core::time::Duration;
817
818        // Use an atomic wait to block the current thread artificially with a
819        // timeout listed. Note that we should never be notified (return value
820        // of 0) or our comparison should never fail (return value of 1) so we
821        // should always only resume execution through a timeout (return value
822        // 2).
823        let dur = Duration::from_micros(microseconds as u64);
824        let mut nanos = dur.as_nanos();
825        while nanos > 0 {
826            let amt = core::cmp::min(i64::MAX as u128, nanos);
827            let mut x = 0;
828            let val = unsafe { core::arch::wasm32::memory_atomic_wait32(&mut x, 0, amt as i64) };
829            debug_assert_eq!(val, 2);
830            nanos -= amt;
831        }
832        SQLITE_OK
833    }
834
835    #[cfg(not(target_feature = "atomics"))]
836    pub unsafe extern "C" fn xSleep(
837        _pVfs: *mut sqlite3_vfs,
838        _microseconds: ::core::ffi::c_int,
839    ) -> ::core::ffi::c_int {
840        SQLITE_OK
841    }
842
843    /// <https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L951>
844    pub unsafe extern "C" fn xRandomness(
845        _pVfs: *mut sqlite3_vfs,
846        nByte: ::core::ffi::c_int,
847        zOut: *mut ::core::ffi::c_char,
848    ) -> ::core::ffi::c_int {
849        for i in 0..nByte as usize {
850            *zOut.add(i) = (Math::random() * 255000.0) as _;
851        }
852        nByte
853    }
854
855    /// <https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L870>
856    pub unsafe extern "C" fn xCurrentTime(
857        _pVfs: *mut sqlite3_vfs,
858        pTimeOut: *mut f64,
859    ) -> ::core::ffi::c_int {
860        *pTimeOut = 2440587.5 + (Date::new_0().get_time() / 86400000.0);
861        SQLITE_OK
862    }
863
864    /// <https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L877>
865    pub unsafe extern "C" fn xCurrentTimeInt64(
866        _pVfs: *mut sqlite3_vfs,
867        pOut: *mut sqlite3_int64,
868    ) -> ::core::ffi::c_int {
869        *pOut = ((2440587.5 * 86400000.0) + Date::new_0().get_time()) as sqlite3_int64;
870        SQLITE_OK
871    }
872}
873
874#[derive(thiserror::Error, Debug)]
875pub enum ImportDbError {
876    #[error("Byte array size is invalid for an SQLite db.")]
877    InvalidDbSize,
878    #[error("Input does not contain an SQLite database header.")]
879    InvalidHeader,
880    #[error("Page size must be a power of two between 512 and 65536 inclusive")]
881    InvalidPageSize,
882}
883
884/// Simple verification when importing db, and return page size;
885pub fn check_import_db(bytes: &[u8]) -> Result<usize, ImportDbError> {
886    let length = bytes.len();
887
888    if length < 512 || length % 512 != 0 {
889        return Err(ImportDbError::InvalidDbSize);
890    }
891
892    if SQLITE3_HEADER
893        .as_bytes()
894        .iter()
895        .zip(bytes)
896        .any(|(x, y)| x != y)
897    {
898        return Err(ImportDbError::InvalidHeader);
899    }
900
901    // The database page size in bytes.
902    // Must be a power of two between 512 and 32768 inclusive, or the value 1 representing a page size of 65536.
903    let page_size = u16::from_be_bytes([bytes[16], bytes[17]]);
904    let page_size = if page_size == 1 {
905        65536
906    } else {
907        usize::from(page_size)
908    };
909
910    Ok(page_size)
911}
912
913/// Check db and page size, page size must be a power of two between 512 and 65536 inclusive, db size must be a multiple of page size.
914pub fn check_db_and_page_size(db_size: usize, page_size: usize) -> Result<(), ImportDbError> {
915    if !(page_size.is_power_of_two() && (512..=65536).contains(&page_size)) {
916        return Err(ImportDbError::InvalidPageSize);
917    }
918    if db_size % page_size != 0 {
919        return Err(ImportDbError::InvalidDbSize);
920    }
921    Ok(())
922}
923
924/// This is a testing utility for VFS, don't use it in production code.
925#[doc(hidden)]
926pub mod test_suite {
927    use alloc::vec;
928
929    use super::{
930        sqlite3_file, sqlite3_vfs, SQLiteVfsFile, VfsAppData, VfsError, VfsFile, VfsResult,
931        VfsStore, SQLITE_IOERR, SQLITE_OK, SQLITE_OPEN_CREATE, SQLITE_OPEN_MAIN_DB,
932        SQLITE_OPEN_READWRITE,
933    };
934
935    fn test_vfs_file<File: VfsFile>(file: &mut File) -> VfsResult<i32> {
936        let base_offset = 1024 * 1024;
937
938        let mut write_buffer = vec![42; 64 * 1024];
939        let mut read_buffer = vec![42; base_offset + write_buffer.len()];
940        let hw = "hello world!";
941        write_buffer[0..hw.len()].copy_from_slice(hw.as_bytes());
942
943        file.write(&write_buffer, 0)?;
944        assert!(!file.read(&mut read_buffer, 0)?);
945        if &read_buffer[0..hw.len()] != hw.as_bytes()
946            || read_buffer[hw.len()..write_buffer.len()]
947                .iter()
948                .any(|&x| x != 42)
949            || read_buffer[write_buffer.len()..].iter().any(|&x| x != 0)
950        {
951            Err(VfsError::new(SQLITE_IOERR, "incorrect buffer data".into()))?;
952        }
953        if file.size()? != write_buffer.len() {
954            Err(VfsError::new(
955                SQLITE_IOERR,
956                "incorrect buffer length".into(),
957            ))?;
958        }
959
960        file.write(&write_buffer, base_offset)?;
961        if file.size()? != base_offset + write_buffer.len() {
962            Err(VfsError::new(
963                SQLITE_IOERR,
964                "incorrect buffer length".into(),
965            ))?;
966        }
967        assert!(file.read(&mut read_buffer, 0)?);
968        if &read_buffer[0..hw.len()] != hw.as_bytes()
969            || read_buffer[hw.len()..write_buffer.len()]
970                .iter()
971                .any(|&x| x != 42)
972            || read_buffer[write_buffer.len()..base_offset]
973                .iter()
974                .all(|&x| x != 0)
975            || &read_buffer[base_offset..base_offset + hw.len()] != hw.as_bytes()
976            || read_buffer[base_offset + hw.len()..]
977                .iter()
978                .any(|&x| x != 42)
979        {
980            Err(VfsError::new(SQLITE_IOERR, "incorrect buffer data".into()))?;
981        }
982
983        Ok(SQLITE_OK)
984    }
985
986    pub fn test_vfs_store<AppData, File: VfsFile, Store: VfsStore<File, AppData>>(
987        vfs_data: VfsAppData<AppData>,
988    ) -> VfsResult<()> {
989        let layout = core::alloc::Layout::new::<sqlite3_vfs>();
990        let vfs = unsafe {
991            let vfs = alloc::alloc::alloc(layout) as *mut sqlite3_vfs;
992            (*vfs).pAppData = vfs_data.leak().cast();
993            vfs
994        };
995
996        let test_file = |filename: &str, flags: i32| {
997            if Store::contains_file(vfs, filename)? {
998                Err(VfsError::new(SQLITE_IOERR, "found file before test".into()))?;
999            }
1000
1001            let vfs_file = SQLiteVfsFile {
1002                io_methods: sqlite3_file {
1003                    pMethods: core::ptr::null(),
1004                },
1005                vfs,
1006                flags,
1007                name_ptr: filename.as_ptr(),
1008                name_length: filename.len(),
1009            };
1010
1011            Store::add_file(vfs, filename, flags)?;
1012
1013            if !Store::contains_file(vfs, filename)? {
1014                Err(VfsError::new(
1015                    SQLITE_IOERR,
1016                    "not found file after create".into(),
1017                ))?;
1018            }
1019
1020            Store::with_file_mut(&vfs_file, |file| test_vfs_file(file))?;
1021
1022            Store::delete_file(vfs, filename)?;
1023
1024            if Store::contains_file(vfs, filename)? {
1025                Err(VfsError::new(
1026                    SQLITE_IOERR,
1027                    "found file after delete".into(),
1028                ))?;
1029            }
1030
1031            Ok(())
1032        };
1033
1034        test_file(
1035            "___test_vfs_store#1___",
1036            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB,
1037        )?;
1038
1039        test_file(
1040            "___test_vfs_store#2___",
1041            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
1042        )?;
1043
1044        unsafe {
1045            drop(VfsAppData::<AppData>::from_raw((*vfs).pAppData as *mut _));
1046            alloc::alloc::dealloc(vfs.cast(), layout);
1047        }
1048
1049        Ok(())
1050    }
1051}
1052
1053#[cfg(test)]
1054mod tests {
1055    use super::{MemChunksFile, VfsFile};
1056    use wasm_bindgen_test::wasm_bindgen_test;
1057
1058    #[wasm_bindgen_test]
1059    fn test_chunks_file() {
1060        let mut file = MemChunksFile::new(512);
1061        file.write(&[], 0).unwrap();
1062        assert!(file.size().unwrap() == 0);
1063
1064        let mut buffer = [1; 2];
1065        let ret = file.read(&mut buffer, 0).unwrap();
1066        assert_eq!(ret, false);
1067        assert_eq!([0; 2], buffer);
1068
1069        file.write(&[1], 0).unwrap();
1070        assert!(file.size().unwrap() == 1);
1071        let mut buffer = [2; 2];
1072        let ret = file.read(&mut buffer, 0).unwrap();
1073        assert_eq!(ret, false);
1074        assert_eq!([1, 0], buffer);
1075
1076        let mut file = MemChunksFile::new(512);
1077        file.write(&[1; 512], 0).unwrap();
1078        assert!(file.size().unwrap() == 512);
1079        assert!(file.chunks.len() == 1);
1080
1081        file.truncate(512).unwrap();
1082        assert!(file.size().unwrap() == 512);
1083        assert!(file.chunks.len() == 1);
1084
1085        file.write(&[41, 42, 43], 511).unwrap();
1086        assert!(file.size().unwrap() == 514);
1087        assert!(file.chunks.len() == 2);
1088
1089        let mut buffer = [0; 3];
1090        let ret = file.read(&mut buffer, 511).unwrap();
1091        assert_eq!(ret, true);
1092        assert_eq!(buffer, [41, 42, 43]);
1093
1094        file.truncate(513).unwrap();
1095        assert!(file.size().unwrap() == 513);
1096        assert!(file.chunks.len() == 2);
1097
1098        file.write(&[1], 2048).unwrap();
1099        assert!(file.size().unwrap() == 2049);
1100        assert!(file.chunks.len() == 5);
1101
1102        file.truncate(0).unwrap();
1103        assert!(file.size().unwrap() == 0);
1104        assert!(file.chunks.len() == 0);
1105    }
1106}