sqlite_wasm_rs/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#![doc = include_str!("../README.md")]

#[allow(non_upper_case_globals)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
mod libsqlite3;

#[allow(non_upper_case_globals)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
mod c;
mod fragile;
mod wasm;

/// These exported APIs are stable and will not have breaking changes.
pub mod export {
    /// C interface definition of sqlite
    pub use super::c::*;
    /// Some sqlite types copied from libsqlite3-sys
    pub use super::libsqlite3::*;
    /// See <https://sqlite.org/wasm/doc/trunk/persistence.md#vfs-opfs-sahpool>
    pub use super::wasm::OpfsSAHPoolUtil;
    /// `async fn init_sqlite()`: before using C-API, you must initialize sqlite, once.
    ///
    /// `fn sqlite()`: get the sqlite instance
    ///
    /// `OpfsSAHPoolCfg`: see <https://sqlite.org/wasm/doc/trunk/persistence.md#vfs-opfs-sahpool>
    ///
    /// `SQLite`: sqlite-wasm instance, only can be created by `init_sqlite`
    ///
    /// `SQLiteError`: initializing instance and other possible errors
    ///
    /// `Version`: sqlite-wasm version
    pub use super::{init_sqlite, sqlite, OpfsSAHPoolCfg, SQLite, SQLiteError, Version};
}

use fragile::FragileComfirmed;
use js_sys::{Object, WebAssembly};
use serde::{Deserialize, Serialize};
use std::{error::Error, fmt::Display, result::Result};
use tokio::sync::OnceCell;
use wasm::{CApi, OpfsSAHPoolUtil, Wasm};
use wasm_bindgen::JsCast;

/// Sqlite only needs to be initialized once
static SQLITE: OnceCell<SQLite> = OnceCell::const_new();

/// Initialize sqlite and opfs vfs
pub async fn init_sqlite() -> Result<&'static SQLite, SQLiteError> {
    SQLITE.get_or_try_init(SQLite::new).await
}

/// Get the current sqlite global instance
pub fn sqlite() -> Option<&'static SQLite> {
    SQLITE.get()
}

/// "Inline" sqlite wasm binary
const WASM: &[u8] = include_bytes!("jswasm/sqlite3.wasm");

/// Initialize sqlite parameters
///
/// Currently, only memory can be configured
#[derive(Serialize)]
struct InitOpts {
    /// sqlite wasm binary
    #[serde(rename = "wasmBinary")]
    wasm_binary: &'static [u8],
    /// opfs proxy uri
    #[serde(rename = "proxyUri")]
    proxy_uri: String,
}

/// SQLite version info
#[derive(Deserialize, Clone, Debug)]
pub struct Version {
    #[serde(rename = "libVersion")]
    pub lib_version: String,
    #[serde(rename = "libVersionNumber")]
    pub lib_version_number: u32,
    #[serde(rename = "sourceId")]
    pub source_id: String,
    #[serde(rename = "downloadVersion")]
    pub download_version: u32,
}

/// See <https://sqlite.org/wasm/doc/trunk/persistence.md#vfs-opfs-sahpool>
#[derive(Serialize, Clone, Debug)]
pub struct OpfsSAHPoolCfg {
    // If truthy, contents and filename mapping are removed from each SAH
    // as it is acquired during initalization of the VFS, leaving the VFS's
    // storage in a pristine state. Use this only for databases which need not
    // survive a page reload.
    #[serde(rename = "clearOnInit")]
    pub clear_on_init: bool,
    // Specifies the default capacity of the VFS, i.e. the number of files
    // it may contain.
    #[serde(rename = "initialCapacity")]
    pub initial_capacity: u32,
    // Specifies the OPFS directory name in which to store metadata for the VFS.
    pub directory: String,
    // Sets the name to register this VFS under.
    pub name: String,
    // It is an opt-in workaround for a particular browser quirk which can cause
    // initialization of this VFS to fail on its first attempt but to succeed if
    // a second attempt is tried a short time later
    #[serde(rename = "forceReinitIfPreviouslyFailed")]
    pub force_reinit_if_previously_failed: bool,
}

/// Possible errors in initializing sqlite
#[derive(Debug)]
pub enum SQLiteError {
    /// error in initializing module
    InitModule(js_sys::Error),
    /// serialization and deserialization errors
    Serde(serde_wasm_bindgen::Error),
    /// error in installing opfs sah pool vfs
    InstallOpfsSAHPoolVfs(js_sys::Error),
}

