Skip to content

Commit 48c6111

Browse files
weiznichSpxg
andauthored
Refactor vfs setup (#156)
* Refactor vfs setup This change refactors the VFS setup to make different VFS implementations reusable by other projects. Specifically the memvfs implementation is untied from sqlite-wasm-rs so that it can be used with any rust sqlite project. For this we refactor the crate structure slightly. Instead of having a single sqlite-wasm-rs crate containing all relevant code we now have: * wsqlite3-sys which contains the bindings and handles linking the actual sqlite library * rsqlite-vfs which contains vfs definitions and memvfs implementation. This crate conditionally depends on libsqlite3-sys or sqlite-wasm-rs depending on the compilation target. So it can be used for different vfs implementations for different targets * sqlite-wasm-rs which is now essentially only a shim over the other crates reexporting most of them in the old locations + providing some platform specific impls. This mostly concerns randomness and timestamp access for wasm Platform specific behavior like randomness and timestamp access for the vfs implementations must be provided by the user by providing a type that handles callbacks for these cases. Otherwise the implementation can be shared. The reason for this is that the implementation of these platform specific function widly differ between platforms, for wasm you need to use web specific methods, for std targets you can use the standard library, for embedded platforms you would likely need to provide custom implementations. The wsqlite3-sys and rsqlite-vfs crate are implemented as ![no_std] crates. * pass `slqite3mc` feature * Efficient copy * Add WasmOsCallback random fallback * Remove unused deps --------- Co-authored-by: Spxg <unsafe@outlook.es>
1 parent c9e3bd2 commit 48c6111

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+707
-79
lines changed

.github/workflows/test.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ jobs:
6464
cd rusqlite
6565
printf "[patch.crates-io]\n" >> Cargo.toml
6666
printf "sqlite-wasm-rs = { path = \"..\" }\n" >> Cargo.toml
67+
printf "libsqlite3-sys = { path = \"libsqlite3-sys\"}\n" >> Cargo.toml
6768
WASM_BINDGEN_TEST_TIMEOUT=60 wasm-pack test --node --features modern-full
6869
6970
test_clippy:
@@ -182,3 +183,15 @@ jobs:
182183
cd tests
183184
wasm-pack test --chrome --headless
184185
wasm-pack test --chrome --headless --features sqlite3mc
186+
187+
test_vfs_native:
188+
strategy:
189+
matrix:
190+
os: [ubuntu-latest]
191+
runs-on: ${{ matrix.os }}
192+
steps:
193+
- uses: actions/checkout@v4
194+
- name: Test
195+
run: |
196+
cd crates/rsqlite-vfs
197+
cargo test

Cargo.toml

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,28 @@
11
[package]
22
name = "sqlite-wasm-rs"
33
version = "0.5.1"
4-
links = "wsqlite3"
5-
edition = "2021"
64
authors = ["Spxg <unsafe@outlook.es>"]
7-
readme = "README.md"
8-
license = "MIT"
95
repository = "https://github.com/Spxg/sqlite-wasm-rs"
106
description = "`wasm32-unknown-unknown` bindings to the libsqlite3 library."
117
categories = ["development-tools::ffi", "wasm", "database"]
128
keywords = ["sqlite", "sqlite-wasm", "wasm", "webassembly", "javascript"]
13-
rust-version = "1.82.0"
14-
include = [
15-
'src/**/*.rs',
16-
'build.rs',
17-
'sqlite3/**/*.c',
18-
'sqlite3/**/*.h',
19-
'sqlite3mc/**/*.c',
20-
'sqlite3mc/**/*.h',
21-
'shim/**/*.h',
22-
'shim/**/*.c',
23-
'Cargo.toml',
24-
'/README.md',
25-
'LICENSE',
26-
]
9+
readme.workspace = true
10+
edition.workspace = true
11+
license.workspace = true
12+
rust-version.workspace = true
13+
include.workspace = true
2714

2815
[dependencies]
2916
wasm-bindgen = { version = "0.2.104", default-features = false }
3017
js-sys = { version = "0.3.81", default-features = false }
31-
thiserror = { version = "2.0.12", default-features = false }
32-
hashbrown = { version = "0.16.1", default-features = false, features = ["default-hasher"] }
18+
wsqlite3-sys = { path = "crates/wsqlite3-sys" }
19+
rsqlite-vfs = { path = "crates/rsqlite-vfs" }
3320

3421
[features]
3522
# SQLite3MultipleCiphers
3623
# <https://github.com/utelle/SQLite3MultipleCiphers>
3724
# <https://utelle.github.io/SQLite3MultipleCiphers>
38-
sqlite3mc = []
25+
sqlite3mc = ["wsqlite3-sys/sqlite3mc"]
3926

4027
[dev-dependencies]
4128
wasm-bindgen-test = "0.3.54"
@@ -53,12 +40,34 @@ members = [
5340
"tests",
5441
"crates/sqlite-wasm-libc",
5542
"crates/sqlite-wasm-vfs",
43+
"crates/rsqlite-vfs",
44+
"crates/wsqlite3-sys",
5645
"extensions/sqlite-vec",
5746
"examples/implement-a-vfs",
5847
"examples/nodejs",
59-
"examples/use-prebuild-lib"
48+
"examples/use-prebuild-lib",
49+
]
50+
51+
[workspace.package]
52+
readme = "README.md"
53+
edition = "2021"
54+
license = "MIT"
55+
rust-version = "1.82.0"
56+
include = [
57+
'src/**/*.rs',
58+
'build.rs',
59+
'sqlite3/**/*.c',
60+
'sqlite3/**/*.h',
61+
'sqlite3mc/**/*.c',
62+
'sqlite3mc/**/*.h',
63+
'shim/**/*.h',
64+
'shim/**/*.c',
65+
'Cargo.toml',
66+
'/README.md',
67+
'LICENSE',
6068
]
6169

70+
6271
[patch.crates-io]
6372
sqlite-wasm-rs = { path = "./" }
6473
sqlite-wasm-vfs = { path = "./crates/sqlite-wasm-vfs" }

crates/rsqlite-vfs/Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "rsqlite-vfs"
3+
version = "0.1.0"
4+
readme.workspace = true
5+
edition.workspace = true
6+
license.workspace = true
7+
rust-version.workspace = true
8+
include.workspace = true
9+
10+
[dependencies]
11+
hashbrown = { version = "0.16.1", default-features = false, features = ["default-hasher"] }
12+
thiserror = { version = "2.0.12", default-features = false }
13+
14+
[target.'cfg(not(all(target_family = "wasm", target_os = "unknown")))'.dependencies]
15+
# libsqlite3-sys doesn't compile without features
16+
# see https://github.com/rusqlite/rusqlite/issues/1205
17+
libsqlite3-sys = { version = "0.35.0", default-features = false, features = ["vcpkg", "pkg-config"] }
18+
19+
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
20+
wsqlite3-sys = { default-features = false, path = "../wsqlite3-sys" }
21+
22+
[dev-dependencies]
23+
rand = { version = "0.9" }

src/utils.rs renamed to crates/rsqlite-vfs/src/lib.rs

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
11
//! Low-level utilities, traits, and macros for implementing custom SQLite Virtual File Systems (VFS).
2+
//!
3+
//! This crate also contains a simple operating system independent memvfs implementation
4+
#![no_std]
5+
#![allow(non_upper_case_globals)]
6+
#![allow(non_camel_case_types)]
7+
#![allow(non_snake_case)]
8+
#![cfg_attr(target_feature = "atomics", feature(stdarch_wasm_atomic_wait))]
29

3-
use crate::bindings::*;
10+
extern crate alloc;
11+
12+
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
13+
extern crate libsqlite3_sys as bindings;
14+
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
15+
extern crate wsqlite3_sys as bindings;
16+
17+
use self::bindings::*;
18+
19+
// libsqlite3-sys misses this type
20+
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
21+
#[allow(non_camel_case_types)]
22+
type sqlite3_filename = *const ::core::ffi::c_char;
423

524
use alloc::string::String;
625
use alloc::vec::Vec;
726
use alloc::{boxed::Box, ffi::CString};
827
use alloc::{format, vec};
928
use core::{cell::RefCell, ffi::CStr, ops::Deref};
10-
use js_sys::{Date, Math, Number};
29+
30+
pub mod memvfs;
1131

1232
/// A macro to return a specific SQLite error code if a condition is true.
1333
///
@@ -70,9 +90,20 @@ macro_rules! unused {
7090
pub const SQLITE3_HEADER: &str = "SQLite format 3";
7191

7292
/// Generates a random, temporary filename, typically used when SQLite requests a file with a NULL name.
73-
pub fn random_name() -> String {
74-
let random = Number::from(Math::random()).to_string(36).unwrap();
75-
random.slice(2, random.length()).as_string().unwrap()
93+
pub fn random_name(randomness: fn(&mut [u8])) -> String {
94+
const GEN_ASCII_STR_CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
95+
abcdefghijklmnopqrstuvwxyz\
96+
0123456789";
97+
const GEN_LEN: u8 = GEN_ASCII_STR_CHARSET.len() as u8;
98+
let mut random_buffer = [0; 32];
99+
randomness(&mut random_buffer);
100+
random_buffer
101+
.into_iter()
102+
.map(|e| {
103+
let idx = e.saturating_sub(GEN_LEN * (e / GEN_LEN));
104+
GEN_ASCII_STR_CHARSET[idx as usize] as char
105+
})
106+
.collect()
76107
}
77108

78109
/// An in-memory file implementation that stores data in fixed-size chunks. Suitable for temporary files.
@@ -433,6 +464,9 @@ pub trait SQLiteVfs<IO: SQLiteIoMethods> {
433464
const VERSION: ::core::ffi::c_int;
434465
const MAX_PATH_SIZE: ::core::ffi::c_int = 1024;
435466

467+
fn random(buf: &mut [u8]);
468+
fn epoch_timestamp_in_ms() -> i64;
469+
436470
fn vfs(
437471
vfs_name: *const ::core::ffi::c_char,
438472
app_data: *mut VfsAppData<IO::AppData>,
@@ -452,11 +486,11 @@ pub trait SQLiteVfs<IO: SQLiteIoMethods> {
452486
xDlError: None,
453487
xDlSym: None,
454488
xDlClose: None,
455-
xRandomness: Some(x_methods_shim::xRandomness),
489+
xRandomness: Some(Self::xRandomness),
456490
xSleep: Some(x_methods_shim::xSleep),
457-
xCurrentTime: Some(x_methods_shim::xCurrentTime),
491+
xCurrentTime: Some(Self::xCurrentTime),
458492
xGetLastError: Some(Self::xGetLastError),
459-
xCurrentTimeInt64: Some(x_methods_shim::xCurrentTimeInt64),
493+
xCurrentTimeInt64: Some(Self::xCurrentTimeInt64),
460494
xSetSystemCall: None,
461495
xGetSystemCall: None,
462496
xNextSystemCall: None,
@@ -483,7 +517,7 @@ pub trait SQLiteVfs<IO: SQLiteIoMethods> {
483517
let app_data = IO::Store::app_data(pVfs);
484518

485519
let name = if zName.is_null() {
486-
random_name()
520+
random_name(Self::random)
487521
} else {
488522
check_result!(CStr::from_ptr(zName).to_str()).into()
489523
};
@@ -598,6 +632,35 @@ pub trait SQLiteVfs<IO: SQLiteIoMethods> {
598632
}
599633
code
600634
}
635+
636+
/// <https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L951>
637+
unsafe extern "C" fn xRandomness(
638+
_pVfs: *mut sqlite3_vfs,
639+
nByte: ::core::ffi::c_int,
640+
zOut: *mut ::core::ffi::c_char,
641+
) -> ::core::ffi::c_int {
642+
let slice = core::slice::from_raw_parts_mut(zOut.cast(), nByte as usize);
643+
Self::random(slice);
644+
nByte
645+
}
646+
647+
/// <https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L870>
648+
unsafe extern "C" fn xCurrentTime(
649+
_pVfs: *mut sqlite3_vfs,
650+
pTimeOut: *mut f64,
651+
) -> ::core::ffi::c_int {
652+
*pTimeOut = 2440587.5 + (Self::epoch_timestamp_in_ms() as f64 / 86400000.0);
653+
SQLITE_OK
654+
}
655+
656+
/// <https://github.com/sqlite/sqlite/blob/fb9e8e48fd70b463fb7ba6d99e00f2be54df749e/ext/wasm/api/sqlite3-vfs-opfs.c-pp.js#L877>
657+
unsafe extern "C" fn xCurrentTimeInt64(
658+
_pVfs: *mut sqlite3_vfs,
659+
pOut: *mut sqlite3_int64,
660+
) -> ::core::ffi::c_int {
661+
*pOut = ((2440587.5 * 86400000.0) + Self::epoch_timestamp_in_ms() as f64) as sqlite3_int64;
662+
SQLITE_OK
663+
}
601664
}
602665

603666
/// A trait that abstracts the `sqlite3_io_methods` struct, allowing for a more idiomatic Rust implementation.
@@ -839,36 +902,6 @@ pub mod x_methods_shim {
839902
) -> ::core::ffi::c_int {
840903
SQLITE_OK
841904
}
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-
}
872905
}
873906

874907
#[derive(thiserror::Error, Debug)]
@@ -1050,7 +1083,7 @@ pub mod test_suite {
10501083
}
10511084
}
10521085

1053-
#[cfg(test)]
1086+
#[cfg(all(test, target_arch = "wasm32"))]
10541087
mod tests {
10551088
use super::{MemChunksFile, VfsFile};
10561089
use wasm_bindgen_test::wasm_bindgen_test;
@@ -1104,3 +1137,16 @@ mod tests {
11041137
assert!(file.chunks.len() == 0);
11051138
}
11061139
}
1140+
1141+
#[cfg(not(all(test, target_arch = "wasm32")))]
1142+
#[test]
1143+
fn random_name_is_valid() {
1144+
fn random(buf: &mut [u8]) {
1145+
rand::fill(buf);
1146+
}
1147+
let name_1 = random_name(random);
1148+
let name_2 = random_name(random);
1149+
assert!(name_1.is_ascii(), "Expected an ascii-name: `{name_1}`");
1150+
assert!(name_2.is_ascii(), "Expected an ascii-name: `{name_2}`");
1151+
assert_ne!(name_1, name_2);
1152+
}

0 commit comments

Comments
 (0)