Skip to content

Commit

Permalink
Support rusqlite params in wasm
Browse files Browse the repository at this point in the history
  • Loading branch information
Hinton committed Jul 26, 2024
1 parent 37733d5 commit 431f3a5
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 14 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion crates/bitwarden-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repository.workspace = true
license-file.workspace = true

[features]
default = []
#default = ["sqlite"]

internal = [] # Internal testing methods
no-memory-hardening = [
Expand Down Expand Up @@ -60,6 +60,7 @@ serde = { version = ">=1.0, <2.0", features = ["derive"] }
serde_json = ">=1.0.96, <2.0"
serde_qs = ">=0.12.0, <0.14"
serde_repr = ">=0.1.12, <0.2"
serde-wasm-bindgen = "0.6.5"
sha1 = ">=0.10.5, <0.11"
sha2 = ">=0.10.6, <0.11"
thiserror = ">=1.0.40, <2.0"
Expand All @@ -71,6 +72,7 @@ wasm-bindgen = { version = "0.2.91", features = [
"serde-serialize",
], optional = true }
wasm-bindgen-futures = "0.4.41"
js-sys = "0.3.69"


[target.'cfg(all(not(target_os = "android"), not(target_arch="wasm32")))'.dependencies]
Expand Down
10 changes: 10 additions & 0 deletions crates/bitwarden-core/src/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ mod migrator;
mod sqlite;
#[cfg(feature = "sqlite")]
pub type Database = sqlite::SqliteDatabase;
use serde::Serialize;
#[cfg(feature = "sqlite")]
pub use sqlite::Params;

#[cfg(feature = "wasm")]
mod wasm;
#[cfg(all(not(feature = "sqlite"), feature = "wasm"))]
pub type Database = wasm::WasmDatabase;
#[cfg(all(not(feature = "sqlite"), feature = "wasm"))]
pub use wasm::{Params, ToSql};

use thiserror::Error;

Expand Down Expand Up @@ -57,4 +62,9 @@ pub trait DatabaseTrait {
async fn set_version(&self, version: usize) -> Result<(), DatabaseError>;

async fn execute_batch(&self, sql: &str) -> Result<(), DatabaseError>;

/// Convenience method to prepare and execute a single SQL statement.
///
/// On success, returns the number of rows that were changed or inserted or deleted.
async fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize, DatabaseError>;
}
7 changes: 7 additions & 0 deletions crates/bitwarden-core/src/database/sqlite.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use rusqlite::Connection;
pub use rusqlite::Params;

use super::{migrator::Migrator, DatabaseError, DatabaseTrait};

Expand Down Expand Up @@ -78,6 +79,12 @@ impl DatabaseTrait for SqliteDatabase {

Ok(())
}

async fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize, DatabaseError> {
self.conn.execute(sql, params)?;

Ok(0)
}
}

#[cfg(test)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
mod params;
pub use params::{Params, ToSql};

use serde::Serialize;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};

use super::{DatabaseError, DatabaseTrait};
Expand All @@ -18,7 +22,10 @@ extern "C" {
async fn set_version(this: &SqliteDatabase, version: u32);

#[wasm_bindgen(method)]
async fn execute(this: &SqliteDatabase, sql: &str);
async fn execute_batch(this: &SqliteDatabase, sql: &str);

#[wasm_bindgen(method)]
async fn execute(this: &SqliteDatabase, sql: &str, params: JsValue);
}

