diff --git a/src/index.rs b/src/index.rs index 35f65b6..c3ffe00 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,3 +1,5 @@ +use js_sys::Array; +use wasm_bindgen::JsValue; use web_sys::{IdbIndexParameters, IdbObjectStore}; use crate::{Error, Result}; @@ -5,7 +7,7 @@ use crate::{Error, Result}; /// An index builder. pub struct Index { pub(crate) name: String, - pub(crate) key_path: String, + pub(crate) key_path: Vec, pub(crate) unique: Option, pub(crate) multi_entry: Option, } @@ -15,7 +17,17 @@ impl Index { pub fn new(name: &str, key_path: &str) -> Self { Self { name: name.to_owned(), - key_path: key_path.to_owned(), + key_path: vec![key_path.to_owned()], + unique: None, + multi_entry: None, + } + } + + /// Creates a new index with given name and compound key path + pub fn new_compound(name: &str, key_path: impl IntoIterator) -> Self { + Self { + name: name.to_owned(), + key_path: key_path.into_iter().map(|s| s.to_string()).collect(), unique: None, multi_entry: None, } @@ -48,7 +60,16 @@ impl Index { } object_store - .create_index_with_str_and_optional_parameters(&self.name, &self.key_path, ¶ms) + .create_index_with_str_sequence_and_optional_parameters( + &self.name, + &self + .key_path + .into_iter() + .map(|s| JsValue::from_str(&s)) + .collect::() + .into(), + ¶ms, + ) .map_err(Error::IndexCreationFailed)?; } diff --git a/src/object_store.rs b/src/object_store.rs index 62f532a..b614ff5 100644 --- a/src/object_store.rs +++ b/src/object_store.rs @@ -1,5 +1,6 @@ -use std::collections::HashSet; +use std::{collections::HashSet, string::ToString}; +use js_sys::Array; use wasm_bindgen::prelude::*; use web_sys::{IdbDatabase, IdbObjectStoreParameters, IdbOpenDbRequest}; @@ -8,7 +9,7 @@ use crate::{Error, Index, Result}; /// An object store builder. pub struct ObjectStore { pub(crate) name: String, - pub(crate) key_path: Option, + pub(crate) key_path: Vec, pub(crate) auto_increment: Option, pub(crate) indexes: Vec, } @@ -18,7 +19,7 @@ impl ObjectStore { pub fn new(name: &str) -> Self { Self { name: name.to_owned(), - key_path: None, + key_path: Vec::new(), auto_increment: None, indexes: Vec::new(), } @@ -26,7 +27,16 @@ impl ObjectStore { /// Specify key path for the object store pub fn key_path(mut self, key_path: &str) -> Self { - self.key_path = Some(key_path.to_owned()); + self.key_path = vec![key_path.to_owned()]; + self + } + + /// Specify compound key path for the object store + pub fn key_path_array( + mut self, + key_path_array: impl IntoIterator, + ) -> Self { + self.key_path = key_path_array.into_iter().map(|s| s.to_string()).collect(); self } @@ -66,8 +76,23 @@ impl ObjectStore { params.auto_increment(auto_increment); } - if let Some(key_path) = self.key_path { - params.key_path(Some(&key_path.into())); + match self.key_path.len() { + 0 => { + params.key_path(None); + } + 1 => { + params.key_path(Some(&(&self.key_path[0]).into())); + } + _ => { + params.key_path(Some( + &self + .key_path + .into_iter() + .map(|key| JsValue::from_str(&key)) + .collect::() + .into(), + )); + } } idb.create_object_store_with_optional_parameters(&self.name, ¶ms) diff --git a/tests/web.rs b/tests/web.rs index 3be4878..28a0420 100644 --- a/tests/web.rs +++ b/tests/web.rs @@ -6,6 +6,7 @@ extern crate wasm_bindgen_test; use std::{assert, assert_eq, option::Option}; +use js_sys::Array; use rexie::{Direction, Index, KeyRange, ObjectStore, Result, Rexie, TransactionMode}; use serde::{Deserialize, Serialize}; use wasm_bindgen::JsValue; @@ -26,6 +27,22 @@ struct EmployeeRequest<'a> { email: &'a str, } +#[derive(Debug, Deserialize, PartialEq)] +struct Invoice { + id: usize, + year: u16, + agent: String, + customer: String, +} + +#[derive(Debug, Serialize)] +struct InvoiceRequest<'a> { + id: usize, + year: u16, + agent: &'a str, + customer: &'a str, +} + /// Creates a database async fn create_db() -> Rexie { assert!(Rexie::delete("test").await.is_ok()); @@ -39,6 +56,11 @@ async fn create_db() -> Rexie { .add_index(Index::new("email", "email").unique(true)), ) .add_object_store(ObjectStore::new("departments").auto_increment(true)) + .add_object_store( + ObjectStore::new("invoices") + .key_path_array(["id", "year"]) + .add_index(Index::new_compound("agent_customer", ["agent", "customer"])), + ) .build() .await; assert!(rexie.is_ok()); @@ -49,7 +71,10 @@ async fn create_db() -> Rexie { async fn basic_test_db(rexie: &Rexie) { assert_eq!(rexie.name(), "test"); assert_eq!(rexie.version(), 1.0); - assert_eq!(rexie.store_names(), vec!["departments", "employees"]); + assert_eq!( + rexie.store_names(), + vec!["departments", "employees", "invoices"] + ); let transaction = rexie.transaction(&["employees"], TransactionMode::ReadOnly); assert!(transaction.is_ok()); @@ -164,6 +189,84 @@ async fn clear_employees(rexie: &Rexie) -> Result<()> { employees.clear().await } +async fn add_invoice( + rexie: &Rexie, + id: usize, + year: u16, + agent: &str, + customer: &str, +) -> Result<()> { + let transaction = rexie.transaction(&["invoices"], TransactionMode::ReadWrite); + assert!(transaction.is_ok()); + let transaction = transaction.unwrap(); + + let invoices = transaction.store("invoices"); + assert!(invoices.is_ok()); + let invoices = invoices.unwrap(); + + let invoice = InvoiceRequest { + id, + year, + agent, + customer, + }; + let invoice = serde_wasm_bindgen::to_value(&invoice).unwrap(); + invoices.add(&invoice, None).await?; + + transaction.done().await?; + Ok(()) +} + +async fn get_invoice(rexie: &Rexie, id: usize, year: u16) -> Result> { + let transaction = rexie.transaction(&["invoices"], TransactionMode::ReadOnly); + assert!(transaction.is_ok()); + let transaction = transaction.unwrap(); + + let invoices = transaction.store("invoices"); + assert!(invoices.is_ok()); + let invoices = invoices.unwrap(); + + let invoice = invoices + .get(&Array::of2(&JsValue::from_f64(id as _), &JsValue::from_f64(year as _)).into()) + .await?; + let invoice: Option = serde_wasm_bindgen::from_value(invoice).unwrap(); + + Ok(invoice) +} + +async fn get_all_invoices_by_agent_and_customer( + rexie: &Rexie, + agent: &str, + customer: &str, +) -> Result> { + let transaction = rexie.transaction(&["invoices"], TransactionMode::ReadOnly); + assert!(transaction.is_ok()); + let transaction = transaction.unwrap(); + + let invoices = transaction.store("invoices"); + assert!(invoices.is_ok()); + let invoices = invoices.unwrap(); + + let agent_customer_index = invoices.index("agent_customer"); + assert!(agent_customer_index.is_ok()); + let agent_customer_index = agent_customer_index.unwrap(); + + let invoices = agent_customer_index + .get_all( + Some(&KeyRange::only(&Array::of2(&agent.into(), &customer.into())).unwrap()), + None, + None, + None, + ) + .await?; + let invoices = invoices + .into_iter() + .map(|(_, value)| serde_wasm_bindgen::from_value(value).unwrap()) + .collect(); + + Ok(invoices) +} + #[wasm_bindgen_test] async fn test_db_creation_pass() { let rexie = create_db().await; @@ -209,6 +312,22 @@ async fn test_db_add_pass() { let employee = employee.unwrap(); assert!(employee.is_none()); + let ok = add_invoice(&rexie, 1, 2022, "John Doe", "Umbrella Corp").await; + assert!(ok.is_ok()); + let ok = add_invoice(&rexie, 1, 2023, "Scooby Doo", "Umbrella Corp").await; + assert!(ok.is_ok()); + + let invoice = get_invoice(&rexie, 1, 2022).await; + assert!(invoice.is_ok()); + let invoice = invoice.unwrap(); + assert!(invoice.is_some()); + let invoice = invoice.unwrap(); + + assert_eq!(invoice.id, 1); + assert_eq!(invoice.year, 2022); + assert_eq!(invoice.agent, "John Doe"); + assert_eq!(invoice.customer, "Umbrella Corp"); + close_and_delete_db(rexie).await; } @@ -306,6 +425,23 @@ async fn test_get_all_pass() { // TODO: check employee details + let ok = add_invoice(&rexie, 1, 2022, "John Doe", "Umbrella Corp").await; + assert!(ok.is_ok()); + let ok = add_invoice(&rexie, 2, 2022, "Scooby Doo", "Umbrella Corp").await; + assert!(ok.is_ok()); + let ok = add_invoice(&rexie, 3, 2022, "John Doe", "Umbrella Corp").await; + assert!(ok.is_ok()); + + let invoices = + get_all_invoices_by_agent_and_customer(&rexie, "John Doe", "Umbrella Corp").await; + assert!(invoices.is_ok()); + let invoices = invoices.unwrap(); + assert_eq!(invoices.len(), 2); + for invoice in invoices { + assert!(invoice.id == 1 || invoice.id == 3); + assert_eq!(invoice.year, 2022); + } + close_and_delete_db(rexie).await; }