diff --git a/src/builder/database_builder.rs b/src/builder/database_builder.rs index 51ba3b4..e870e0b 100644 --- a/src/builder/database_builder.rs +++ b/src/builder/database_builder.rs @@ -77,6 +77,41 @@ impl DatabaseBuilder { self } + /// Mutates the chosen object store. + /// + /// Note that this does not permit renaming the object store. For that, use [`Self::rename_object_store`]. + /// + /// Panics if no object store with the chosen name is found. + pub fn mutate_object_store( + mut self, + object_store_name: &str, + mutation: impl FnOnce(ObjectStoreBuilder) -> ObjectStoreBuilder, + ) -> Self { + let object_store_builder_pointer = self + .object_stores + .get_mut(object_store_name) + .or_else(|| { + self.object_stores_to_rename + .get_mut(object_store_name) + .map(|(_name, store)| store) + }) + .expect("cannot mutate an object store which does not exist"); + + let dummy = ObjectStoreBuilder::new(""); + + // swap out the real builder with a dummy, then mutate it, then swap it back in + // this is sound because we don't allow for the possibility of error in the mutation function + let object_store_builder = std::mem::replace(object_store_builder_pointer, dummy); + *object_store_builder_pointer = mutation(object_store_builder); + + debug_assert!( + !object_store_builder_pointer.name().is_empty(), + "dummy must have been replaced" + ); + + self + } + /// Builds the database. pub async fn build(mut self) -> Result { let factory = Factory::new()?; diff --git a/tests/builder.rs b/tests/builder.rs index 1209173..64a93cb 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -328,3 +328,60 @@ async fn test_database_builder_reopen() { transaction.abort().unwrap(); database.close(); } + +#[wasm_bindgen_test] +async fn test_mutate_object_store() { + const DB_NAME: &str = "test"; + const STORE_NAME: &str = "object_store"; + const IDX_NAME: &str = "id_idx"; + + let factory = Factory::new().unwrap(); + factory.delete(DB_NAME).unwrap().await.unwrap(); + + // here we simulate a migration workflow: version 1 creates an object store, and version 2 mutates it to add an index + let make_db_version_1 = || { + DatabaseBuilder::new(DB_NAME) + .version(1) + .add_object_store(ObjectStoreBuilder::new(STORE_NAME)) + }; + + let make_db_version_2 = || { + make_db_version_1() + .version(2) + .mutate_object_store(STORE_NAME, |object_store_builder| { + object_store_builder.add_index(IndexBuilder::new( + IDX_NAME.into(), + KeyPath::Single("id".into()), + )) + }) + }; + + // create a version 1 database and test it has no indices + let database = make_db_version_1().build().await.unwrap(); + let transaction = database + .transaction(&[STORE_NAME], TransactionMode::ReadOnly) + .unwrap(); + + let store = transaction.object_store(STORE_NAME).unwrap(); + let indices = store.index_names(); + assert!(indices.is_empty()); + + transaction.abort().unwrap(); + database.close(); + + // now migrate and test it has indices + let database = make_db_version_2().build().await.unwrap(); + let transaction = database + .transaction(&[STORE_NAME], TransactionMode::ReadOnly) + .unwrap(); + + let store = transaction.object_store(STORE_NAME).unwrap(); + let indices = store.index_names(); + assert_eq!(indices, vec![IDX_NAME]); + + let index = store.index(IDX_NAME); + assert!(index.is_ok()); + + transaction.abort().unwrap(); + database.close(); +}