From d056c7cc10df76d5a475932e8627cf45acb0aa44 Mon Sep 17 00:00:00 2001 From: Emily Toop Date: Wed, 4 Jul 2018 13:52:07 +0100 Subject: [PATCH 1/4] Fold in @ncalexan's changes to make Conn manage its own mutability. ref: https://github.com/ncalexan/mentat/tree/stores/src --- src/conn.rs | 39 ++++---- tests/query.rs | 74 ++++++++++++-- tests/vocabulary.rs | 2 +- transaction/src/entity_builder.rs | 154 ++++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+), 28 deletions(-) diff --git a/src/conn.rs b/src/conn.rs index 1462f73f5..cc21bf853 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -134,7 +134,6 @@ impl Conn { /// Prepare the provided SQLite handle for use as a Mentat store. Creates tables but /// _does not_ write the bootstrap schema. This constructor should only be used by /// consumers that expect to populate raw transaction data themselves. - pub(crate) fn empty(sqlite: &mut rusqlite::Connection) -> Result { let (tx, db) = db::create_empty_current_version(sqlite)?; tx.commit()?; @@ -276,7 +275,7 @@ impl Conn { } /// Take a SQLite transaction. - fn begin_transaction_with_behavior<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection, behavior: TransactionBehavior) -> Result> { + fn begin_transaction_with_behavior<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection, behavior: TransactionBehavior) -> Result> { let tx = sqlite.transaction_with_behavior(behavior)?; let (current_generation, current_partition_map, current_schema, cache_cow) = { @@ -305,12 +304,12 @@ impl Conn { // Helper to avoid passing connections around. // Make both args mutable so that we can't have parallel access. - pub fn begin_read<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result> { + pub fn begin_read<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection) -> Result> { self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Deferred) .map(|ip| InProgressRead { in_progress: ip }) } - pub fn begin_uncached_read<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result> { + pub fn begin_uncached_read<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection) -> Result> { self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Deferred) .map(|mut ip| { ip.use_caching(false); @@ -322,20 +321,20 @@ impl Conn { /// connections from taking immediate or exclusive transactions. This is appropriate for our /// writes and `InProgress`: it means we are ready to write whenever we want to, and nobody else /// can start a transaction that's not `DEFERRED`, but we don't need exclusivity yet. - pub fn begin_transaction<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result> { + pub fn begin_transaction<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection) -> Result> { self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Immediate) } /// Transact entities against the Mentat store, using the given connection and the current /// metadata. - pub fn transact(&mut self, + pub fn transact(&self, sqlite: &mut rusqlite::Connection, - transaction: B) -> Result where B: Borrow { + transaction: T) -> Result where T: AsRef { // Parse outside the SQL transaction. This is a tradeoff: we are limiting the scope of the // transaction, and indeed we don't even create a SQL transaction if the provided input is // invalid, but it means SQLite errors won't be found until the parse is complete, and if // there's a race for the database (don't do that!) we are less likely to win it. - let entities = edn::parse::entities(transaction.borrow())?; + let entities = edn::parse::entities(transaction.as_ref())?; let mut in_progress = self.begin_transaction(sqlite)?; let report = in_progress.transact_entities(entities)?; @@ -349,7 +348,7 @@ impl Conn { /// `cache_action` determines if the attribute should be added or removed from the cache. /// CacheAction::Add is idempotent - each attribute is only added once. /// CacheAction::Remove throws an error if the attribute does not currently exist in the cache. - pub fn cache(&mut self, + pub fn cache(&self, sqlite: &mut rusqlite::Connection, schema: &Schema, attribute: &Keyword, @@ -381,11 +380,11 @@ impl Conn { } } - pub fn register_observer(&mut self, key: String, observer: Arc) { + pub fn register_observer(&self, key: String, observer: Arc) { self.tx_observer_service.lock().unwrap().register(key, observer); } - pub fn unregister_observer(&mut self, key: &String) { + pub fn unregister_observer(&self, key: &String) { self.tx_observer_service.lock().unwrap().deregister(key); } } @@ -428,7 +427,7 @@ mod tests { #[test] fn test_transact_does_not_collide_existing_entids() { let mut sqlite = db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); // Let's find out the next ID that'll be allocated. We're going to try to collide with it // a bit later. @@ -454,7 +453,7 @@ mod tests { #[test] fn test_transact_does_not_collide_new_entids() { let mut sqlite = db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); // Let's find out the next ID that'll be allocated. We're going to try to collide with it. let next = conn.metadata.lock().expect("metadata").partition_map[":db.part/user"].next_entid(); @@ -488,7 +487,7 @@ mod tests { #[test] fn test_compound_transact() { let mut sqlite = db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); let tempid_offset = get_next_entid(&conn); @@ -529,7 +528,7 @@ mod tests { #[test] fn test_simple_prepared_query() { let mut c = db::new_connection("").expect("Couldn't open conn."); - let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + let conn = Conn::connect(&mut c).expect("Couldn't open DB."); conn.transact(&mut c, r#"[ [:db/add "s" :db/ident :foo/boolean] [:db/add "s" :db/valueType :db.type/boolean] @@ -566,7 +565,7 @@ mod tests { #[test] fn test_compound_rollback() { let mut sqlite = db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); let tempid_offset = get_next_entid(&conn); @@ -616,7 +615,7 @@ mod tests { #[test] fn test_transact_errors() { let mut sqlite = db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); // Good: empty transaction. let report = conn.transact(&mut sqlite, "[]").unwrap(); @@ -661,7 +660,7 @@ mod tests { #[test] fn test_add_to_cache_failure_no_attribute() { let mut sqlite = db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); let _report = conn.transact(&mut sqlite, r#"[ { :db/ident :foo/bar :db/valueType :db.type/long }, @@ -682,7 +681,7 @@ mod tests { fn test_lookup_attribute_with_caching() { let mut sqlite = db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); let _report = conn.transact(&mut sqlite, r#"[ { :db/ident :foo/bar :db/valueType :db.type/long }, @@ -741,7 +740,7 @@ mod tests { #[test] fn test_cache_usage() { let mut sqlite = db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); let db_ident = (*conn.current_schema()).get_entid(&kw!(:db/ident)).expect("db_ident").0; let db_type = (*conn.current_schema()).get_entid(&kw!(:db/valueType)).expect("db_ident").0; diff --git a/tests/query.rs b/tests/query.rs index 819835add..5a4de2056 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -254,7 +254,7 @@ fn test_instants_and_uuids() { let start = Utc::now() + FixedOffset::west(60 * 60); let mut c = new_connection("").expect("Couldn't open conn."); - let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + let conn = Conn::connect(&mut c).expect("Couldn't open DB."); conn.transact(&mut c, r#"[ [:db/add "s" :db/ident :foo/uuid] [:db/add "s" :db/valueType :db.type/uuid] @@ -291,7 +291,7 @@ fn test_instants_and_uuids() { #[test] fn test_tx() { let mut c = new_connection("").expect("Couldn't open conn."); - let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + let conn = Conn::connect(&mut c).expect("Couldn't open DB."); conn.transact(&mut c, r#"[ [:db/add "s" :db/ident :foo/uuid] [:db/add "s" :db/valueType :db.type/uuid] @@ -324,7 +324,7 @@ fn test_tx() { #[test] fn test_tx_as_input() { let mut c = new_connection("").expect("Couldn't open conn."); - let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + let conn = Conn::connect(&mut c).expect("Couldn't open DB."); conn.transact(&mut c, r#"[ [:db/add "s" :db/ident :foo/uuid] [:db/add "s" :db/valueType :db.type/uuid] @@ -361,7 +361,7 @@ fn test_tx_as_input() { #[test] fn test_fulltext() { let mut c = new_connection("").expect("Couldn't open conn."); - let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + let conn = Conn::connect(&mut c).expect("Couldn't open DB."); conn.transact(&mut c, r#"[ [:db/add "a" :db/ident :foo/term] @@ -467,7 +467,7 @@ fn test_fulltext() { #[test] fn test_instant_range_query() { let mut c = new_connection("").expect("Couldn't open conn."); - let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + let conn = Conn::connect(&mut c).expect("Couldn't open DB."); conn.transact(&mut c, r#"[ [:db/add "a" :db/ident :foo/date] @@ -503,7 +503,7 @@ fn test_instant_range_query() { #[test] fn test_lookup() { let mut c = new_connection("").expect("Couldn't open conn."); - let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + let conn = Conn::connect(&mut c).expect("Couldn't open DB."); conn.transact(&mut c, r#"[ [:db/add "a" :db/ident :foo/date] @@ -656,7 +656,7 @@ fn test_aggregates_type_handling() { #[test] fn test_type_reqs() { let mut c = new_connection("").expect("Couldn't open conn."); - let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + let conn = Conn::connect(&mut c).expect("Couldn't open DB."); conn.transact(&mut c, r#"[ {:db/ident :test/boolean :db/valueType :db.type/boolean :db/cardinality :db.cardinality/one} @@ -1526,3 +1526,63 @@ fn test_encrypted() { // so the specific test we use doesn't matter that much. run_tx_data_test(Store::open_with_key("", "secret").expect("opened")); } + +#[test] +fn test_conn_cross_thread() { + let file = "file:memdb?mode=memory&cache=shared"; + + let mut sqlite = mentat_db::db::new_connection(file).expect("Couldn't open in-memory db"); + let conn = Conn::connect(&mut sqlite).expect("to connect"); + + conn.transact(&mut sqlite, r#"[ + [:db/add "a" :db/ident :foo/term] + [:db/add "a" :db/valueType :db.type/string] + [:db/add "a" :db/fulltext false] + [:db/add "a" :db/cardinality :db.cardinality/many] + ]"#).unwrap(); + + let _tx1 = conn.transact(&mut sqlite, r#"[ + [:db/add "e" :foo/term "1"] + ]"#).expect("tx1 to apply"); + + let _tx2 = conn.transact(&mut sqlite, r#"[ + [:db/add "e" :foo/term "2"] + ]"#).expect("tx2 to apply"); + + use std::sync::Arc; + use std::sync::mpsc::channel; + + let conn = Arc::new(conn); + + let (tx1, rx1) = channel(); + let (txs, rxs) = channel(); + let (tx2, rx2) = channel(); + + std::thread::spawn(move || { + let shared_conn: Arc = rx1.recv().expect("rx1"); + let mut sqlite1 = mentat_db::db::new_connection(file).expect("Couldn't open in-memory db"); + + shared_conn.transact(&mut sqlite1, r#"[ + [:db/add "a" :db/ident :foo/bar] + [:db/add "a" :db/valueType :db.type/long] + [:db/add "a" :db/cardinality :db.cardinality/many] + ]"#).unwrap(); + + txs.send(()).expect("to sync"); + }); + + tx1.send(conn.clone()).expect("tx1"); + + rxs.recv().expect("to sync"); + + std::thread::spawn(move || { + let shared_conn: Arc = rx2.recv().expect("rx1"); + let mut sqlite2 = mentat_db::db::new_connection(file).expect("Couldn't open in-memory db"); + + assert_eq!(None, shared_conn.current_schema().get_entid(&Keyword::namespaced("foo", "bar"))); + + shared_conn.q_once(&mut sqlite2, "[:find ?e :where [?e _ _]]", None).expect("to prepare"); + }); + + tx2.send(conn.clone()).expect("tx2"); +} diff --git a/tests/vocabulary.rs b/tests/vocabulary.rs index 979a4a24c..f56421b9d 100644 --- a/tests/vocabulary.rs +++ b/tests/vocabulary.rs @@ -186,7 +186,7 @@ fn test_add_vocab() { let foo_v1_b = vocabulary::Definition::new(kw!(:org.mozilla/foo), 1, bar_and_baz.clone()); let mut sqlite = mentat_db::db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); let foo_version_query = r#"[:find [?version ?aa] :where diff --git a/transaction/src/entity_builder.rs b/transaction/src/entity_builder.rs index af4ac1ac0..dcfaf61ad 100644 --- a/transaction/src/entity_builder.rs +++ b/transaction/src/entity_builder.rs @@ -276,3 +276,157 @@ impl<'a, 'c> EntityBuilder> { self.finish().0.commit() } } + +#[cfg(test)] +mod testing { + extern crate mentat_db; + + use ::{ + Conn, + Entid, + HasSchema, + KnownEntid, + MentatError, + Queryable, + TxReport, + TypedValue, + }; + + use super::*; + + // In reality we expect the store to hand these out safely. + fn fake_known_entid(e: Entid) -> KnownEntid { + KnownEntid(e) + } + + #[test] + fn test_entity_builder_bogus_entids() { + let mut builder = TermBuilder::new(); + let e = builder.named_tempid("x"); + let a1 = fake_known_entid(37); // :db/doc + let a2 = fake_known_entid(999); + let v = TypedValue::typed_string("Some attribute"); + let ve = fake_known_entid(12345); + + builder.add(e.clone(), a1, v).expect("add succeeded"); + builder.add(e.clone(), a2, e.clone()).expect("add succeeded, even though it's meaningless"); + builder.add(e.clone(), a2, ve).expect("add succeeded, even though it's meaningless"); + let (terms, tempids) = builder.build().expect("build succeeded"); + + assert_eq!(tempids.len(), 1); + assert_eq!(terms.len(), 3); // TODO: check the contents? + + // Now try to add them to a real store. + let mut sqlite = mentat_db::db::new_connection("").unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); + let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); + + // This should fail: unrecognized entid. + match in_progress.transact_entities(terms).expect_err("expected transact to fail") { + MentatError::DbError(e) => { + assert_eq!(e.kind(), mentat_db::DbErrorKind::UnrecognizedEntid(999)); + }, + _ => panic!("Should have rejected the entid."), + } + } + + #[test] + fn test_in_progress_builder() { + let mut sqlite = mentat_db::db::new_connection("").unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); + + // Give ourselves a schema to work with! + conn.transact(&mut sqlite, r#"[ + [:db/add "o" :db/ident :foo/one] + [:db/add "o" :db/valueType :db.type/long] + [:db/add "o" :db/cardinality :db.cardinality/one] + [:db/add "m" :db/ident :foo/many] + [:db/add "m" :db/valueType :db.type/string] + [:db/add "m" :db/cardinality :db.cardinality/many] + [:db/add "r" :db/ident :foo/ref] + [:db/add "r" :db/valueType :db.type/ref] + [:db/add "r" :db/cardinality :db.cardinality/one] + ]"#).unwrap(); + + let in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); + + // We can use this or not! + let a_many = in_progress.get_entid(&kw!(:foo/many)).expect(":foo/many"); + + let mut builder = in_progress.builder(); + let e_x = builder.named_tempid("x"); + let v_many_1 = TypedValue::typed_string("Some text"); + let v_many_2 = TypedValue::typed_string("Other text"); + builder.add(e_x.clone(), kw!(:foo/many), v_many_1).expect("add succeeded"); + builder.add(e_x.clone(), a_many, v_many_2).expect("add succeeded"); + builder.commit().expect("commit succeeded"); + } + + #[test] + fn test_entity_builder() { + let mut sqlite = mentat_db::db::new_connection("").unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); + + let foo_one = kw!(:foo/one); + let foo_many = kw!(:foo/many); + let foo_ref = kw!(:foo/ref); + let report: TxReport; + + // Give ourselves a schema to work with! + // Scoped borrow of conn. + { + conn.transact(&mut sqlite, r#"[ + [:db/add "o" :db/ident :foo/one] + [:db/add "o" :db/valueType :db.type/long] + [:db/add "o" :db/cardinality :db.cardinality/one] + [:db/add "m" :db/ident :foo/many] + [:db/add "m" :db/valueType :db.type/string] + [:db/add "m" :db/cardinality :db.cardinality/many] + [:db/add "r" :db/ident :foo/ref] + [:db/add "r" :db/valueType :db.type/ref] + [:db/add "r" :db/cardinality :db.cardinality/one] + ]"#).unwrap(); + + let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); + + // Scoped borrow of in_progress. + { + let mut builder = TermBuilder::new(); + let e_x = builder.named_tempid("x"); + let e_y = builder.named_tempid("y"); + let a_ref = in_progress.get_entid(&foo_ref).expect(":foo/ref"); + let a_one = in_progress.get_entid(&foo_one).expect(":foo/one"); + let a_many = in_progress.get_entid(&foo_many).expect(":foo/many"); + let v_many_1 = TypedValue::typed_string("Some text"); + let v_many_2 = TypedValue::typed_string("Other text"); + let v_long: TypedValue = 123.into(); + + builder.add(e_x.clone(), a_many, v_many_1).expect("add succeeded"); + builder.add(e_x.clone(), a_many, v_many_2).expect("add succeeded"); + builder.add(e_y.clone(), a_ref, e_x.clone()).expect("add succeeded"); + builder.add(e_x.clone(), a_one, v_long).expect("add succeeded"); + + let (terms, tempids) = builder.build().expect("build succeeded"); + + assert_eq!(tempids.len(), 2); + assert_eq!(terms.len(), 4); + + report = in_progress.transact_entities(terms).expect("add succeeded"); + let x = report.tempids.get("x").expect("our tempid has an ID"); + let y = report.tempids.get("y").expect("our tempid has an ID"); + assert_eq!(in_progress.lookup_value_for_attribute(*y, &foo_ref).expect("lookup succeeded"), + Some(TypedValue::Ref(*x))); + assert_eq!(in_progress.lookup_value_for_attribute(*x, &foo_one).expect("lookup succeeded"), + Some(TypedValue::Long(123))); + } + + in_progress.commit().expect("commit succeeded"); + } + + // It's all still there after the commit. + let x = report.tempids.get("x").expect("our tempid has an ID"); + let y = report.tempids.get("y").expect("our tempid has an ID"); + assert_eq!(conn.lookup_value_for_attribute(&mut sqlite, *y, &foo_ref).expect("lookup succeeded"), + Some(TypedValue::Ref(*x))); + } +} From e1c2c9ee773dc309d11ce9cb7539b6888a993a30 Mon Sep 17 00:00:00 2001 From: Emily Toop Date: Wed, 4 Jul 2018 14:30:23 +0100 Subject: [PATCH 2/4] Add Stores to manage `Conn` and creation of `rusqlite::Connection`s. Enable ability to create named in memory stores and in memory stores with shared caches. Include ability to create encrypted connections. Update `Store` to take an `Arc` so references can be shared. Update FFI to use `Stores` instead of `Store`. Add `store_open_named_in_memory_store` to open a named in-memory store over FFI (useful for tests). --- db/src/db.rs | 2 +- db/src/lib.rs | 1 + ffi/src/lib.rs | 11 +- public-traits/errors.rs | 11 +- src/conn.rs | 4 - src/lib.rs | 5 + src/store.rs | 45 ++++- src/stores.rs | 432 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 494 insertions(+), 17 deletions(-) create mode 100644 src/stores.rs diff --git a/db/src/db.rs b/db/src/db.rs index 881f83e00..6a8dd31be 100644 --- a/db/src/db.rs +++ b/db/src/db.rs @@ -89,7 +89,7 @@ fn escape_string_for_pragma(s: &str) -> String { s.replace("'", "''") } -fn make_connection(uri: &Path, maybe_encryption_key: Option<&str>) -> rusqlite::Result { +pub fn make_connection(uri: &Path, maybe_encryption_key: Option<&str>) -> rusqlite::Result { let conn = match uri.to_string_lossy().len() { 0 => rusqlite::Connection::open_in_memory()?, _ => rusqlite::Connection::open(uri)?, diff --git a/db/src/lib.rs b/db/src/lib.rs index 9c9ac6fab..1cbee1f8e 100644 --- a/db/src/lib.rs +++ b/db/src/lib.rs @@ -82,6 +82,7 @@ pub use entids::{ pub use db::{ TypedSQLValue, new_connection, + make_connection, }; #[cfg(feature = "sqlcipher")] diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 5d2c532ee..562f1464f 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -107,6 +107,7 @@ pub use mentat::{ QueryResults, RelResult, Store, + Stores, Syncable, TypedValue, TxObserver, @@ -220,7 +221,14 @@ pub unsafe extern "C" fn store_open(uri: *const c_char, error: *mut ExternError) pub unsafe extern "C" fn store_open_encrypted(uri: *const c_char, key: *const c_char, error: *mut ExternError) -> *mut Store { let uri = c_char_to_string(uri); let key = c_char_to_string(key); - translate_result(Store::open_with_key(&uri, &key), error) + translate_result(Stores::open_with_key(&uri, &key), error) +} + +/// Variant of store_open that opens a named in-memory database. +#[no_mangle] +pub unsafe extern "C" fn store_open_named_in_memory_store(name: *const c_char, error: *mut ExternError) -> *mut Store { + let name = c_char_to_string(name); + translate_result(Stores::open_named_in_memory_store(name), error) } // TODO: open empty @@ -1556,7 +1564,6 @@ pub unsafe extern "C" fn typed_value_into_long(typed_value: *mut Binding) -> c_l pub unsafe extern "C" fn typed_value_into_entid(typed_value: *mut Binding) -> Entid { assert_not_null!(typed_value); let typed_value = Box::from_raw(typed_value); - println!("typed value as entid {:?}", typed_value); unwrap_conversion(typed_value.into_entid(), ValueType::Ref) } diff --git a/public-traits/errors.rs b/public-traits/errors.rs index 5f212ab60..1c22937f0 100644 --- a/public-traits/errors.rs +++ b/public-traits/errors.rs @@ -11,8 +11,8 @@ #![allow(dead_code)] use std; // To refer to std::result::Result. - use std::collections::BTreeSet; +use std::path::PathBuf; use rusqlite; @@ -87,6 +87,15 @@ pub enum MentatError { #[fail(display = "provided value of type {} doesn't match attribute value type {}", _0, _1)] ValueTypeMismatch(ValueType, ValueType), + #[fail(display = "Cannot open store {} at path {:?} as it does not match previous store location {:?}", _0, _1, _2)] + StorePathMismatch(String, PathBuf, PathBuf), + + #[fail(display = "The Store at {} does not exist or is not yet open.", _0)] + StoreNotFound(String), + + #[fail(display = "The Store at {:?} has active connections and cannot be closed.", _0)] + StoresLockPoisoned(String), + #[fail(display = "{}", _0)] IoError(#[cause] std::io::Error), diff --git a/src/conn.rs b/src/conn.rs index cc21bf853..15e814a79 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -10,10 +10,6 @@ #![allow(dead_code)] -use std::borrow::{ - Borrow, -}; - use std::collections::{ BTreeMap, }; diff --git a/src/lib.rs b/src/lib.rs index a0e0b0c8f..7d0c7a5de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,6 +176,7 @@ pub use mentat_transaction::query::{ pub mod conn; pub mod query_builder; pub mod store; +pub mod stores; pub mod vocabulary; pub use query_builder::{ @@ -199,6 +200,10 @@ pub use store::{ Store, }; +pub use stores::{ + Stores, +}; + #[cfg(test)] mod tests { use edn::symbols::Keyword; diff --git a/src/store.rs b/src/store.rs index 591b9fad3..773cf5d15 100644 --- a/src/store.rs +++ b/src/store.rs @@ -72,17 +72,25 @@ use mentat_transaction::query::{ /// A convenience wrapper around a single SQLite connection and a Conn. This is suitable /// for applications that don't require complex connection management. pub struct Store { - conn: Conn, + conn: Arc, sqlite: rusqlite::Connection, } impl Store { + /// Create a Store from a connection and Conn. + pub fn new(conn: Arc, connection: rusqlite::Connection) -> Result { + Ok(Store { + conn: conn, + sqlite: connection, + }) + } + /// Open a store at the supplied path, ensuring that it includes the bootstrap schema. pub fn open(path: &str) -> Result { let mut connection = ::new_connection(path)?; let conn = Conn::connect(&mut connection)?; Ok(Store { - conn: conn, + conn: Arc::new(conn), sqlite: connection, }) } @@ -104,14 +112,33 @@ impl Store { let mut connection = ::new_connection_with_key(path, encryption_key)?; let conn = Conn::connect(&mut connection)?; Ok(Store { - conn: conn, + conn: Arc::new(conn), + sqlite: connection, + }) + } + + /// Variant of `open_empty` that allows a key (for encryption/decryption) to + /// be supplied. Fails unless linked against sqlcipher (or something else + /// that supports the Sqlite Encryption Extension). + pub fn open_empty_with_key(path: &str, encryption_key: &str) -> Result { + if !path.is_empty() { + if Path::new(path).exists() { + bail!(MentatError::PathAlreadyExists(path.to_string())); + } + } + + let mut connection = ::new_connection_with_key(path, encryption_key)?; + let conn = Conn::empty(&mut connection)?; + Ok(Store { + conn: Arc::new(conn), sqlite: connection, }) } - /// Change the key for a database that was opened using `open_with_key` (using `PRAGMA - /// rekey`). Fails unless linked against sqlcipher (or something else that supports the Sqlite - /// Encryption Extension). + /// Change the key for a database that was opened using `open_with_key` or + /// `open_empty_with_key` (using `PRAGMA rekey`). Fails unless linked + /// against sqlcipher (or something else that supports the Sqlite Encryption + /// Extension). pub fn change_encryption_key(&mut self, new_encryption_key: &str) -> Result<()> { ::change_encryption_key(&self.sqlite, new_encryption_key)?; Ok(()) @@ -131,12 +158,12 @@ impl Store { } impl Store { - pub fn dismantle(self) -> (rusqlite::Connection, Conn) { + pub fn dismantle(self) -> (rusqlite::Connection, Arc) { (self.sqlite, self.conn) } - pub fn conn(&self) -> &Conn { - &self.conn + pub fn conn(&self) -> Arc { + self.conn.clone() } pub fn begin_read<'m>(&'m mut self) -> Result> { diff --git a/src/stores.rs b/src/stores.rs new file mode 100644 index 000000000..075583437 --- /dev/null +++ b/src/stores.rs @@ -0,0 +1,432 @@ +// Copyright 2016 Mozilla +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#![allow(dead_code)] + +use std::collections::{ + BTreeMap, +}; +use std::convert::{AsRef}; +use std::collections::btree_map::{ + Entry, +}; +use std::path::{ + Path, +}; +use std::sync::{ + Arc, + RwLock, +}; + +use rusqlite; + +use mentat_db::{ + make_connection, +}; + +use conn::{ + Conn, +}; + +use errors::*; + +use store::{ + Store, +}; + +/// A process is only permitted to have one open handle to each database. This manager +/// exists to enforce that constraint: don't open databases directly. +lazy_static! { + static ref MANAGER: RwLock = RwLock::new(Stores::new()); +} + +/// A struct to store a tuple of a path to a store +/// and the connection to that store. We stores these things +/// together to ensure that two stores at different paths cannot +/// be opened with the same name. +struct StoreConnection { + conn: Arc, + file_path: String, +} + +impl StoreConnection { + fn new(path: T, maybe_encryption_key: Option<&str>) -> Result<(StoreConnection, rusqlite::Connection)> where T: AsRef { + let path = path.as_ref().to_path_buf(); + let os_str = path.as_os_str(); + let file_path = if os_str.is_empty() { + "file::memory:?cache=shared" + } else { + os_str.to_str().unwrap() + }; + StoreConnection::new_connection(file_path, maybe_encryption_key) + } + + fn new_named_in_memory_connection(name: &str, maybe_encryption_key: Option<&str>) -> Result<(StoreConnection, rusqlite::Connection)> { + let file = format!("file::{}?mode=memory&cache=shared", name); + StoreConnection::new_connection(&file, maybe_encryption_key) + } + + fn new_connection(file: &str, maybe_encryption_key: Option<&str>) -> Result<(StoreConnection, rusqlite::Connection)> { + let mut sqlite = make_connection(file.as_ref(), maybe_encryption_key)?; + Ok((StoreConnection { + conn: Arc::new(Conn::connect(&mut sqlite)?), + file_path: file.to_string(), + }, sqlite)) + } + + fn store(& mut self) -> Result { + let sqlite = make_connection(&self.file_path.as_ref(), None)?; + Store::new(self.conn.clone(), sqlite) + } + + fn encrypted_store(& mut self, encryption_key: &str) -> Result { + let sqlite = make_connection(&self.file_path.as_ref(), Some(encryption_key))?; + Store::new(self.conn.clone(), sqlite) + } + + fn store_with_connection(& mut self, sqlite: rusqlite::Connection) -> Result { + Store::new(self.conn.clone(), sqlite) + } +} + +/// Stores keeps a reference to a Conn that has been opened for a store +/// along with the path to the store and a key that uniquely identifies +/// that store. The key is stored as a String so that multiple in memory stores +/// can be named and uniquely identified. +pub struct Stores { + connections: BTreeMap, +} + +impl Stores { + fn new() -> Stores { + Stores { + connections: Default::default(), + } + } + + pub fn singleton() -> &'static RwLock { + &*MANAGER + } + + fn is_store_open(name: &str) -> bool { + Stores::singleton().read().unwrap().is_open(&name) + } + + pub fn open_store(path: T) -> Result where T: AsRef { + let path_ref = path.as_ref(); + let name: String = path_ref.to_string_lossy().into(); + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.clone()))?.open(&name, path_ref) + } + + pub fn open_named_in_memory_store(name: &str) -> Result { + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.to_string()))?.open(name, "") + } + + #[cfg(feature = "sqlcipher")] + pub fn open_store_with_key(path: T, encryption_key: &str) -> Result where T: AsRef { + let path_ref = path.as_ref(); + let name: String = path_ref.to_string_lossy().into(); + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.clone()))?.open_with_key(&name, path_ref, encryption_key) + } + + pub fn get_store(path: T) -> Result> where T: AsRef { + let name: String = path.as_ref().to_string_lossy().into(); + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.clone()))?.get(&name) + } + + #[cfg(feature = "sqlcipher")] + pub fn get_store_with_key(path: T, encryption_key: &str) -> Result> where T: AsRef { + let name: String = path.as_ref().to_string_lossy().into(); + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.clone()))?.get_with_key(&name, encryption_key) + } + + pub fn get_named_in_memory_store(name: &str) -> Result> { + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.to_string()))?.get(name) + } + + pub fn connect_store< T>(path: T) -> Result where T: AsRef { + let name: String = path.as_ref().to_string_lossy().into(); + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.clone()))?.connect(&name) + } + + #[cfg(feature = "sqlcipher")] + pub fn connect_store_with_key< T>(path: T, encryption_key: &str) -> Result where T: AsRef { + let name: String = path.as_ref().to_string_lossy().into(); + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.clone()))?.connect_with_key(&name, encryption_key) + } + + pub fn connect_named_in_memory_store(name: &str) -> Result { + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.to_string()))?.connect(name) + } + + pub fn close_store(path: T) -> Result<()> where T: AsRef { + let name: String = path.as_ref().to_string_lossy().into(); + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.clone()))?.close(&name) + } + + pub fn close_named_in_memory_store(name: &str) -> Result<()> { + Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.to_string()))?.close(name) + } +} + +impl Stores { + // Returns true if there exists an entry for the provided name in the connections map. + // This does not guarentee that the weak reference we hold to the Conn is still valid. + fn is_open(&self, name: &str) -> bool { + self.connections.contains_key(name) + } + + // Open a store with an existing connection if available, or + // create a new connection if not. + pub fn open(&mut self, name: &str, path: T) -> Result where T: AsRef { + match self.connections.entry(name.to_string()) { + Entry::Occupied(mut entry) => { + let connection = entry.get_mut(); + connection.store() + }, + Entry::Vacant(entry) =>{ + let path = path.as_ref().to_path_buf(); + + let (mut store_connection, connection) = if !name.is_empty() && path.as_os_str().is_empty() { + StoreConnection::new_named_in_memory_connection(name, None)? + } else { + StoreConnection::new(path, None)? + }; + let store = store_connection.store_with_connection(connection); + entry.insert(store_connection); + store + }, + } + } + + // Open an encrypted store with an existing connection if available, or + // create a new connection if not. + #[cfg(feature = "sqlcipher")] + pub fn open_with_key(&mut self, name: &str, path: T, encryption_key: &str) -> Result where T: AsRef { + match self.connections.entry(name.to_string()) { + Entry::Occupied(mut entry) => { + let connection = entry.get_mut(); + connection.store() + }, + Entry::Vacant(entry) =>{ + let path = path.as_ref().to_path_buf(); + + let (mut store_connection, connection) = if !name.is_empty() && path.as_os_str().is_empty() { + StoreConnection::new_named_in_memory_connection(name, Some(encryption_key))? + } else { + StoreConnection::new(path, Some(encryption_key))? + }; + let store = store_connection.store_with_connection(connection); + entry.insert(store_connection); + store + }, + } + } + + // Returns a store with an existing connection to path, if available, or None if a + // store at the provided path has not yet been opened. + pub fn get(&mut self, name: &str) -> Result> { + self.connections.get_mut(name) + .map_or(Ok(None), |store_conn| store_conn.store() + .map(|s| Some(s))) + } + + // Returns an encrypted store with an existing connection to path, if available, or None if a + // store at the provided path has not yet been opened. + #[cfg(feature = "sqlcipher")] + pub fn get_with_key(&mut self, name: &str, encryption_key: &str) -> Result> { + self.connections.get_mut(name) + .map_or(Ok(None), |store_conn| store_conn.encrypted_store(encryption_key) + .map(|s| Some(s))) + } + + // Creates a new store on an existing connection with a new rusqlite connection. + // Equivalent to forking an existing store. + pub fn connect(&mut self, name: &str) -> Result { + self.connections.get_mut(name) + .ok_or(MentatError::StoreNotFound(name.to_string()).into()) + .and_then(|store_conn| store_conn.store()) + } + + // Creates a new store on an existing connection with a new encrypted rusqlite connection. + // Equivalent to forking an existing store. + #[cfg(feature = "sqlcipher")] + pub fn connect_with_key(&mut self, name: &str, encryption_key: &str) -> Result { + self.connections.get_mut(name) + .ok_or(MentatError::StoreNotFound(name.to_string()).into()) + .and_then(|store_conn| store_conn.encrypted_store(encryption_key)) + } + + // Drops the weak reference we have stored to an opened store there is no more than + // one Store with a reference to the Conn for the provided path. + pub fn close(&mut self, name: &str) -> Result<()> { + self.connections.remove(name); + return Ok(()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use conn::{ + Queryable, + }; + + #[test] + fn test_stores_open_new_store() { + let name = "test.db"; + let _store = Stores::open_store(name).expect("Expected a store to be opened"); + assert!(Stores::is_store_open(name)); + } + + #[test] + fn test_stores_open_new_named_in_memory_store() { + let name = "test_stores_open_new_named_in_memory_store"; + let _store = Stores::open_named_in_memory_store(name).expect("Expected a store to be opened"); + assert!(Stores::is_store_open(name)); + } + + #[test] + fn test_stores_open_existing_store() { + let name = "test_stores_open_existing_store"; + let store1 = Stores::open_named_in_memory_store(name).expect("Expected a store to be opened"); + assert!(Stores::is_store_open(name)); + let store2 = Stores::open_named_in_memory_store(name).expect("Expected a store to be opened"); + assert!(Arc::ptr_eq(&store1.conn(), &store2.conn())); + } + + #[test] + fn test_stores_get_open_store() { + let name = "test_stores_get_open_store"; + let store = Stores::open_named_in_memory_store(name).expect("Expected a store to be opened"); + assert!(Stores::is_store_open(name)); + let store_ref = Stores::get_named_in_memory_store(name).expect("Expected a store to be fetched").expect("store"); + assert!(Arc::ptr_eq(&store.conn(), &store_ref.conn())); + } + + #[test] + fn test_stores_get_closed_store() { + match Stores::get_named_in_memory_store("test_stores_get_closed_store").expect("Expected a store to be fetched") { + None => (), + Some(_) => panic!("Store is not open and so none should be returned"), + } + } + + #[test] + fn test_stores_connect_open_store() { + let name = "test_stores_connect_open_store"; + let store1 = Stores::open_named_in_memory_store(name).expect("Expected a store to be opened"); + assert!(Stores::is_store_open(name)); + { + // connect to an existing store + let store2 = Stores::connect_named_in_memory_store(name).expect("expected a new store"); + assert!(Arc::ptr_eq(&store1.conn(), &store2.conn())); + + // get the existing store + let store3 = Stores::get_named_in_memory_store(name).expect("Expected a store to be fetched").unwrap(); + assert!(Arc::ptr_eq(&store2.conn(), &store3.conn())); + } + + // connect to the store again + let store4 = Stores::connect_named_in_memory_store(name).expect("expected a new store"); + assert!(Arc::ptr_eq(&store1.conn(), &store4.conn())); + } + + #[test] + fn test_stores_connect_closed_store() { + let name = "test_stores_connect_closed_store"; + let err = Stores::connect_named_in_memory_store(name).err(); + match err.unwrap() { + MentatError::StoreNotFound(message) => { assert_eq!(name, message); }, + x => panic!("expected Store Not Found error, got {:?}", x), + } + } + + #[test] + fn test_stores_close_store_with_one_reference() { + let name = "test_stores_close_store_with_one_reference"; + let store = Stores::open_named_in_memory_store(name).expect("Expected a store to be opened"); + assert_eq!(3, Arc::strong_count(&store.conn())); + + assert!(Stores::close_named_in_memory_store(name).is_ok()); + + assert!(Stores::get_named_in_memory_store(name).expect("expected an empty result").is_none()) + } + + #[test] + fn test_stores_close_store_with_multiple_references() { + let name = "test_stores_close_store_with_multiple_references"; + + let store1 = Stores::open_named_in_memory_store(name).expect("Expected a store to be opened"); + assert!(Stores::is_store_open(name)); + + let store2 = Stores::connect_named_in_memory_store(name).expect("expected a connected store"); + assert!(Arc::ptr_eq(&store1.conn(), &store2.conn())); + + Stores::close_named_in_memory_store(name).expect("succeeded"); + assert!(Stores::is_store_open(name) == false); + } + + #[test] + fn test_stores_close_unopened_store() { + let name = "test_stores_close_unopened_store"; + + Stores::close_named_in_memory_store(name).expect("succeeded"); + assert!(Stores::is_store_open(name) == false); + } + + #[test] + fn test_stores_connect_perform_mutable_operations() { + let path = "test.db"; + let mut store1 = Stores::open_store(path).expect("Expected a store to be opened"); + { + let mut in_progress = store1.begin_transaction().expect("begun"); + in_progress.transact(r#"[ + { :db/ident :foo/bar + :db/cardinality :db.cardinality/one + :db/index true + :db/unique :db.unique/identity + :db/valueType :db.type/long }, + { :db/ident :foo/baz + :db/cardinality :db.cardinality/one + :db/valueType :db.type/boolean } + { :db/ident :foo/x + :db/cardinality :db.cardinality/many + :db/valueType :db.type/long }]"#).expect("transact"); + + in_progress.commit().expect("commit"); + } + + // Forking an open store leads to a ref count of 2 on the shared conn. + // We should be able to perform write operations on this connection. + let mut store2 = Stores::connect_store(path).expect("expected a new store"); + let mut in_progress = store2.begin_transaction().expect("begun"); + in_progress.transact(r#"[ + {:foo/bar 15, :foo/baz false, :foo/x [1, 2, 3]} + {:foo/bar 99, :foo/baz true} + {:foo/bar -2, :foo/baz true} + ]"#).expect("transact"); + in_progress.commit().expect("commit"); + + // We should be able to see the changes made on `store2` on `store1` + let result = store1.q_once(r#"[:find ?e . :where [?e :foo/baz false]]"#, None).expect("succeeded"); + assert!(result.into_scalar().expect("succeeded").is_some()); + } + + #[test] + #[cfg(feature = "sqlcipher")] + fn test_open_store_with_key() { + let secret_key = "key"; + let name = "../fixtures/v1encrypted.db"; + let _store = Stores::open_store_with_key(name, secret_key).expect("Expected a store to be opened"); + assert!(Stores::is_store_open(name)); + } +} From b361ea8119f7cec0a0ae8aef4190790c35a301fa Mon Sep 17 00:00:00 2001 From: Emily Toop Date: Wed, 4 Jul 2018 14:51:04 +0100 Subject: [PATCH 3/4] Update Android and iOS SDKs to reflect new named in memory functions. Utilize named in memory store in tests to ensure isolation when running. --- .../src/main/java/org/mozilla/mentat/JNA.java | 1 + .../main/java/org/mozilla/mentat/Mentat.java | 12 +- .../mozilla/mentat/FFIIntegrationTest.java | 80 ++++----- sdks/swift/Mentat/Mentat/Mentat.swift | 11 ++ sdks/swift/Mentat/Mentat/store.h | 1 + .../Mentat/MentatTests/MentatTests.swift | 161 +++++++++--------- 6 files changed, 147 insertions(+), 119 deletions(-) diff --git a/sdks/android/Mentat/library/src/main/java/org/mozilla/mentat/JNA.java b/sdks/android/Mentat/library/src/main/java/org/mozilla/mentat/JNA.java index 44ac12c2f..83a036087 100644 --- a/sdks/android/Mentat/library/src/main/java/org/mozilla/mentat/JNA.java +++ b/sdks/android/Mentat/library/src/main/java/org/mozilla/mentat/JNA.java @@ -43,6 +43,7 @@ class InProgressBuilder extends PointerType {} class EntityBuilder extends PointerType {} Store store_open(String dbPath, RustError.ByReference err); + Store store_open_named_in_memory_store(String name, RustError.ByReference err); void destroy(Pointer obj); void uuid_destroy(Pointer obj); diff --git a/sdks/android/Mentat/library/src/main/java/org/mozilla/mentat/Mentat.java b/sdks/android/Mentat/library/src/main/java/org/mozilla/mentat/Mentat.java index 90e7c4fc0..5f700a8e9 100644 --- a/sdks/android/Mentat/library/src/main/java/org/mozilla/mentat/Mentat.java +++ b/sdks/android/Mentat/library/src/main/java/org/mozilla/mentat/Mentat.java @@ -28,7 +28,7 @@ public class Mentat extends RustObject { private Mentat(JNA.Store rawPointer) { super(rawPointer); } /** - * Open a connection to an in-memory Mentat Store. + * Open a connection to an anonymous in-memory Store. */ public static Mentat open() { return open(""); @@ -51,6 +51,16 @@ public static Mentat open(String dbPath) { return new Mentat(store); } + /** + * Open a connection to a named in-memory Store. + * @param name The named to be given to the in memory store to open. + * @return An instance of Mentat connected to a named in memory store. + */ + public static Mentat namedInMemoryStore(String name) { + RustError.ByReference err = new RustError.ByReference(); + return new Mentat(JNA.INSTANCE.store_open_named_in_memory_store(name, err)); + } + /** * Add an attribute to the cache. The {@link CacheDirection} determines how that attribute can be * looked up. diff --git a/sdks/android/Mentat/library/src/test/java/org/mozilla/mentat/FFIIntegrationTest.java b/sdks/android/Mentat/library/src/test/java/org/mozilla/mentat/FFIIntegrationTest.java index f9f75408d..9a3dfa466 100644 --- a/sdks/android/Mentat/library/src/test/java/org/mozilla/mentat/FFIIntegrationTest.java +++ b/sdks/android/Mentat/library/src/test/java/org/mozilla/mentat/FFIIntegrationTest.java @@ -116,9 +116,9 @@ public TxReport transactSeattleData(Mentat mentat) { return mentat.transact(seattleData); } - public Mentat openAndInitializeCitiesStore() { + public Mentat openAndInitializeCitiesStore(String name) { if (this.mentat == null) { - this.mentat = Mentat.open(); + this.mentat = Mentat.namedInMemoryStore(name); this.transactCitiesSchema(mentat); this.transactSeattleData(mentat); } @@ -181,7 +181,7 @@ public DBSetupResult populateWithTypesSchema(Mentat mentat) { @Test public void transactingVocabularySucceeds() { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("transactingVocabularySucceeds"); TxReport schemaReport = this.transactCitiesSchema(mentat); assertNotNull(schemaReport); assertTrue(schemaReport.getTxId() > 0); @@ -189,7 +189,7 @@ public void transactingVocabularySucceeds() { @Test public void transactingEntitiesSucceeds() { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("transactingEntitiesSucceeds"); this.transactCitiesSchema(mentat); TxReport dataReport = this.transactSeattleData(mentat); assertNotNull(dataReport); @@ -200,7 +200,7 @@ public void transactingEntitiesSucceeds() { @Test public void runScalarSucceeds() throws InterruptedException { - Mentat mentat = openAndInitializeCitiesStore(); + Mentat mentat = openAndInitializeCitiesStore("runScalarSucceeds"); String query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"; final CountDownLatch expectation = new CountDownLatch(1); mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() { @@ -216,7 +216,7 @@ public void handleValue(TypedValue value) { @Test public void runCollSucceeds() throws InterruptedException { - Mentat mentat = openAndInitializeCitiesStore(); + Mentat mentat = openAndInitializeCitiesStore("runCollSucceeds"); String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"; final CountDownLatch expectation = new CountDownLatch(1); mentat.query(query).run(new CollResultHandler() { @@ -234,7 +234,7 @@ public void handleList(CollResult list) { @Test public void runCollResultIteratorSucceeds() throws InterruptedException { - Mentat mentat = openAndInitializeCitiesStore(); + Mentat mentat = openAndInitializeCitiesStore("runCollResultIteratorSucceeds"); String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"; final CountDownLatch expectation = new CountDownLatch(1); mentat.query(query).run(new CollResultHandler() { @@ -253,7 +253,7 @@ public void handleList(CollResult list) { @Test public void runTupleSucceeds() throws InterruptedException { - Mentat mentat = openAndInitializeCitiesStore(); + Mentat mentat = openAndInitializeCitiesStore("runTupleSucceeds"); String query = "[:find [?name ?cat]\n" + " :where\n" + " [?c :community/name ?name]\n" + @@ -276,7 +276,7 @@ public void handleRow(TupleResult row) { @Test public void runRelIteratorSucceeds() throws InterruptedException { - Mentat mentat = openAndInitializeCitiesStore(); + Mentat mentat = openAndInitializeCitiesStore("runRelIteratorSucceeds"); String query = "[:find ?name ?cat\n" + " :where\n" + " [?c :community/name ?name]\n" + @@ -313,7 +313,7 @@ public void handleRows(RelResult rows) { @Test public void runRelSucceeds() throws InterruptedException { - Mentat mentat = openAndInitializeCitiesStore(); + Mentat mentat = openAndInitializeCitiesStore("runRelSucceeds"); String query = "[:find ?name ?cat\n" + " :where\n" + " [?c :community/name ?name]\n" + @@ -349,7 +349,7 @@ public void handleRows(RelResult rows) { @Test public void bindingLongValueSucceeds() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("bindingLongValueSucceeds"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]"; @@ -367,7 +367,7 @@ public void handleValue(TypedValue value) { @Test public void bindingRefValueSucceeds() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("bindingRefValueSucceeds"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; long stringEntid = mentat.entIdForAttribute(":foo/string"); final Long bEntid = report.getEntidForTempId("b"); @@ -386,7 +386,7 @@ public void handleValue(TypedValue value) { @Test public void bindingRefKwValueSucceeds() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("bindingRefKwValueSucceeds"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; String refKeyword = ":foo/string"; final Long bEntid = report.getEntidForTempId("b"); @@ -405,7 +405,7 @@ public void handleValue(TypedValue value) { @Test public void bindingKwValueSucceeds() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("bindingKwValueSucceeds"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]"; @@ -422,8 +422,8 @@ public void handleValue(TypedValue value) { } @Test - public void bindingDateValueSucceeds() throws InterruptedException { - Mentat mentat = Mentat.open(); + public void bindingDateValueSucceeds() throws InterruptedException, ParseException { + Mentat mentat = Mentat.namedInMemoryStore("bindingDateValueSucceeds"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); @@ -445,7 +445,7 @@ public void handleRow(TupleResult row) { @Test public void bindingStringValueSucceeds() throws InterruptedException { - Mentat mentat = this.openAndInitializeCitiesStore(); + Mentat mentat = this.openAndInitializeCitiesStore("bindingStringValueSucceeds"); String query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"; final CountDownLatch expectation = new CountDownLatch(1); mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() { @@ -461,7 +461,7 @@ public void handleValue(TypedValue value) { @Test public void bindingUuidValueSucceeds() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("bindingUuidValueSucceeds"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]"; @@ -480,7 +480,7 @@ public void handleValue(TypedValue value) { @Test public void bindingBooleanValueSucceeds() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("bindingBooleanValueSucceeds"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]"; @@ -499,7 +499,7 @@ public void handleValue(TypedValue value) { @Test public void bindingDoubleValueSucceeds() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("bindingDoubleValueSucceeds"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]"; @@ -517,7 +517,7 @@ public void handleValue(TypedValue value) { @Test public void typedValueConvertsToLong() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToLong"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]"; @@ -536,7 +536,7 @@ public void handleValue(TypedValue value) { @Test public void typedValueConvertsToRef() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToRef"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?e . :where [?e :foo/long 25]]"; @@ -555,7 +555,7 @@ public void handleValue(TypedValue value) { @Test public void typedValueConvertsToKeyword() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToKeyword"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]"; @@ -574,7 +574,7 @@ public void handleValue(TypedValue value) { @Test public void typedValueConvertsToBoolean() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToBoolean"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]"; @@ -593,7 +593,7 @@ public void handleValue(TypedValue value) { @Test public void typedValueConvertsToDouble() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToDouble"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]"; @@ -612,7 +612,7 @@ public void handleValue(TypedValue value) { @Test public void typedValueConvertsToDate() throws InterruptedException, ParseException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToDate"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]"; @@ -638,7 +638,7 @@ public void handleValue(TypedValue value) { @Test public void typedValueConvertsToString() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToString"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]"; @@ -657,7 +657,7 @@ public void handleValue(TypedValue value) { @Test public void typedValueConvertsToUUID() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToUUID"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); String query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]"; @@ -676,8 +676,8 @@ public void handleValue(TypedValue value) { } @Test - public void valueForAttributeOfEntitySucceeds() { - Mentat mentat = Mentat.open(); + public void valueForAttributeOfEntitySucceeds() throws InterruptedException { + Mentat mentat = Mentat.namedInMemoryStore("valueForAttributeOfEntitySucceeds"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; final Long aEntid = report.getEntidForTempId("a"); TypedValue value = mentat.valueForAttributeOfEntity(":foo/long", aEntid); @@ -695,7 +695,7 @@ public void entidForAttributeSucceeds() { @Test public void testInProgressTransact() { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("testInProgressTransact"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; assertNotNull(report); @@ -703,7 +703,7 @@ public void testInProgressTransact() { @Test public void testInProgressRollback() { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("testInProgressRollback"); TxReport report = this.populateWithTypesSchema(mentat).dataReport; assertNotNull(report); long aEntid = report.getEntidForTempId("a"); @@ -721,7 +721,7 @@ public void testInProgressRollback() { @Test public void testInProgressEntityBuilder() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("testInProgressEntityBuilder"); DBSetupResult reports = this.populateWithTypesSchema(mentat); long bEntid = reports.dataReport.getEntidForTempId("b"); final long longEntid = reports.schemaReport.getEntidForTempId("l"); @@ -795,7 +795,7 @@ public void handleRow(TupleResult row) { @Test public void testEntityBuilderForEntid() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderForEntid"); DBSetupResult reports = this.populateWithTypesSchema(mentat); long bEntid = reports.dataReport.getEntidForTempId("b"); final long longEntid = reports.schemaReport.getEntidForTempId("l"); @@ -869,7 +869,7 @@ public void handleRow(TupleResult row) { @Test public void testEntityBuilderForTempid() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderForTempid"); DBSetupResult reports = this.populateWithTypesSchema(mentat); final long longEntid = reports.schemaReport.getEntidForTempId("l"); @@ -922,7 +922,7 @@ public void handleRow(TupleResult row) { @Test public void testInProgressBuilderTransact() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("testInProgressBuilderTransact"); DBSetupResult reports = this.populateWithTypesSchema(mentat); long aEntid = reports.dataReport.getEntidForTempId("a"); long bEntid = reports.dataReport.getEntidForTempId("b"); @@ -984,7 +984,7 @@ public void handleRow(TupleResult row) { @Test public void testEntityBuilderTransact() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderTransact"); DBSetupResult reports = this.populateWithTypesSchema(mentat); long aEntid = reports.dataReport.getEntidForTempId("a"); long bEntid = reports.dataReport.getEntidForTempId("b"); @@ -1047,7 +1047,7 @@ public void handleRow(TupleResult row) { @Test public void testEntityBuilderRetract() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderRetract"); DBSetupResult reports = this.populateWithTypesSchema(mentat); long bEntid = reports.dataReport.getEntidForTempId("b"); final long longEntid = reports.schemaReport.getEntidForTempId("l"); @@ -1111,7 +1111,7 @@ public void handleRow(TupleResult row) { @Test public void testInProgressBuilderRetract() throws InterruptedException { - Mentat mentat = Mentat.open(); + Mentat mentat = Mentat.namedInMemoryStore("testInProgressBuilderRetract"); DBSetupResult reports = this.populateWithTypesSchema(mentat); long bEntid = reports.dataReport.getEntidForTempId("b"); final long longEntid = reports.schemaReport.getEntidForTempId("l"); @@ -1180,7 +1180,7 @@ public void testCaching() throws InterruptedException { " [?neighborhood :neighborhood/district ?d]\n" + " [?d :district/name ?district]]"; - Mentat mentat = openAndInitializeCitiesStore(); + Mentat mentat = openAndInitializeCitiesStore("testCaching"); final CountDownLatch expectation1 = new CountDownLatch(1); final QueryTimer uncachedTimer = new QueryTimer(); diff --git a/sdks/swift/Mentat/Mentat/Mentat.swift b/sdks/swift/Mentat/Mentat/Mentat.swift index 712bcf4b0..5aad1f665 100644 --- a/sdks/swift/Mentat/Mentat/Mentat.swift +++ b/sdks/swift/Mentat/Mentat/Mentat.swift @@ -61,6 +61,17 @@ open class Mentat: RustObject { public class func open(storeURI: String = "") throws -> Mentat { return Mentat(raw: try RustError.unwrap({err in store_open(storeURI, err) })) } + + /** + Open a connection to a Store in a given location. + If the store does not already exist, one will be created. + + - Parameter storeURI: The URI as a String of the store to open. + If no store URI is provided, an in-memory store will be opened. + */ + public convenience init(namedInMemoryStore name: String) throws { + self.init(raw: try RustError.unwrap({err in store_open_named_in_memory_store(name, err) })) + } /** Add an attribute to the cache. The {@link CacheDirection} determines how that attribute can be diff --git a/sdks/swift/Mentat/Mentat/store.h b/sdks/swift/Mentat/Mentat/store.h index 729875f6e..7c48e3999 100644 --- a/sdks/swift/Mentat/Mentat/store.h +++ b/sdks/swift/Mentat/Mentat/store.h @@ -103,6 +103,7 @@ typedef NS_ENUM(NSInteger, ValueType) { // Store struct Store*_Nonnull store_open(const char*_Nonnull uri, struct RustError* _Nonnull error); +struct Store*_Nonnull store_open_named_in_memory_store(const char*_Nonnull name, struct RustError* _Nonnull error); // Destructors. void destroy(void* _Nullable obj); diff --git a/sdks/swift/Mentat/MentatTests/MentatTests.swift b/sdks/swift/Mentat/MentatTests/MentatTests.swift index 25a59378d..4c16f5f9c 100644 --- a/sdks/swift/Mentat/MentatTests/MentatTests.swift +++ b/sdks/swift/Mentat/MentatTests/MentatTests.swift @@ -32,6 +32,11 @@ class MentatTests: XCTestCase { func testOpenInMemoryStore() { XCTAssertNotNil(try Mentat.open().raw) } + + // test that a store can be opened in memory + func testOpenNamedInMemoryStore() { + XCTAssertNotNil(try Mentat(namedInMemoryStore: "testOpenInMemoryStore").raw) + } // test that a store can be opened in a specific location func testOpenStoreInLocation() { @@ -77,9 +82,9 @@ class MentatTests: XCTestCase { return report } - func openAndInitializeCitiesStore() -> Mentat { + func openAndInitializeCitiesStore() throws -> Mentat { guard let mentat = self.store else { - let mentat = try! Mentat.open() + let mentat = try Mentat(namedInMemoryStore: "openAndInitializeCitiesStore") let _ = try! self.transactCitiesSchema(mentat: mentat) let _ = try! self.transactSeattleData(mentat: mentat) self.store = mentat @@ -153,7 +158,7 @@ class MentatTests: XCTestCase { func test1TransactVocabulary() { do { - let mentat = try Mentat.open() + let mentat = try Mentat(namedInMemoryStore: "test1TransactVocabulary") let vocab = try readCitiesSchema() let report = try mentat.transact(transaction: vocab) XCTAssertNotNil(report) @@ -165,7 +170,7 @@ class MentatTests: XCTestCase { func test2TransactEntities() { do { - let mentat = try Mentat.open() + let mentat = try Mentat(namedInMemoryStore: "test2TransactEntities") let vocab = try readCitiesSchema() let _ = try mentat.transact(transaction: vocab) let data = try readSeattleData() @@ -179,8 +184,8 @@ class MentatTests: XCTestCase { } } - func testQueryScalar() { - let mentat = openAndInitializeCitiesStore() + func testQueryScalar() throws { + let mentat = try openAndInitializeCitiesStore() let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]" let expect = expectation(description: "Query is executed") XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?name", toString: "Wallingford").runScalar(callback: { scalarResult in @@ -197,8 +202,8 @@ class MentatTests: XCTestCase { } } - func testQueryColl() { - let mentat = openAndInitializeCitiesStore() + func testQueryColl() throws { + let mentat = try openAndInitializeCitiesStore() let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]" let expect = expectation(description: "Query is executed") XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in @@ -219,8 +224,8 @@ class MentatTests: XCTestCase { } } - func testQueryCollResultIterator() { - let mentat = openAndInitializeCitiesStore() + func testQueryCollResultIterator() throws { + let mentat = try openAndInitializeCitiesStore() let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]" let expect = expectation(description: "Query is executed") XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in @@ -240,8 +245,8 @@ class MentatTests: XCTestCase { } } - func testQueryTuple() { - let mentat = openAndInitializeCitiesStore() + func testQueryTuple() throws { + let mentat = try openAndInitializeCitiesStore() let query = """ [:find [?name ?cat] :where @@ -267,8 +272,8 @@ class MentatTests: XCTestCase { } } - func testQueryRel() { - let mentat = openAndInitializeCitiesStore() + func testQueryRel() throws { + let mentat = try openAndInitializeCitiesStore() let query = """ [:find ?name ?cat :where @@ -300,8 +305,8 @@ class MentatTests: XCTestCase { } } - func testQueryRelResultIterator() { - let mentat = openAndInitializeCitiesStore() + func testQueryRelResultIterator() throws { + let mentat = try openAndInitializeCitiesStore() let query = """ [:find ?name ?cat :where @@ -336,8 +341,8 @@ class MentatTests: XCTestCase { } } - func testBindLong() { - let mentat = try! Mentat.open() + func testBindLong() throws { + let mentat = try Mentat(namedInMemoryStore: "testBindLong") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a") let query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]" @@ -356,8 +361,8 @@ class MentatTests: XCTestCase { } } - func testBindRef() { - let mentat = try! Mentat.open() + func testBindRef() throws { + let mentat = try Mentat(namedInMemoryStore: "testBindRef") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let stringEntid = mentat.entidForAttribute(attribute: ":foo/string") let bEntid = report!.entid(forTempId: "b") @@ -377,8 +382,8 @@ class MentatTests: XCTestCase { } } - func testBindKwRef() { - let mentat = try! Mentat.open() + func testBindKwRef() throws { + let mentat = try Mentat(namedInMemoryStore: "testBindKwRef") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let bEntid = report!.entid(forTempId: "b") let query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]" @@ -397,8 +402,8 @@ class MentatTests: XCTestCase { } } - func testBindKw() { - let mentat = try! Mentat.open() + func testBindKw() throws { + let mentat = try Mentat(namedInMemoryStore: "testBindKw") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a") let query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]" @@ -417,8 +422,8 @@ class MentatTests: XCTestCase { } } - func testBindDate() { - let mentat = try! Mentat.open() + func testBindDate() throws { + let mentat = try Mentat(namedInMemoryStore: "testBindDate") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a") let query = "[:find [?e ?d] :in ?now :where [?e :foo/instant ?d] [(< ?d ?now)]]" @@ -443,8 +448,8 @@ class MentatTests: XCTestCase { } - func testBindString() { - let mentat = openAndInitializeCitiesStore() + func testBindString() throws { + let mentat = try openAndInitializeCitiesStore() let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]" let expect = expectation(description: "Query is executed") XCTAssertNoThrow(try mentat.query(query: query) @@ -463,8 +468,8 @@ class MentatTests: XCTestCase { } } - func testBindUuid() { - let mentat = try! Mentat.open() + func testBindUuid() throws { + let mentat = try Mentat(namedInMemoryStore: "testBindUuid") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a") let query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]" @@ -484,8 +489,8 @@ class MentatTests: XCTestCase { } } - func testBindBoolean() { - let mentat = try! Mentat.open() + func testBindBoolean() throws { + let mentat = try Mentat(namedInMemoryStore: "testBindBoolean") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a") let query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]" @@ -504,8 +509,8 @@ class MentatTests: XCTestCase { } } - func testBindDouble() { - let mentat = try! Mentat.open() + func testBindDouble() throws { + let mentat = try Mentat(namedInMemoryStore: "testBindDouble") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a") let query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]" @@ -524,8 +529,8 @@ class MentatTests: XCTestCase { } } - func testTypedValueAsLong() { - let mentat = try! Mentat.open() + func testTypedValueAsLong() throws { + let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsLong") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a")! let query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]" @@ -545,8 +550,8 @@ class MentatTests: XCTestCase { } } - func testTypedValueAsRef() { - let mentat = try! Mentat.open() + func testTypedValueAsRef() throws { + let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsRef") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a")! let query = "[:find ?e . :where [?e :foo/long 25]]" @@ -565,8 +570,8 @@ class MentatTests: XCTestCase { } } - func testTypedValueAsKw() { - let mentat = try! Mentat.open() + func testTypedValueAsKw() throws { + let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsKw") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a")! let query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]" @@ -586,8 +591,8 @@ class MentatTests: XCTestCase { } } - func testTypedValueAsBoolean() { - let mentat = try! Mentat.open() + func testTypedValueAsBoolean() throws { + let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsBoolean") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a")! let query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]" @@ -607,8 +612,8 @@ class MentatTests: XCTestCase { } } - func testTypedValueAsDouble() { - let mentat = try! Mentat.open() + func testTypedValueAsDouble() throws { + let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsDouble") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a")! let query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]" @@ -628,8 +633,8 @@ class MentatTests: XCTestCase { } } - func testTypedValueAsDate() { - let mentat = try! Mentat.open() + func testTypedValueAsDate() throws { + let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsDate") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a")! let query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]" @@ -654,8 +659,8 @@ class MentatTests: XCTestCase { } } - func testTypedValueAsString() { - let mentat = try! Mentat.open() + func testTypedValueAsString() throws { + let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsString") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a")! let query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]" @@ -675,8 +680,8 @@ class MentatTests: XCTestCase { } } - func testTypedValueAsUuid() { - let mentat = try! Mentat.open() + func testTypedValueAsUuid() throws { + let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsUuid") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a")! let query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]" @@ -697,8 +702,8 @@ class MentatTests: XCTestCase { } } - func testValueForAttributeOfEntity() { - let mentat = try! Mentat.open() + func testValueForAttributeOfEntity() throws { + let mentat = try Mentat(namedInMemoryStore: "testValueForAttributeOfEntity") let (_, report) = self.populateWithTypesSchema(mentat: mentat) let aEntid = report!.entid(forTempId: "a")! var value: TypedValue? = nil; @@ -707,15 +712,15 @@ class MentatTests: XCTestCase { assert(value?.asLong() == 25) } - func testEntidForAttribute() { - let mentat = try! Mentat.open() + func testEntidForAttribute() throws { + let mentat = try Mentat(namedInMemoryStore: "testEntidForAttribute") let _ = self.populateWithTypesSchema(mentat: mentat) let entid = mentat.entidForAttribute(attribute: ":foo/long") assert(entid == 65540) } - func testMultipleQueries() { - let mentat = try! Mentat.open() + func testMultipleQueries() throws { + let mentat = try Mentat(namedInMemoryStore: "testMultipleQueries") let _ = self.populateWithTypesSchema(mentat: mentat) let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]") @@ -739,8 +744,8 @@ class MentatTests: XCTestCase { } } - func testNestedQueries() { - let mentat = try! Mentat.open() + func testNestedQueries() throws { + let mentat = try Mentat(namedInMemoryStore: "testNestedQueries") let _ = self.populateWithTypesSchema(mentat: mentat) let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]") let q2 = mentat.query(query: "[:find ?x :where [_ _ ?x]]") @@ -761,14 +766,14 @@ class MentatTests: XCTestCase { } } - func test3InProgressTransact() { - let mentat = try! Mentat.open() + func test3InProgressTransact() throws { + let mentat = try Mentat(namedInMemoryStore: "test3InProgressTransact") let (_, report) = self.populateWithTypesSchema(mentat: mentat) XCTAssertNotNil(report) } - func testInProgressRollback() { - let mentat = try! Mentat.open() + func testInProgressRollback() throws { + let mentat = try Mentat(namedInMemoryStore: "testInProgressRollback") let (_, report) = self.populateWithTypesSchema(mentat: mentat) XCTAssertNotNil(report) let aEntid = report!.entid(forTempId: "a")! @@ -785,8 +790,8 @@ class MentatTests: XCTestCase { } - func testInProgressEntityBuilder() { - let mentat = try! Mentat.open() + func testInProgressEntityBuilder() throws { + let mentat = try Mentat(namedInMemoryStore: "testInProgressEntityBuilder") let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) let bEntid = dataReport!.entid(forTempId: "b")! let longEntid = schemaReport!.entid(forTempId: "l")! @@ -850,8 +855,8 @@ class MentatTests: XCTestCase { }) } - func testEntityBuilderForEntid() { - let mentat = try! Mentat.open() + func testEntityBuilderForEntid() throws { + let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderForEntid") let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) let bEntid = dataReport!.entid(forTempId: "b")! let longEntid = schemaReport!.entid(forTempId: "l")! @@ -915,8 +920,8 @@ class MentatTests: XCTestCase { }) } - func testEntityBuilderForTempid() { - let mentat = try! Mentat.open() + func testEntityBuilderForTempid() throws { + let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderForTempid") let (schemaReport, _) = self.populateWithTypesSchema(mentat: mentat) let longEntid = schemaReport!.entid(forTempId: "l")! // test that the values are as expected @@ -962,8 +967,8 @@ class MentatTests: XCTestCase { }) } - func testInProgressBuilderTransact() { - let mentat = try! Mentat.open() + func testInProgressBuilderTransact() throws { + let mentat = try Mentat(namedInMemoryStore: "testInProgressBuilderTransact") let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) let aEntid = dataReport!.entid(forTempId: "a")! let bEntid = dataReport!.entid(forTempId: "b")! @@ -1018,8 +1023,8 @@ class MentatTests: XCTestCase { XCTAssertEqual(22, longValue?.asLong()) } - func testEntityBuilderTransact() { - let mentat = try! Mentat.open() + func testEntityBuilderTransact() throws { + let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderTransact") let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) let aEntid = dataReport!.entid(forTempId: "a")! let bEntid = dataReport!.entid(forTempId: "b")! @@ -1074,8 +1079,8 @@ class MentatTests: XCTestCase { XCTAssertEqual(22, longValue?.asLong()) } - func testEntityBuilderRetract() { - let mentat = try! Mentat.open() + func testEntityBuilderRetract() throws { + let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderRetract") let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) let bEntid = dataReport!.entid(forTempId: "b")! let stringEntid = schemaReport!.entid(forTempId: "s")! @@ -1124,8 +1129,8 @@ class MentatTests: XCTestCase { }) } - func testInProgressEntityBuilderRetract() { - let mentat = try! Mentat.open() + func testInProgressEntityBuilderRetract() throws { + let mentat = try Mentat(namedInMemoryStore: "testInProgressEntityBuilderRetract") let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) let bEntid = dataReport!.entid(forTempId: "b")! let stringEntid = schemaReport!.entid(forTempId: "s")! @@ -1174,7 +1179,7 @@ class MentatTests: XCTestCase { }) } - func testCaching() { + func testCaching() throws { let query = """ [:find ?district :where [?neighborhood :neighborhood/name \"Beacon Hill\"] @@ -1182,7 +1187,7 @@ class MentatTests: XCTestCase { [?d :district/name ?district]] """ - let mentat = openAndInitializeCitiesStore() + let mentat = try openAndInitializeCitiesStore() struct QueryTimer { private var _start: UInt64 From 5ead1d3989b9ae92a3fd6c5bf6ca961edc5d3bea Mon Sep 17 00:00:00 2001 From: Emily Toop Date: Wed, 29 Aug 2018 17:34:58 +0100 Subject: [PATCH 4/4] Fixing tests broken by rebase --- src/stores.rs | 5 +- tests/entity_builder.rs | 6 +- tests/tolstoy.rs | 2 +- tools/cli/src/mentat_cli/input.rs | 2 +- transaction/src/entity_builder.rs | 154 ------------------------------ 5 files changed, 6 insertions(+), 163 deletions(-) diff --git a/src/stores.rs b/src/stores.rs index 075583437..152b56418 100644 --- a/src/stores.rs +++ b/src/stores.rs @@ -275,10 +275,7 @@ impl Stores { #[cfg(test)] mod tests { use super::*; - - use conn::{ - Queryable, - }; + use mentat_transaction::Queryable; #[test] fn test_stores_open_new_store() { diff --git a/tests/entity_builder.rs b/tests/entity_builder.rs index 62f5202bf..a8a543a24 100644 --- a/tests/entity_builder.rs +++ b/tests/entity_builder.rs @@ -69,7 +69,7 @@ fn test_entity_builder_bogus_entids() { // Now try to add them to a real store. let mut sqlite = mentat_db::db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); // This should fail: unrecognized entid. @@ -84,7 +84,7 @@ fn test_entity_builder_bogus_entids() { #[test] fn test_in_progress_builder() { let mut sqlite = mentat_db::db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); // Give ourselves a schema to work with! conn.transact(&mut sqlite, r#"[ @@ -116,7 +116,7 @@ fn test_in_progress_builder() { #[test] fn test_entity_builder() { let mut sqlite = mentat_db::db::new_connection("").unwrap(); - let mut conn = Conn::connect(&mut sqlite).unwrap(); + let conn = Conn::connect(&mut sqlite).unwrap(); let foo_one = kw!(:foo/one); let foo_many = kw!(:foo/many); diff --git a/tests/tolstoy.rs b/tests/tolstoy.rs index 34ef03610..40497349b 100644 --- a/tests/tolstoy.rs +++ b/tests/tolstoy.rs @@ -101,7 +101,7 @@ mod tests { #[test] fn test_reader() { let mut c = new_connection("").expect("Couldn't open conn."); - let mut conn = Conn::connect(&mut c).expect("Couldn't open DB."); + let conn = Conn::connect(&mut c).expect("Couldn't open DB."); { let db_tx = c.transaction().expect("db tx"); // Don't inspect the bootstrap transaction, but we'd like to see it's there. diff --git a/tools/cli/src/mentat_cli/input.rs b/tools/cli/src/mentat_cli/input.rs index 27139af8c..452a2bb1a 100644 --- a/tools/cli/src/mentat_cli/input.rs +++ b/tools/cli/src/mentat_cli/input.rs @@ -187,7 +187,7 @@ impl InputReader { fn read_line(&mut self, prompt: &str) -> UserAction { match self.interface { Some(ref mut r) => { - r.set_prompt(prompt); + let _ = r.set_prompt(prompt); r.read_line().ok().map_or(UserAction::Quit, |line| match line { ReadResult::Input(s) => UserAction::TextInput(s), diff --git a/transaction/src/entity_builder.rs b/transaction/src/entity_builder.rs index dcfaf61ad..af4ac1ac0 100644 --- a/transaction/src/entity_builder.rs +++ b/transaction/src/entity_builder.rs @@ -276,157 +276,3 @@ impl<'a, 'c> EntityBuilder> { self.finish().0.commit() } } - -#[cfg(test)] -mod testing { - extern crate mentat_db; - - use ::{ - Conn, - Entid, - HasSchema, - KnownEntid, - MentatError, - Queryable, - TxReport, - TypedValue, - }; - - use super::*; - - // In reality we expect the store to hand these out safely. - fn fake_known_entid(e: Entid) -> KnownEntid { - KnownEntid(e) - } - - #[test] - fn test_entity_builder_bogus_entids() { - let mut builder = TermBuilder::new(); - let e = builder.named_tempid("x"); - let a1 = fake_known_entid(37); // :db/doc - let a2 = fake_known_entid(999); - let v = TypedValue::typed_string("Some attribute"); - let ve = fake_known_entid(12345); - - builder.add(e.clone(), a1, v).expect("add succeeded"); - builder.add(e.clone(), a2, e.clone()).expect("add succeeded, even though it's meaningless"); - builder.add(e.clone(), a2, ve).expect("add succeeded, even though it's meaningless"); - let (terms, tempids) = builder.build().expect("build succeeded"); - - assert_eq!(tempids.len(), 1); - assert_eq!(terms.len(), 3); // TODO: check the contents? - - // Now try to add them to a real store. - let mut sqlite = mentat_db::db::new_connection("").unwrap(); - let conn = Conn::connect(&mut sqlite).unwrap(); - let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - - // This should fail: unrecognized entid. - match in_progress.transact_entities(terms).expect_err("expected transact to fail") { - MentatError::DbError(e) => { - assert_eq!(e.kind(), mentat_db::DbErrorKind::UnrecognizedEntid(999)); - }, - _ => panic!("Should have rejected the entid."), - } - } - - #[test] - fn test_in_progress_builder() { - let mut sqlite = mentat_db::db::new_connection("").unwrap(); - let conn = Conn::connect(&mut sqlite).unwrap(); - - // Give ourselves a schema to work with! - conn.transact(&mut sqlite, r#"[ - [:db/add "o" :db/ident :foo/one] - [:db/add "o" :db/valueType :db.type/long] - [:db/add "o" :db/cardinality :db.cardinality/one] - [:db/add "m" :db/ident :foo/many] - [:db/add "m" :db/valueType :db.type/string] - [:db/add "m" :db/cardinality :db.cardinality/many] - [:db/add "r" :db/ident :foo/ref] - [:db/add "r" :db/valueType :db.type/ref] - [:db/add "r" :db/cardinality :db.cardinality/one] - ]"#).unwrap(); - - let in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - - // We can use this or not! - let a_many = in_progress.get_entid(&kw!(:foo/many)).expect(":foo/many"); - - let mut builder = in_progress.builder(); - let e_x = builder.named_tempid("x"); - let v_many_1 = TypedValue::typed_string("Some text"); - let v_many_2 = TypedValue::typed_string("Other text"); - builder.add(e_x.clone(), kw!(:foo/many), v_many_1).expect("add succeeded"); - builder.add(e_x.clone(), a_many, v_many_2).expect("add succeeded"); - builder.commit().expect("commit succeeded"); - } - - #[test] - fn test_entity_builder() { - let mut sqlite = mentat_db::db::new_connection("").unwrap(); - let conn = Conn::connect(&mut sqlite).unwrap(); - - let foo_one = kw!(:foo/one); - let foo_many = kw!(:foo/many); - let foo_ref = kw!(:foo/ref); - let report: TxReport; - - // Give ourselves a schema to work with! - // Scoped borrow of conn. - { - conn.transact(&mut sqlite, r#"[ - [:db/add "o" :db/ident :foo/one] - [:db/add "o" :db/valueType :db.type/long] - [:db/add "o" :db/cardinality :db.cardinality/one] - [:db/add "m" :db/ident :foo/many] - [:db/add "m" :db/valueType :db.type/string] - [:db/add "m" :db/cardinality :db.cardinality/many] - [:db/add "r" :db/ident :foo/ref] - [:db/add "r" :db/valueType :db.type/ref] - [:db/add "r" :db/cardinality :db.cardinality/one] - ]"#).unwrap(); - - let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - - // Scoped borrow of in_progress. - { - let mut builder = TermBuilder::new(); - let e_x = builder.named_tempid("x"); - let e_y = builder.named_tempid("y"); - let a_ref = in_progress.get_entid(&foo_ref).expect(":foo/ref"); - let a_one = in_progress.get_entid(&foo_one).expect(":foo/one"); - let a_many = in_progress.get_entid(&foo_many).expect(":foo/many"); - let v_many_1 = TypedValue::typed_string("Some text"); - let v_many_2 = TypedValue::typed_string("Other text"); - let v_long: TypedValue = 123.into(); - - builder.add(e_x.clone(), a_many, v_many_1).expect("add succeeded"); - builder.add(e_x.clone(), a_many, v_many_2).expect("add succeeded"); - builder.add(e_y.clone(), a_ref, e_x.clone()).expect("add succeeded"); - builder.add(e_x.clone(), a_one, v_long).expect("add succeeded"); - - let (terms, tempids) = builder.build().expect("build succeeded"); - - assert_eq!(tempids.len(), 2); - assert_eq!(terms.len(), 4); - - report = in_progress.transact_entities(terms).expect("add succeeded"); - let x = report.tempids.get("x").expect("our tempid has an ID"); - let y = report.tempids.get("y").expect("our tempid has an ID"); - assert_eq!(in_progress.lookup_value_for_attribute(*y, &foo_ref).expect("lookup succeeded"), - Some(TypedValue::Ref(*x))); - assert_eq!(in_progress.lookup_value_for_attribute(*x, &foo_one).expect("lookup succeeded"), - Some(TypedValue::Long(123))); - } - - in_progress.commit().expect("commit succeeded"); - } - - // It's all still there after the commit. - let x = report.tempids.get("x").expect("our tempid has an ID"); - let y = report.tempids.get("y").expect("our tempid has an ID"); - assert_eq!(conn.lookup_value_for_attribute(&mut sqlite, *y, &foo_ref).expect("lookup succeeded"), - Some(TypedValue::Ref(*x))); - } -}