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