Skip to content

Commit

Permalink
Compound primary keys and indicies (#8)
Browse files Browse the repository at this point in the history
* Implemented support for compound primary keys.

* Added support for compound indices.

* Added unit tests for compound primary keys and indexes.

* Simplify key_path handling by removing the Option wrapper.

* Treat all indexes as compound to simplify the code.
  • Loading branch information
anlumo authored Apr 6, 2022
1 parent c130002 commit f2905cb
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 10 deletions.
27 changes: 24 additions & 3 deletions src/index.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use js_sys::Array;
use wasm_bindgen::JsValue;
use web_sys::{IdbIndexParameters, IdbObjectStore};

use crate::{Error, Result};

/// An index builder.
pub struct Index {
pub(crate) name: String,
pub(crate) key_path: String,
pub(crate) key_path: Vec<String>,
pub(crate) unique: Option<bool>,
pub(crate) multi_entry: Option<bool>,
}
Expand All @@ -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<S: ToString>(name: &str, key_path: impl IntoIterator<Item = S>) -> Self {
Self {
name: name.to_owned(),
key_path: key_path.into_iter().map(|s| s.to_string()).collect(),
unique: None,
multi_entry: None,
}
Expand Down Expand Up @@ -48,7 +60,16 @@ impl Index {
}

object_store
.create_index_with_str_and_optional_parameters(&self.name, &self.key_path, &params)
.create_index_with_str_sequence_and_optional_parameters(
&self.name,
&self
.key_path
.into_iter()
.map(|s| JsValue::from_str(&s))
.collect::<Array>()
.into(),
&params,
)
.map_err(Error::IndexCreationFailed)?;
}

Expand Down
37 changes: 31 additions & 6 deletions src/object_store.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -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<String>,
pub(crate) key_path: Vec<String>,
pub(crate) auto_increment: Option<bool>,
pub(crate) indexes: Vec<Index>,
}
Expand All @@ -18,15 +19,24 @@ 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(),
}
}

/// 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<S: ToString>(
mut self,
key_path_array: impl IntoIterator<Item = S>,
) -> Self {
self.key_path = key_path_array.into_iter().map(|s| s.to_string()).collect();
self
}

Expand Down Expand Up @@ -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::<Array>()
.into(),
));
}
}

idb.create_object_store_with_optional_parameters(&self.name, &params)
Expand Down
138 changes: 137 additions & 1 deletion tests/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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());
Expand All @@ -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());
Expand All @@ -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());
Expand Down Expand Up @@ -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<Option<Invoice>> {
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<Invoice> = 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<Vec<Invoice>> {
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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down

0 comments on commit f2905cb

Please sign in to comment.