impl core::fmt::Debug for SqliteDatabase {
Expand All @@ -39,7 +46,7 @@ impl WasmDatabase {

pub async fn new() -> Result<Self, DatabaseError> {
let db: SqliteDatabase = SqliteDatabase::factory("test").await.into();
db.execute(
db.execute_batch(
"CREATE TABLE IF NOT EXISTS ciphers (
id TEXT PRIMARY KEY,
value TEXT NOT NULL
Expand Down Expand Up @@ -72,8 +79,14 @@ impl DatabaseTrait for WasmDatabase {
}

async fn execute_batch(&self, sql: &str) -> Result<(), DatabaseError> {
self.db.execute(sql).await;
self.db.execute_batch(sql).await;

Ok(())
}

async fn execute<P: Params>(&self, sql: &str, params: P) -> Result<usize, DatabaseError> {
self.db.execute(sql, params.to_sql()).await;

Ok(0)
}
}
71 changes: 71 additions & 0 deletions crates/bitwarden-core/src/database/wasm/params.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use uuid::Uuid;
use wasm_bindgen::JsValue;

// Borrowed from Rusqlite
pub trait ToSql {
fn to_sql(&self) -> JsValue;
}
impl ToSql for u8 {
fn to_sql(&self) -> JsValue {
JsValue::from_f64(*self as f64)
}
}
impl ToSql for String {
fn to_sql(&self) -> JsValue {
JsValue::from_str(self)
}
}
impl ToSql for Uuid {
fn to_sql(&self) -> JsValue {
JsValue::from_str(&self.to_string())
}
}

pub trait Params {
fn to_sql(&self) -> JsValue;
}
impl Params for [&(dyn ToSql + Send + Sync); 0] {
fn to_sql(&self) -> JsValue {
JsValue::NULL
}
}
impl Params for &[&dyn ToSql] {
fn to_sql(&self) -> JsValue {
let array = js_sys::Array::new();
for item in *self {
array.push(&item.to_sql());
}
array.into()
}
}
impl Params for &[(&str, &dyn ToSql)] {
fn to_sql(&self) -> JsValue {
let object = js_sys::Object::new();
for (key, value) in *self {
js_sys::Reflect::set(&object, &JsValue::from_str(key), &value.to_sql()).unwrap();
}
object.into()
}
}

#[macro_export]
macro_rules! params {
() => {
&[] as &[&dyn $crate::ToSql]
};
($($param:expr),+ $(,)?) => {
&[$(&$param as &dyn $crate::ToSql),+] as &[&dyn $crate::ToSql]
};
}

#[macro_export]
macro_rules! named_params {
() => {
&[] as &[(&str, &dyn $crate::ToSql)]
};
// Note: It's a lot more work to support this as part of the same macro as
// `params!`, unfortunately.
($($param_name:literal: $param_val:expr),+ $(,)?) => {
&[$(($param_name, &$param_val as &dyn $crate::ToSql)),+] as &[(&str, &dyn $crate::ToSql)]
};
}
2 changes: 1 addition & 1 deletion crates/bitwarden-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use error::Error;
pub mod mobile;
pub use error::{MissingFieldError, VaultLocked};
mod database;
pub use database::{Database, DatabaseError, DatabaseTrait};
pub use database::{Database, DatabaseError, DatabaseTrait, Params, ToSql};
#[cfg(feature = "internal")]
pub mod platform;
#[cfg(feature = "secrets")]
Expand Down
2 changes: 1 addition & 1 deletion crates/bitwarden-json/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl Client {

let client = &self.0;

let ciphers: Vec<Cipher> = (0..70000).map(|_| Cipher {
let ciphers: Vec<Cipher> = (0..10).map(|_| Cipher {
id: Some(Uuid::new_v4()),
organization_id: None,
folder_id: None,
Expand Down
16 changes: 9 additions & 7 deletions crates/bitwarden-vault/src/cipher/repository.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::sync::{Arc, Mutex};

use bitwarden_core::{require, Database, DatabaseError, DatabaseTrait, Error};
use bitwarden_core::{
named_params, params, require, Database, DatabaseError, DatabaseTrait, Error,
};
use idb::{DatabaseEvent, Factory, KeyPath, ObjectStoreParams, TransactionMode};
use serde::Serialize;
use serde_wasm_bindgen::Serializer;
Expand Down Expand Up @@ -65,6 +67,7 @@ impl CipherRepository {
.await?;
*/

/*
// Get a factory instance from global scope
let factory = Factory::new().unwrap();
Expand Down Expand Up @@ -111,6 +114,7 @@ impl CipherRepository {
// Commit the transaction
transaction.commit().unwrap().await.unwrap();
*/

//let tx = guard.conn.transaction()?;
//{
Expand All @@ -123,19 +127,17 @@ impl CipherRepository {
",
)?;*/

/*
for cipher in ciphers {
let id = require!(cipher.id);
let serialized = serde_json::to_string(&cipher)?;

guard
.execute_batch(&format!(
"INSERT INTO ciphers (id, value) VALUES ('{}', '{}')",
id, "abc"
))
.execute(
"INSERT INTO ciphers (id, value) VALUES (:id, :data)",
named_params! {":id": id, ":data": serialized},
)
.await?;
}
*/

//}
//tx.commit()?;
Expand Down
18 changes: 17 additions & 1 deletion languages/js/sqlite-test/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,24 @@ class SqliteDatabase {
console.log("Version", version);
}

async execute(sql) {
async execute_batch(sql) {
console.log(sql);
// localStorage.setItem("sql", sql);
await sqlite3.exec(this.db, sql);
}


async execute(sql, params) {
console.log(sql, params);
for await (const stmt of sqlite3.statements(this.db, sql)) {
let rc = sqlite3.bind_collection(stmt, params);

while ((rc = await sqlite3.step(stmt)) !== SQLite.SQLITE_DONE) {
console.log(rc);
}

}

// localStorage.setItem("sql", sql);
// await sqlite3.exec(this.db, sql);
}
Expand Down

0 comments on commit 431f3a5

Please sign in to comment.