lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 0c4bed11d173609ed50b0460e27c697abc75a9e8
parent 1e2c8796b7ece8d125fdceaae0c2fa1718d58e2d
Author: triesap <tyson@radroots.org>
Date:   Sat, 13 Jun 2026 02:28:17 -0700

sql_core: align dependent feature maps

- Make SQL core workspace dependencies opt into features explicitly
- Remove non-portable embedded SQLite from the wasm SQL facade
- Keep replica wasm bindings on the std-backed sync surface
- Update replica and coverage metadata for the repaired feature contract

Diffstat:
MCargo.lock | 41++---------------------------------------
MCargo.toml | 2+-
Mcrates/replica_db/Cargo.toml | 14++++++++++----
Mcrates/replica_db/README | 3+--
Mcrates/replica_db_wasm/Cargo.toml | 2+-
Mcrates/replica_db_wasm/src/utils.rs | 12------------
Mcrates/replica_sync/Cargo.toml | 1+
Mcrates/replica_sync_wasm/Cargo.toml | 2+-
Mcrates/sql_core/Cargo.toml | 5++++-
Mcrates/sql_wasm_core/Cargo.toml | 24++++++++++--------------
Mcrates/sql_wasm_core/README | 4+---
Dcrates/sql_wasm_core/src/embedded.rs | 493-------------------------------------------------------------------------------
Mcrates/sql_wasm_core/src/lib.rs | 112++++++++++---------------------------------------------------------------------
Mpolicy/coverage/profiles.toml | 2+-
14 files changed, 46 insertions(+), 671 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -678,24 +678,13 @@ dependencies = [ ] [[package]] -name = "chacha20" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.0", -] - -[[package]] name = "chacha20poly1305" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20 0.9.1", + "chacha20", "cipher", "poly1305", "zeroize", @@ -710,7 +699,6 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", - "serde", "wasm-bindgen", "windows-link", ] @@ -1916,7 +1904,6 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core 0.10.0", "wasip2", "wasip3", ] @@ -2884,7 +2871,7 @@ dependencies = [ "bip39", "bitcoin_hashes", "cbc", - "chacha20 0.9.1", + "chacha20", "chacha20poly1305", "getrandom 0.2.17", "hex", @@ -4577,16 +4564,10 @@ dependencies = [ name = "radroots_sql_wasm_core" version = "0.1.0-alpha.2" dependencies = [ - "chrono", - "js-sys", "radroots_sql_core", "radroots_sql_wasm_bridge", - "rusqlite", "serde", "serde-wasm-bindgen", - "serde_json", - "thiserror 1.0.69", - "uuid", "wasm-bindgen", ] @@ -4639,17 +4620,6 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" -dependencies = [ - "chacha20 0.10.0", - "getrandom 0.4.2", - "rand_core 0.10.0", -] - -[[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4688,12 +4658,6 @@ dependencies = [ ] [[package]] -name = "rand_core" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" - -[[package]] name = "range-set-blaze" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -7541,7 +7505,6 @@ dependencies = [ "getrandom 0.4.2", "js-sys", "md-5", - "rand 0.10.0", "sha1_smol", "wasm-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml @@ -94,7 +94,7 @@ radroots_simplex_smp_proto = { path = "crates/simplex_smp_proto", version = "0.1 radroots_simplex_smp_transport = { path = "crates/simplex_smp_transport", version = "0.1.0-alpha.2", default-features = false } radroots_sql_wasm_bridge = { path = "crates/sql_wasm_bridge", version = "0.1.0-alpha.2" } radroots_sql_wasm_core = { path = "crates/sql_wasm_core", version = "0.1.0-alpha.2", default-features = false } -radroots_sql_core = { path = "crates/sql_core", version = "0.1.0-alpha.2" } +radroots_sql_core = { path = "crates/sql_core", version = "0.1.0-alpha.2", default-features = false } radroots_test_fixtures = { path = "crates/test_fixtures", version = "0.1.0-alpha.2" } radroots_replica_db_schema = { path = "crates/replica_db_schema", version = "0.1.0-alpha.2", default-features = false } radroots_replica_sync = { path = "crates/replica_sync", version = "0.1.0-alpha.2", default-features = false } diff --git a/crates/replica_db/Cargo.toml b/crates/replica_db/Cargo.toml @@ -16,10 +16,16 @@ readme = "README" crate-type = ["rlib"] [features] -default = [] -web = ["radroots_sql_core/web"] -native = ["radroots_sql_core/native"] -embedded = ["radroots_sql_core/embedded"] +default = ["std"] +std = ["radroots_sql_core/std"] +web = [ + "std", + "radroots_sql_core/web", +] +native = [ + "std", + "radroots_sql_core/native", +] coverage-minimal = [] [dependencies] diff --git a/crates/replica_db/README b/crates/replica_db/README @@ -9,8 +9,7 @@ facades and shaped query helpers for the `radroots` core libraries. * migration, backup, restore, and export helpers for replica database state; * typed CRUD helpers over the shared replica schema models; * shaped query helpers for common trade, farm, and event freshness lookups; - * feature-gated web, native, and embedded backend support through - `radroots_sql_core`. + * feature-gated web and native backend support through `radroots_sql_core`. ## Copyright diff --git a/crates/replica_db_wasm/Cargo.toml b/crates/replica_db_wasm/Cargo.toml @@ -22,7 +22,7 @@ radroots_sql_wasm_core = { workspace = true, default-features = false, features ] } radroots_replica_db = { workspace = true } radroots_replica_db_schema = { workspace = true } -radroots_replica_sync = { workspace = true } +radroots_replica_sync = { workspace = true, features = ["std"] } js-sys = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/crates/replica_db_wasm/src/utils.rs b/crates/replica_db_wasm/src/utils.rs @@ -1,20 +1,8 @@ use serde::Serialize; -use serde::de::DeserializeOwned; use wasm_bindgen::prelude::*; use radroots_sql_core::SqlError; -pub fn parse_optional_json<T>(json: &str) -> Result<Option<T>, serde_json::Error> -where - T: DeserializeOwned, -{ - if json.trim().is_empty() { - return Ok(None); - } - let value: Option<T> = serde_json::from_str(json)?; - Ok(value) -} - pub fn value_to_js<T>(value: T) -> Result<JsValue, JsValue> where T: Serialize, diff --git a/crates/replica_sync/Cargo.toml b/crates/replica_sync/Cargo.toml @@ -20,6 +20,7 @@ default = ["std"] std = [ "radroots_events/std", "radroots_events_codec/std", + "radroots_sql_core/std", "dep:base64", "dep:uuid", ] diff --git a/crates/replica_sync_wasm/Cargo.toml b/crates/replica_sync_wasm/Cargo.toml @@ -24,7 +24,7 @@ radroots_sql_core = { workspace = true, features = ["bridge"] } radroots_sql_wasm_core = { workspace = true, default-features = false, features = [ "bridge", ] } -radroots_replica_sync = { workspace = true } +radroots_replica_sync = { workspace = true, features = ["std"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } serde-wasm-bindgen = { workspace = true } diff --git a/crates/sql_core/Cargo.toml b/crates/sql_core/Cargo.toml @@ -23,7 +23,10 @@ std = [ "serde/std", "serde_json/std", ] -web = ["std"] +web = [ + "std", + "uuid/js", +] bridge = [ "std", "web", diff --git a/crates/sql_wasm_core/Cargo.toml b/crates/sql_wasm_core/Cargo.toml @@ -17,21 +17,17 @@ crate-type = ["cdylib", "rlib"] [features] default = ["bridge"] -bridge = ["dep:radroots_sql_wasm_bridge"] -embedded = ["dep:rusqlite", "radroots_sql_core/native"] +bridge = [ + "radroots_sql_core/bridge", + "dep:radroots_sql_wasm_bridge", + "dep:serde", + "dep:serde-wasm-bindgen", + "dep:wasm-bindgen", +] [dependencies] radroots_sql_core = { workspace = true } radroots_sql_wasm_bridge = { workspace = true, optional = true } -rusqlite = { workspace = true, features = [ - "bundled", - "serialize", -], optional = true } -chrono = { workspace = true, features = ["serde"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -serde-wasm-bindgen = { workspace = true } -thiserror = { workspace = true } -wasm-bindgen = { workspace = true } -js-sys = { workspace = true } -uuid = { workspace = true, features = ["v4", "fast-rng", "js"] } +serde = { workspace = true, optional = true } +serde-wasm-bindgen = { workspace = true, optional = true } +wasm-bindgen = { workspace = true, optional = true } diff --git a/crates/sql_wasm_core/README b/crates/sql_wasm_core/README @@ -7,9 +7,7 @@ runtime facade for the `radroots` core libraries. * shared wasm entry points for SQL execution, query, export, and transaction control; - * compile-time selection between the host bridge layer and an embedded SQL - engine; - * an optional singleton embedded engine for self-contained wasm runtimes; + * host bridge integration for browser and worker SQL runtimes; * small JSON parsing and JavaScript error helpers for wasm callers. ## Copyright diff --git a/crates/sql_wasm_core/src/embedded.rs b/crates/sql_wasm_core/src/embedded.rs @@ -1,493 +0,0 @@ -#![forbid(unsafe_code)] - -use std::sync::Mutex; - -use radroots_sql_core::sqlite_util; -use radroots_sql_core::{ExecOutcome, SqlError, SqlExecutor}; -use rusqlite::{Connection, MAIN_DB, params_from_iter}; -use serde_json::Value; - -const SAVEPOINT_BEGIN: &str = "savepoint radroots_schema_tx"; -const SAVEPOINT_RELEASE: &str = "release savepoint radroots_schema_tx"; -const SAVEPOINT_ROLLBACK: &str = "rollback to savepoint radroots_schema_tx"; - -#[cfg(test)] -mod failpoints { - use std::sync::atomic::{AtomicUsize, Ordering}; - - #[derive(Clone, Copy)] - pub enum Point { - Open = 1 << 0, - BeginExecute = 1 << 1, - ReleaseExecute = 1 << 2, - ExportSerialize = 1 << 3, - EncodeRows = 1 << 4, - RowToJson = 1 << 5, - } - - static FLAGS: AtomicUsize = AtomicUsize::new(0); - - pub fn set(point: Point) { - FLAGS.fetch_or(point as usize, Ordering::SeqCst); - } - - pub fn take(point: Point) -> bool { - let mask = point as usize; - let prev = FLAGS.fetch_and(!mask, Ordering::SeqCst); - (prev & mask) != 0 - } - - pub fn clear() { - FLAGS.store(0, Ordering::SeqCst); - } -} - -#[cfg(test)] -fn forced_error() -> rusqlite::Error { - rusqlite::Error::InvalidParameterName("forced".to_string()) -} - -fn open_in_memory_with_failpoint() -> Result<Connection, rusqlite::Error> { - #[cfg(test)] - if failpoints::take(failpoints::Point::Open) { - return Err(forced_error()); - } - Connection::open_in_memory() -} - -fn execute_begin_savepoint(conn: &Connection) -> Result<(), SqlError> { - #[cfg(test)] - let result = if failpoints::take(failpoints::Point::BeginExecute) { - Err(forced_error()) - } else { - conn.execute(SAVEPOINT_BEGIN, []) - }; - #[cfg(not(test))] - let result = conn.execute(SAVEPOINT_BEGIN, []); - result.map(|_| ()).map_err(map_rusqlite) -} - -fn execute_release_savepoint(conn: &Connection) -> Result<(), SqlError> { - #[cfg(test)] - let result = if failpoints::take(failpoints::Point::ReleaseExecute) { - Err(forced_error()) - } else { - conn.execute(SAVEPOINT_RELEASE, []) - }; - #[cfg(not(test))] - let result = conn.execute(SAVEPOINT_RELEASE, []); - result.map(|_| ()).map_err(map_rusqlite) -} - -fn serialize_main(conn: &Connection) -> Result<Vec<u8>, rusqlite::Error> { - #[cfg(test)] - if failpoints::take(failpoints::Point::ExportSerialize) { - return Err(forced_error()); - } - conn.serialize(MAIN_DB).map(|data| data.to_vec()) -} - -fn map_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Value> { - #[cfg(test)] - if failpoints::take(failpoints::Point::RowToJson) { - return Err(forced_error()); - } - sqlite_util::row_to_json(row) -} - -fn encode_rows(rows: &[Value]) -> Result<String, SqlError> { - #[cfg(test)] - if failpoints::take(failpoints::Point::EncodeRows) { - return serde_json::to_string(&FailSerialize).map_err(SqlError::from); - } - serde_json::to_string(rows).map_err(SqlError::from) -} - -#[cfg(test)] -struct FailSerialize; - -#[cfg(test)] -impl serde::Serialize for FailSerialize { - fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - Err(serde::ser::Error::custom("forced")) - } -} - -#[derive(Debug)] -pub struct EmbeddedSqlEngine { - conn: Mutex<Connection>, -} - -impl EmbeddedSqlEngine { - pub fn new() -> Result<Self, SqlError> { - let conn = open_in_memory_with_failpoint().map_err(map_rusqlite)?; - Ok(Self { - conn: Mutex::new(conn), - }) - } - - pub fn exec(&self, sql: &str, params_json: &str) -> Result<ExecOutcome, SqlError> { - let binds = sqlite_util::parse_params(params_json)?; - let conn = self.conn.lock().map_err(|_| SqlError::Internal)?; - if binds.is_empty() { - let total_changes_before = conn.total_changes(); - conn.execute_batch(sql).map_err(map_rusqlite)?; - let total_changes_after = conn.total_changes(); - let last_insert_id = conn.last_insert_rowid(); - return Ok(ExecOutcome { - changes: (total_changes_after - total_changes_before) as i64, - last_insert_id, - }); - } - let changes = conn - .execute(sql, params_from_iter(binds.into_iter())) - .map_err(map_rusqlite)?; - let last_insert_id = conn.last_insert_rowid(); - Ok(ExecOutcome { - changes: changes as i64, - last_insert_id, - }) - } - - pub fn query_rows(&self, sql: &str, params_json: &str) -> Result<Vec<Value>, SqlError> { - let binds = sqlite_util::parse_params(params_json)?; - let rows = { - let conn = self.conn.lock().map_err(|_| SqlError::Internal)?; - let mut stmt = conn.prepare(sql).map_err(map_rusqlite)?; - let params = params_from_iter(binds.into_iter()); - let mapped = stmt.query_map(params, map_row)?; - mapped - .collect::<Result<Vec<_>, _>>() - .map_err(map_rusqlite)? - }; - Ok(rows) - } - - pub fn query_raw(&self, sql: &str, params_json: &str) -> Result<String, SqlError> { - let rows = self.query_rows(sql, params_json)?; - encode_rows(&rows) - } - - pub fn begin_tx(&self) -> Result<(), SqlError> { - let conn = self.conn.lock().map_err(|_| SqlError::Internal)?; - execute_begin_savepoint(&conn) - } - - pub fn commit_tx(&self) -> Result<(), SqlError> { - let conn = self.conn.lock().map_err(|_| SqlError::Internal)?; - execute_release_savepoint(&conn) - } - - pub fn rollback_tx(&self) -> Result<(), SqlError> { - let conn = self.conn.lock().map_err(|_| SqlError::Internal)?; - conn.execute(SAVEPOINT_ROLLBACK, []).map_err(map_rusqlite)?; - execute_release_savepoint(&conn) - } - - pub fn export_bytes(&self) -> Result<Vec<u8>, SqlError> { - let conn = self.conn.lock().map_err(|_| SqlError::Internal)?; - let data = serialize_main(&conn).map_err(map_rusqlite)?; - Ok(data) - } -} - -impl SqlExecutor for EmbeddedSqlEngine { - fn exec(&self, sql: &str, params_json: &str) -> Result<ExecOutcome, SqlError> { - EmbeddedSqlEngine::exec(self, sql, params_json) - } - - fn query_raw(&self, sql: &str, params_json: &str) -> Result<String, SqlError> { - EmbeddedSqlEngine::query_raw(self, sql, params_json) - } - - fn begin(&self) -> Result<(), SqlError> { - EmbeddedSqlEngine::begin_tx(self) - } - - fn commit(&self) -> Result<(), SqlError> { - EmbeddedSqlEngine::commit_tx(self) - } - - fn rollback(&self) -> Result<(), SqlError> { - EmbeddedSqlEngine::rollback_tx(self) - } -} - -fn map_rusqlite(err: rusqlite::Error) -> SqlError { - SqlError::InvalidQuery(err.to_string()) -} - -pub fn coverage_branch_probe(input: bool) -> &'static str { - if input { "sql" } else { "sql" } -} - -#[cfg(all(test, feature = "embedded"))] -mod tests { - use super::{EmbeddedSqlEngine, coverage_branch_probe, failpoints}; - use radroots_sql_core::SqlExecutor; - - const CREATE_TABLE_SQL: &str = "CREATE TABLE test_items (id INTEGER PRIMARY KEY, name TEXT)"; - - fn poison_engine(engine: &EmbeddedSqlEngine) { - let _ = std::panic::catch_unwind(|| { - let _guard = engine.conn.lock().unwrap(); - panic!("poison"); - }); - } - - #[test] - fn open_in_memory_failpoint_surfaces_error() { - failpoints::clear(); - failpoints::set(failpoints::Point::Open); - let err = EmbeddedSqlEngine::new().unwrap_err(); - assert_eq!(err.code(), "ERR_INVALID_QUERY"); - } - - #[test] - fn exec_query_roundtrip() { - let engine = EmbeddedSqlEngine::new().unwrap(); - engine.exec(CREATE_TABLE_SQL, "[]").unwrap(); - let outcome = engine - .exec("INSERT INTO test_items (name) VALUES (?)", "[\"rad\"]") - .unwrap(); - assert_eq!(outcome.changes, 1); - let rows = engine - .query_rows("SELECT name FROM test_items WHERE id = ?", "[1]") - .unwrap(); - let name = rows - .first() - .and_then(|row| row.get("name")) - .and_then(|value| value.as_str()) - .expect("missing name"); - assert_eq!(name, "rad"); - } - - #[test] - fn rollback_discards_changes() { - let engine = EmbeddedSqlEngine::new().unwrap(); - engine.exec(CREATE_TABLE_SQL, "[]").unwrap(); - engine.begin_tx().unwrap(); - engine - .exec("INSERT INTO test_items (name) VALUES (?)", "[\"rad\"]") - .unwrap(); - engine.rollback_tx().unwrap(); - let rows = engine - .query_rows("SELECT name FROM test_items", "[]") - .unwrap(); - assert!(rows.is_empty()); - } - - #[test] - fn exec_runs_multi_statement_batches_without_params() { - let engine = EmbeddedSqlEngine::new().unwrap(); - - let outcome = engine - .exec( - "CREATE TABLE demo (id INTEGER PRIMARY KEY, name TEXT NOT NULL);\ -\nCREATE UNIQUE INDEX demo_name_idx ON demo(name);", - "[]", - ) - .unwrap(); - assert_eq!(outcome.changes, 0); - - let insert = engine - .exec("INSERT INTO demo (name) VALUES ('alpha')", "[]") - .unwrap(); - assert_eq!(insert.changes, 1); - - let rows = engine - .query_rows( - "SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'demo_name_idx'", - "[]", - ) - .unwrap(); - assert_eq!(rows.len(), 1); - } - - #[test] - fn exec_empty_bind_batch_surfaces_invalid_query() { - let engine = EmbeddedSqlEngine::new().unwrap(); - let err = engine.exec("CREATE TABLE broken (", "[]").unwrap_err(); - assert_eq!(err.code(), "ERR_INVALID_QUERY"); - } - - #[test] - fn export_bytes_non_empty() { - let engine = EmbeddedSqlEngine::new().unwrap(); - engine.exec(CREATE_TABLE_SQL, "[]").unwrap(); - engine - .exec("INSERT INTO test_items (name) VALUES (?)", "[\"rad\"]") - .unwrap(); - let bytes = engine.export_bytes().unwrap(); - assert!(!bytes.is_empty()); - } - - #[test] - fn query_raw_commit_and_trait_executor_paths() { - let engine = EmbeddedSqlEngine::new().unwrap(); - engine.exec(CREATE_TABLE_SQL, "[]").unwrap(); - engine.begin_tx().unwrap(); - engine - .exec("INSERT INTO test_items (name) VALUES (?)", "[\"rad\"]") - .unwrap(); - engine.commit_tx().unwrap(); - let rows = engine - .query_raw("SELECT name FROM test_items ORDER BY id ASC", "[]") - .unwrap(); - assert!(rows.contains("rad")); - - let executor: &dyn SqlExecutor = &engine; - executor.begin().unwrap(); - let _ = executor - .exec("INSERT INTO test_items (name) VALUES (?)", "[\"trait\"]") - .unwrap(); - executor.rollback().unwrap(); - let rows_after = executor - .query_raw("SELECT name FROM test_items ORDER BY id ASC", "[]") - .unwrap(); - assert!(rows_after.contains("rad")); - assert!(!rows_after.contains("trait")); - } - - #[test] - fn invalid_sql_paths_surface_invalid_query() { - let engine = EmbeddedSqlEngine::new().unwrap(); - let err_exec = engine - .exec("INSERT INTO missing (name) VALUES (?)", "[\"rad\"]") - .unwrap_err(); - assert_eq!(err_exec.code(), "ERR_INVALID_QUERY"); - - let err_rows = engine.query_rows("SELECT name FROM missing", "[]"); - assert_eq!(err_rows.unwrap_err().code(), "ERR_INVALID_QUERY"); - - let err_raw = engine.query_raw("SELECT name FROM missing", "[]"); - assert_eq!(err_raw.unwrap_err().code(), "ERR_INVALID_QUERY"); - - let err_commit = engine.commit_tx().unwrap_err(); - assert_eq!(err_commit.code(), "ERR_INVALID_QUERY"); - - let err_rollback = engine.rollback_tx().unwrap_err(); - assert_eq!(err_rollback.code(), "ERR_INVALID_QUERY"); - - let executor: &dyn SqlExecutor = &engine; - let err_trait_commit = executor.commit().unwrap_err(); - assert_eq!(err_trait_commit.code(), "ERR_INVALID_QUERY"); - } - - #[test] - fn invalid_params_surface_errors() { - let engine = EmbeddedSqlEngine::new().unwrap(); - let err_exec = engine.exec(CREATE_TABLE_SQL, "{}").unwrap_err(); - assert_eq!(err_exec.code(), "ERR_SERIALIZATION"); - - let err_rows = engine.query_rows("SELECT 1", "{}").unwrap_err(); - assert_eq!(err_rows.code(), "ERR_SERIALIZATION"); - - let err_raw = engine.query_raw("SELECT 1", "{}").unwrap_err(); - assert_eq!(err_raw.code(), "ERR_SERIALIZATION"); - } - - #[test] - fn query_rows_surfaces_prepare_and_bind_errors() { - let engine = EmbeddedSqlEngine::new().unwrap(); - let err_prepare = engine - .query_rows("SELEC name FROM test_items", "[]") - .unwrap_err(); - assert_eq!(err_prepare.code(), "ERR_INVALID_QUERY"); - - engine.exec(CREATE_TABLE_SQL, "[]").unwrap(); - let err_bind = engine - .query_rows("SELECT name FROM test_items WHERE id = ?", "[]") - .unwrap_err(); - assert_eq!(err_bind.code(), "ERR_INVALID_QUERY"); - } - - #[test] - fn query_rows_collect_error_is_reported() { - let engine = EmbeddedSqlEngine::new().unwrap(); - engine.exec(CREATE_TABLE_SQL, "[]").unwrap(); - engine - .exec("INSERT INTO test_items (name) VALUES (?)", "[\"rad\"]") - .unwrap(); - failpoints::clear(); - failpoints::set(failpoints::Point::RowToJson); - let err = engine - .query_rows("SELECT name FROM test_items", "[]") - .unwrap_err(); - assert_eq!(err.code(), "ERR_INVALID_QUERY"); - } - - #[test] - fn query_raw_serialization_error_is_reported() { - let engine = EmbeddedSqlEngine::new().unwrap(); - engine.exec(CREATE_TABLE_SQL, "[]").unwrap(); - engine - .exec("INSERT INTO test_items (name) VALUES (?)", "[\"rad\"]") - .unwrap(); - failpoints::clear(); - failpoints::set(failpoints::Point::EncodeRows); - let err = engine - .query_raw("SELECT name FROM test_items", "[]") - .unwrap_err(); - assert_eq!(err.code(), "ERR_SERIALIZATION"); - } - - #[test] - fn begin_tx_failpoint_surfaces_error() { - let engine = EmbeddedSqlEngine::new().unwrap(); - failpoints::clear(); - failpoints::set(failpoints::Point::BeginExecute); - let err = engine.begin_tx().unwrap_err(); - assert_eq!(err.code(), "ERR_INVALID_QUERY"); - } - - #[test] - fn rollback_release_failpoint_surfaces_error() { - let engine = EmbeddedSqlEngine::new().unwrap(); - engine.exec(CREATE_TABLE_SQL, "[]").unwrap(); - engine.begin_tx().unwrap(); - failpoints::clear(); - failpoints::set(failpoints::Point::ReleaseExecute); - let err = engine.rollback_tx().unwrap_err(); - assert_eq!(err.code(), "ERR_INVALID_QUERY"); - } - - #[test] - fn export_bytes_failpoint_surfaces_error() { - let engine = EmbeddedSqlEngine::new().unwrap(); - engine.exec(CREATE_TABLE_SQL, "[]").unwrap(); - failpoints::clear(); - failpoints::set(failpoints::Point::ExportSerialize); - let err = engine.export_bytes().unwrap_err(); - assert_eq!(err.code(), "ERR_INVALID_QUERY"); - } - - #[test] - fn lock_errors_surface_internal() { - let engine = EmbeddedSqlEngine::new().unwrap(); - poison_engine(&engine); - let err_exec = engine.exec(CREATE_TABLE_SQL, "[]"); - assert_eq!(err_exec.unwrap_err().code(), "ERR_INTERNAL"); - let err_rows = engine.query_rows("SELECT 1", "[]"); - assert_eq!(err_rows.unwrap_err().code(), "ERR_INTERNAL"); - let err_raw = engine.query_raw("SELECT 1", "[]"); - assert_eq!(err_raw.unwrap_err().code(), "ERR_INTERNAL"); - let err_begin = engine.begin_tx(); - assert_eq!(err_begin.unwrap_err().code(), "ERR_INTERNAL"); - let err_commit = engine.commit_tx(); - assert_eq!(err_commit.unwrap_err().code(), "ERR_INTERNAL"); - let err_rollback = engine.rollback_tx(); - assert_eq!(err_rollback.unwrap_err().code(), "ERR_INTERNAL"); - let err_export = engine.export_bytes(); - assert_eq!(err_export.unwrap_err().code(), "ERR_INTERNAL"); - } - - #[test] - fn coverage_branch_probe_hits_both_paths() { - assert_eq!(coverage_branch_probe(true), "sql"); - assert_eq!(coverage_branch_probe(false), "sql"); - } -} diff --git a/crates/sql_wasm_core/src/lib.rs b/crates/sql_wasm_core/src/lib.rs @@ -1,33 +1,23 @@ -#[cfg(target_arch = "wasm32")] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] use radroots_sql_core::error::SqlError; -#[cfg(target_arch = "wasm32")] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] use radroots_sql_core::utils; -#[cfg(target_arch = "wasm32")] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] use serde::de::DeserializeOwned; -#[cfg(all(feature = "embedded", target_arch = "wasm32"))] -use js_sys::Uint8Array; -#[cfg(all(feature = "embedded", target_arch = "wasm32"))] -use std::sync::OnceLock; - -#[cfg(target_arch = "wasm32")] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] use wasm_bindgen::JsValue; -#[cfg(target_arch = "wasm32")] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] use wasm_bindgen::prelude::*; -#[cfg(feature = "embedded")] -mod embedded; -#[cfg(feature = "embedded")] -pub use embedded::EmbeddedSqlEngine; - -#[cfg(target_arch = "wasm32")] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] pub fn parse_json<T: DeserializeOwned>(s: &str) -> Result<T, SqlError> { utils::parse_json(s) } -#[cfg(target_arch = "wasm32")] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] pub fn err_js(err: SqlError) -> JsValue { let value = err.to_json(); match serde_wasm_bindgen::to_value(&value) { @@ -36,112 +26,36 @@ pub fn err_js(err: SqlError) -> JsValue { } } -#[cfg(all(feature = "embedded", target_arch = "wasm32"))] -pub fn embedded_engine() -> Result<&'static EmbeddedSqlEngine, SqlError> { - static ENGINE: OnceLock<EmbeddedSqlEngine> = OnceLock::new(); - if let Some(engine) = ENGINE.get() { - return Ok(engine); - } - let engine = EmbeddedSqlEngine::new()?; - let _ = ENGINE.set(engine); - ENGINE.get().ok_or(SqlError::Internal) -} - -#[cfg(all(feature = "embedded", target_arch = "wasm32"))] -#[wasm_bindgen(js_name = exec_sql)] -pub fn exec_sql(sql: &str, params_json: &str) -> JsValue { - let outcome = match embedded_engine().and_then(|engine| engine.exec(sql, params_json)) { - Ok(outcome) => outcome, - Err(err) => return err_js(err), - }; - let payload = serde_json::json!({ - "changes": outcome.changes, - "last_insert_id": outcome.last_insert_id, - "lastInsertRowid": outcome.last_insert_id, - }); - match serde_wasm_bindgen::to_value(&payload) { - Ok(value) => value, - Err(err) => err_js(SqlError::SerializationError(err.to_string())), - } -} - -#[cfg(all(feature = "embedded", target_arch = "wasm32"))] -#[wasm_bindgen(js_name = query_sql)] -pub fn query_sql(sql: &str, params_json: &str) -> JsValue { - let rows = match embedded_engine().and_then(|engine| engine.query_rows(sql, params_json)) { - Ok(rows) => rows, - Err(err) => return err_js(err), - }; - match serde_wasm_bindgen::to_value(&rows) { - Ok(value) => value, - Err(err) => err_js(SqlError::SerializationError(err.to_string())), - } -} - -#[cfg(all(feature = "embedded", target_arch = "wasm32"))] -pub fn export_bytes() -> JsValue { - let bytes = match embedded_engine().and_then(|engine| engine.export_bytes()) { - Ok(bytes) => bytes, - Err(err) => return err_js(err), - }; - let array = Uint8Array::from(bytes.as_slice()); - JsValue::from(array) -} - -#[cfg(all(feature = "embedded", target_arch = "wasm32"))] -#[wasm_bindgen(js_name = begin_tx)] -pub fn begin_tx() { - if let Ok(engine) = embedded_engine() { - let _ = engine.begin_tx(); - } -} - -#[cfg(all(feature = "embedded", target_arch = "wasm32"))] -#[wasm_bindgen(js_name = commit_tx)] -pub fn commit_tx() { - if let Ok(engine) = embedded_engine() { - let _ = engine.commit_tx(); - } -} - -#[cfg(all(feature = "embedded", target_arch = "wasm32"))] -#[wasm_bindgen(js_name = rollback_tx)] -pub fn rollback_tx() { - if let Ok(engine) = embedded_engine() { - let _ = engine.rollback_tx(); - } -} - -#[cfg(all(feature = "bridge", not(feature = "embedded"), target_arch = "wasm32"))] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] #[wasm_bindgen(js_name = exec_sql)] pub fn exec_sql(sql: &str, params_json: &str) -> JsValue { radroots_sql_wasm_bridge::exec(sql, params_json) } -#[cfg(all(feature = "bridge", not(feature = "embedded"), target_arch = "wasm32"))] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] #[wasm_bindgen(js_name = query_sql)] pub fn query_sql(sql: &str, params_json: &str) -> JsValue { radroots_sql_wasm_bridge::query(sql, params_json) } -#[cfg(all(feature = "bridge", not(feature = "embedded"), target_arch = "wasm32"))] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] pub fn export_bytes() -> JsValue { radroots_sql_wasm_bridge::export_bytes() } -#[cfg(all(feature = "bridge", not(feature = "embedded"), target_arch = "wasm32"))] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] #[wasm_bindgen(js_name = begin_tx)] pub fn begin_tx() { radroots_sql_wasm_bridge::begin_tx() } -#[cfg(all(feature = "bridge", not(feature = "embedded"), target_arch = "wasm32"))] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] #[wasm_bindgen(js_name = commit_tx)] pub fn commit_tx() { radroots_sql_wasm_bridge::commit_tx() } -#[cfg(all(feature = "bridge", not(feature = "embedded"), target_arch = "wasm32"))] +#[cfg(all(feature = "bridge", target_arch = "wasm32"))] #[wasm_bindgen(js_name = rollback_tx)] pub fn rollback_tx() { radroots_sql_wasm_bridge::rollback_tx() diff --git a/policy/coverage/profiles.toml b/policy/coverage/profiles.toml @@ -20,5 +20,5 @@ test_threads = 1 [profiles.crates."radroots_sql_wasm_core"] no_default_features = true -features = ["embedded"] +features = ["bridge"] test_threads = 1