Compare commits
4 commits
master
...
fluffyemil
Author | SHA1 | Date | |
---|---|---|---|
|
5ead1d3989 | ||
|
b361ea8119 | ||
|
e1c2c9ee77 | ||
|
d056c7cc10 |
19 changed files with 730 additions and 169 deletions
|
@ -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<rusqlite::Connection> {
|
||||
pub fn make_connection(uri: &Path, maybe_encryption_key: Option<&str>) -> rusqlite::Result<rusqlite::Connection> {
|
||||
let conn = match uri.to_string_lossy().len() {
|
||||
0 => rusqlite::Connection::open_in_memory()?,
|
||||
_ => rusqlite::Connection::open(uri)?,
|
||||
|
|
|
@ -82,6 +82,7 @@ pub use entids::{
|
|||
pub use db::{
|
||||
TypedSQLValue,
|
||||
new_connection,
|
||||
make_connection,
|
||||
};
|
||||
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ public interface JNA extends Library {
|
|||
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);
|
||||
|
|
|
@ -28,7 +28,7 @@ public class Mentat extends RustObject<JNA.Store> {
|
|||
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 class Mentat extends RustObject<JNA.Store> {
|
|||
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.
|
||||
|
|
|
@ -116,9 +116,9 @@ public class FFIIntegrationTest {
|
|||
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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
}
|
||||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
}
|
||||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
|
||||
@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 class FFIIntegrationTest {
|
|||
" [?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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
43
src/conn.rs
43
src/conn.rs
|
@ -10,10 +10,6 @@
|
|||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::borrow::{
|
||||
Borrow,
|
||||
};
|
||||
|
||||
use std::collections::{
|
||||
BTreeMap,
|
||||
};
|
||||
|
@ -134,7 +130,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<Conn> {
|
||||
let (tx, db) = db::create_empty_current_version(sqlite)?;
|
||||
tx.commit()?;
|
||||
|
@ -276,7 +271,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<InProgress<'m, 'conn>> {
|
||||
fn begin_transaction_with_behavior<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection, behavior: TransactionBehavior) -> Result<InProgress<'m, 'conn>> {
|
||||
let tx = sqlite.transaction_with_behavior(behavior)?;
|
||||
let (current_generation, current_partition_map, current_schema, cache_cow) =
|
||||
{
|
||||
|
@ -305,12 +300,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<InProgressRead<'m, 'conn>> {
|
||||
pub fn begin_read<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgressRead<'m, 'conn>> {
|
||||
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<InProgressRead<'m, 'conn>> {
|
||||
pub fn begin_uncached_read<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgressRead<'m, 'conn>> {
|
||||
self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Deferred)
|
||||
.map(|mut ip| {
|
||||
ip.use_caching(false);
|
||||
|
@ -322,20 +317,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<InProgress<'m, 'conn>> {
|
||||
pub fn begin_transaction<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgress<'m, 'conn>> {
|
||||
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<B>(&mut self,
|
||||
pub fn transact<T>(&self,
|
||||
sqlite: &mut rusqlite::Connection,
|
||||
transaction: B) -> Result<TxReport> where B: Borrow<str> {
|
||||
transaction: T) -> Result<TxReport> where T: AsRef<str> {
|
||||
// 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 +344,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 +376,11 @@ impl Conn {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn register_observer(&mut self, key: String, observer: Arc<TxObserver>) {
|
||||
pub fn register_observer(&self, key: String, observer: Arc<TxObserver>) {
|
||||
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 +423,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 +449,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 +483,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 +524,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 +561,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 +611,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 +656,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 +677,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 +736,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;
|
||||
|
|
|
@ -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;
|
||||
|
|
45
src/store.rs
45
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<Conn>,
|
||||
sqlite: rusqlite::Connection,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
/// Create a Store from a connection and Conn.
|
||||
pub fn new(conn: Arc<Conn>, connection: rusqlite::Connection) -> Result<Store> {
|
||||
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<Store> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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).
|
||||
/// 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<Store> {
|
||||
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` 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<Conn>) {
|
||||
(self.sqlite, self.conn)
|
||||
}
|
||||
|
||||
pub fn conn(&self) -> &Conn {
|
||||
&self.conn
|
||||
pub fn conn(&self) -> Arc<Conn> {
|
||||
self.conn.clone()
|
||||
}
|
||||
|
||||
pub fn begin_read<'m>(&'m mut self) -> Result<InProgressRead<'m, 'm>> {
|
||||
|
|
429
src/stores.rs
Normal file
429
src/stores.rs
Normal file
|
@ -0,0 +1,429 @@
|
|||
// 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<Stores> = 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<Conn>,
|
||||
file_path: String,
|
||||
}
|
||||
|
||||
impl StoreConnection {
|
||||
fn new<T>(path: T, maybe_encryption_key: Option<&str>) -> Result<(StoreConnection, rusqlite::Connection)> where T: AsRef<Path> {
|
||||
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<Store> {
|
||||
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<Store> {
|
||||
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> {
|
||||
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<String, StoreConnection>,
|
||||
}
|
||||
|
||||
impl Stores {
|
||||
fn new() -> Stores {
|
||||
Stores {
|
||||
connections: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn singleton() -> &'static RwLock<Stores> {
|
||||
&*MANAGER
|
||||
}
|
||||
|
||||
fn is_store_open(name: &str) -> bool {
|
||||
Stores::singleton().read().unwrap().is_open(&name)
|
||||
}
|
||||
|
||||
pub fn open_store<T>(path: T) -> Result<Store> where T: AsRef<Path> {
|
||||
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<Store> {
|
||||
Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.to_string()))?.open(name, "")
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlcipher")]
|
||||
pub fn open_store_with_key<T>(path: T, encryption_key: &str) -> Result<Store> where T: AsRef<Path> {
|
||||
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<T>(path: T) -> Result<Option<Store>> where T: AsRef<Path> {
|
||||
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<T>(path: T, encryption_key: &str) -> Result<Option<Store>> where T: AsRef<Path> {
|
||||
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<Option<Store>> {
|
||||
Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.to_string()))?.get(name)
|
||||
}
|
||||
|
||||
pub fn connect_store< T>(path: T) -> Result<Store> where T: AsRef<Path> {
|
||||
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<Store> where T: AsRef<Path> {
|
||||
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<Store> {
|
||||
Stores::singleton().write().map_err(|_| MentatError::StoresLockPoisoned(name.to_string()))?.connect(name)
|
||||
}
|
||||
|
||||
pub fn close_store<T>(path: T) -> Result<()> where T: AsRef<Path> {
|
||||
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<T>(&mut self, name: &str, path: T) -> Result<Store> where T: AsRef<Path> {
|
||||
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<T>(&mut self, name: &str, path: T, encryption_key: &str) -> Result<Store> where T: AsRef<Path> {
|
||||
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<Option<Store>> {
|
||||
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<Option<Store>> {
|
||||
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<Store> {
|
||||
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<Store> {
|
||||
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 mentat_transaction::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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<Conn> = 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<Conn> = 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");
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue