sqlite_wasm_rs/vfs/
memory.rs

1//! memory vfs, used as the default VFS
2//!
3//! ```rust
4//! use sqlite_wasm_rs as ffi;
5//!
6//! fn open_db() {
7//!     // open with memory vfs
8//!     let mut db = std::ptr::null_mut();
9//!     let ret = unsafe {
10//!         ffi::sqlite3_open_v2(
11//!             c"mem.db".as_ptr().cast(),
12//!             &mut db as *mut _,
13//!             ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE,
14//!             std::ptr::null()
15//!         )
16//!     };
17//!     assert_eq!(ffi::SQLITE_OK, ret);
18//! }
19//! ```
20//!
21//! Data is stored in memory, this is the default vfs, and reading
22//! and writing are very fast, after all, in memory.
23//!
24//! Refresh the page and data will be lost, and you also need to
25//! pay attention to the memory size limit of the browser page.
26
27use crate::libsqlite3::*;
28use crate::vfs::utils::{
29    check_import_db, ImportDbError, MemChunksFile, SQLiteIoMethods, SQLiteVfs, SQLiteVfsFile,
30    VfsAppData, VfsError, VfsFile, VfsResult, VfsStore,
31};
32
33use std::cell::RefCell;
34use std::collections::HashMap;
35use std::ffi::CStr;
36
37const VFS_NAME: &CStr = c"memvfs";
38
39type Result<T> = std::result::Result<T, MemVfsError>;
40
41pub(crate) enum MemFile {
42    Main(MemChunksFile),
43    Temp(MemChunksFile),
44}
45
46impl MemFile {
47    fn new(flags: i32) -> Self {
48        if flags & SQLITE_OPEN_MAIN_DB == 0 {
49            Self::Temp(MemChunksFile::default())
50        } else {
51            Self::Main(MemChunksFile::waiting_for_write())
52        }
53    }
54
55    fn file(&self) -> &MemChunksFile {
56        let (MemFile::Main(file) | MemFile::Temp(file)) = self;
57        file
58    }
59
60    fn file_mut(&mut self) -> &mut MemChunksFile {
61        let (MemFile::Main(file) | MemFile::Temp(file)) = self;
62        file
63    }
64}
65
66impl VfsFile for MemFile {
67    fn read(&self, buf: &mut [u8], offset: usize) -> VfsResult<bool> {
68        self.file().read(buf, offset)
69    }
70
71    fn write(&mut self, buf: &[u8], offset: usize) -> VfsResult<()> {
72        self.file_mut().write(buf, offset)
73    }
74
75    fn truncate(&mut self, size: usize) -> VfsResult<()> {
76        self.file_mut().truncate(size)
77    }
78
79    fn flush(&mut self) -> VfsResult<()> {
80        self.file_mut().flush()
81    }
82
83    fn size(&self) -> VfsResult<usize> {
84        self.file().size()
85    }
86}
87
88type MemAppData = RefCell<HashMap<String, MemFile>>;
89
90struct MemStore;
91
92impl VfsStore<MemFile, MemAppData> for MemStore {
93    fn add_file(vfs: *mut sqlite3_vfs, file: &str, flags: i32) -> VfsResult<()> {
94        let app_data = unsafe { Self::app_data(vfs) };
95        app_data
96            .borrow_mut()
97            .insert(file.into(), MemFile::new(flags));
98        Ok(())
99    }
100
101    fn contains_file(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<bool> {
102        let app_data = unsafe { Self::app_data(vfs) };
103        Ok(app_data.borrow().contains_key(file))
104    }
105
106    fn delete_file(vfs: *mut sqlite3_vfs, file: &str) -> VfsResult<()> {
107        let app_data = unsafe { Self::app_data(vfs) };
108        if app_data.borrow_mut().remove(file).is_none() {
109            return Err(VfsError::new(
110                SQLITE_IOERR_DELETE,
111                format!("{file} not found"),
112            ));
113        }
114        Ok(())
115    }
116
117    fn with_file<F: Fn(&MemFile) -> VfsResult<i32>>(
118        vfs_file: &SQLiteVfsFile,
119        f: F,
120    ) -> VfsResult<i32> {
121        let name = unsafe { vfs_file.name() };
122        let app_data = unsafe { Self::app_data(vfs_file.vfs) };
123        match app_data.borrow().get(name) {
124            Some(file) => f(file),
125            None => Err(VfsError::new(SQLITE_IOERR, format!("{name} not found"))),
126        }
127    }
128
129    fn with_file_mut<F: Fn(&mut MemFile) -> VfsResult<i32>>(
130        vfs_file: &SQLiteVfsFile,
131        f: F,
132    ) -> VfsResult<i32> {
133        let name = unsafe { vfs_file.name() };
134        let app_data = unsafe { Self::app_data(vfs_file.vfs) };
135        match app_data.borrow_mut().get_mut(name) {
136            Some(file) => f(file),
137            None => Err(VfsError::new(SQLITE_IOERR, format!("{name} not found"))),
138        }
139    }
140}
141
142struct MemIoMethods;
143
144impl SQLiteIoMethods for MemIoMethods {
145    type File = MemFile;
146    type AppData = MemAppData;
147    type Store = MemStore;
148
149    const VERSION: ::std::os::raw::c_int = 1;
150}
151
152struct MemVfs;
153
154impl SQLiteVfs<MemIoMethods> for MemVfs {
155    const VERSION: ::std::os::raw::c_int = 1;
156}
157
158#[derive(thiserror::Error, Debug)]
159pub enum MemVfsError {
160    #[error(transparent)]
161    ImportDb(#[from] ImportDbError),
162    #[error("Generic error: {0}")]
163    Generic(String),
164}
165
166/// MemVfs management tool.
167pub struct MemVfsUtil(&'static VfsAppData<MemAppData>);
168
169/// Because it was previously implemented with `Send` + `Sync` by mistake,
170/// it is temporarily retained for compatibility reasons and will be
171/// removed in the next major version update.
172unsafe impl Send for MemVfsUtil {}
173unsafe impl Sync for MemVfsUtil {}
174
175impl Default for MemVfsUtil {
176    fn default() -> Self {
177        MemVfsUtil::new()
178    }
179}
180
181impl MemVfsUtil {
182    /// Get management tool
183    pub fn new() -> Self {
184        MemVfsUtil(unsafe { install() })
185    }
186}
187
188impl MemVfsUtil {
189    fn import_db_unchecked_impl(
190        &self,
191        filename: &str,
192        bytes: &[u8],
193        page_size: usize,
194        clear_wal: bool,
195    ) -> Result<()> {
196        if self.exists(filename) {
197            return Err(MemVfsError::Generic(format!(
198                "{filename} file already exists"
199            )));
200        }
201
202        self.0.borrow_mut().insert(filename.into(), {
203            let mut file = MemFile::Main(MemChunksFile::new(page_size));
204            file.write(bytes, 0).unwrap();
205            if clear_wal {
206                file.write(&[1, 1], 18).unwrap();
207            }
208            file
209        });
210
211        Ok(())
212    }
213
214    /// Import the database.
215    ///
216    /// If the database is imported with WAL mode enabled,
217    /// it will be forced to write back to legacy mode, see
218    /// <https://sqlite.org/forum/forumpost/67882c5b04>
219    ///
220    /// If the imported database is encrypted, use `import_db_unchecked` instead.
221    pub fn import_db(&self, filename: &str, bytes: &[u8]) -> Result<()> {
222        let page_size = check_import_db(bytes)?;
223        self.import_db_unchecked_impl(filename, bytes, page_size, true)
224    }
225
226    /// `import_db` without checking, can be used to import encrypted database.
227    pub fn import_db_unchecked(
228        &self,
229        filename: &str,
230        bytes: &[u8],
231        page_size: usize,
232    ) -> Result<()> {
233        self.import_db_unchecked_impl(filename, bytes, page_size, false)
234    }
235
236    /// Export the database.
237    pub fn export_db(&self, filename: &str) -> Result<Vec<u8>> {
238        let name2file = self.0.borrow();
239
240        if let Some(file) = name2file.get(filename) {
241            let file_size = file.size().unwrap();
242            let mut ret = vec![0; file_size];
243            file.read(&mut ret, 0).unwrap();
244            Ok(ret)
245        } else {
246            Err(MemVfsError::Generic(
247                "The file to be exported does not exist".into(),
248            ))
249        }
250    }
251
252    /// Delete the specified database, make sure that the database is closed.
253    pub fn delete_db(&self, filename: &str) {
254        self.0.borrow_mut().remove(filename);
255    }
256
257    /// Delete all database, make sure that all database is closed.
258    pub fn clear_all(&self) {
259        std::mem::take(&mut *self.0.borrow_mut());
260    }
261
262    /// Does the database exists.
263    pub fn exists(&self, filename: &str) -> bool {
264        self.0.borrow().contains_key(filename)
265    }
266
267    /// List all files.
268    pub fn list(&self) -> Vec<String> {
269        self.0.borrow().keys().cloned().collect()
270    }
271
272    /// Number of files.
273    pub fn count(&self) -> usize {
274        self.0.borrow().len()
275    }
276}
277
278pub(crate) unsafe fn install() -> &'static VfsAppData<MemAppData> {
279    let vfs = sqlite3_vfs_find(VFS_NAME.as_ptr());
280
281    let vfs = if vfs.is_null() {
282        let vfs = Box::leak(Box::new(MemVfs::vfs(
283            VFS_NAME.as_ptr(),
284            VfsAppData::new(MemAppData::default()).leak(),
285        )));
286        assert_eq!(
287            sqlite3_vfs_register(vfs, 1),
288            SQLITE_OK,
289            "failed to register memvfs"
290        );
291        vfs as *mut sqlite3_vfs
292    } else {
293        vfs
294    };
295
296    MemStore::app_data(vfs)
297}
298
299pub(crate) unsafe fn uninstall() {
300    let vfs = sqlite3_vfs_find(VFS_NAME.as_ptr());
301
302    if !vfs.is_null() {
303        assert_eq!(
304            sqlite3_vfs_unregister(vfs),
305            SQLITE_OK,
306            "failed to unregister memvfs"
307        );
308        drop(VfsAppData::<MemAppData>::from_raw(
309            (*vfs).pAppData as *mut _,
310        ));
311        drop(Box::from_raw(vfs));
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use crate::{
318        mem_vfs::{MemAppData, MemFile, MemStore},
319        utils::{test_suite::test_vfs_store, VfsAppData},
320    };
321    use wasm_bindgen_test::wasm_bindgen_test;
322
323    #[wasm_bindgen_test]
324    fn test_memory_vfs_store() {
325        test_vfs_store::<MemAppData, MemFile, MemStore>(VfsAppData::new(MemAppData::default()))
326            .unwrap();
327    }
328}