impl Display for SQLiteError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InitModule(msg) => f.debug_tuple("InitModule").field(msg).finish(),
            Self::Serde(msg) => f.debug_tuple("Serde").field(msg).finish(),
            Self::InstallOpfsSAHPoolVfs(msg) => {
                f.debug_tuple("InstallOpfsSAHPoolVfs").field(msg).finish()
            }
        }
    }
}

impl Error for SQLiteError {}

/// Wrapped sqlite instance
///
/// It is not sure about the multi-thread support of sqlite-wasm,
/// so use `Fragile` to limit it to one thread.
pub struct SQLite {
    ffi: FragileComfirmed<wasm::SQLite>,
    version: Version,
}

impl SQLite {
    /// # Errors
    ///
    /// `SQLiteError::Module`: error in initializing module
    ///
    /// `SQLiteError::Serde`: serialization and deserialization errors
    async fn new() -> Result<Self, SQLiteError> {
        let proxy_uri = wasm_bindgen::link_to!(module = "/src/jswasm/sqlite3-opfs-async-proxy.js");

        let opts = InitOpts {
            wasm_binary: WASM,
            proxy_uri,
        };

        let opts = serde_wasm_bindgen::to_value(&opts).map_err(SQLiteError::Serde)?;
        let module = wasm::SQLite::init(&Object::from(opts))
            .await
            .map_err(SQLiteError::InitModule)?;

        let sqlite = wasm::SQLite::new(module);

        let version =
            serde_wasm_bindgen::from_value(sqlite.version()).map_err(SQLiteError::Serde)?;

        let sqlite = Self {
            ffi: FragileComfirmed::new(sqlite),
            version,
        };

        Ok(sqlite)
    }

    /// install_opfs_sahpool() returns a Promise which resolves, on success, to a utility object
    /// which can be used to perform basic administration of the file pool (colloquially known as `PoolUtil`).
    ///
    /// # Errors
    ///
    /// `SQLiteError::Serde`: serialization and deserialization errors
    ///
    /// `SQLiteError::InstallOpfsSAHPoolVfs`: error in installing opfs sah pool vfs
    pub async fn install_opfs_sahpool(
        &self,
        cfg: Option<&OpfsSAHPoolCfg>,
    ) -> Result<OpfsSAHPoolUtil, SQLiteError> {
        let cfg = cfg
            .map(|cfg| {
                serde_wasm_bindgen::to_value(&cfg)
                    .map_err(SQLiteError::Serde)
                    .map(Object::from)
            })
            .transpose()?;
        self.ffi
            .handle()
            .install_opfs_sahpool(cfg.as_ref())
            .await
            .map_err(SQLiteError::InstallOpfsSAHPoolVfs)
    }

    /// SQLite version
    #[must_use]
    pub fn version(&self) -> &Version {
        &self.version
    }

    /// SQLite CAPI
    #[must_use]
    fn capi(&self) -> CApi {
        self.ffi.handle().capi()
    }

    /// SQLite memeory manager
    #[must_use]
    fn wasm(&self) -> Wasm {
        self.ffi.handle().wasm()
    }
}

/// Peek and Poke on the JS side
///
/// See <https://github.com/rustwasm/wasm-bindgen/issues/4395>
///
/// See <https://github.com/rustwasm/wasm-bindgen/issues/4392>
impl SQLite {
    unsafe fn poke_buf(&self, src: &[u8], dst: *mut u8) {
        let buf = wasm_bindgen::memory();
        let mem = buf.unchecked_ref::<WebAssembly::Memory>();
        self.ffi.poke_buf(mem, src.as_ptr(), dst, src.len() as u32)
    }

    unsafe fn peek<T>(&self, from: *mut u8, dst: &mut T) {
        let dst = std::ptr::from_ref(dst) as *mut u8;
        let slice = unsafe { std::slice::from_raw_parts_mut(dst, size_of::<T>()) };
        self.peek_buf(from, slice);
    }

    unsafe fn peek_buf(&self, src: *const u8, dst: &mut [u8]) {
        let buf = wasm_bindgen::memory();
        let mem = buf.unchecked_ref::<WebAssembly::Memory>();
        self.ffi
            .peek_buf(mem, src, dst.as_mut_ptr(), dst.len() as u32)
    }
}