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("'", "''")
|
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() {
|
let conn = match uri.to_string_lossy().len() {
|
||||||
0 => rusqlite::Connection::open_in_memory()?,
|
0 => rusqlite::Connection::open_in_memory()?,
|
||||||
_ => rusqlite::Connection::open(uri)?,
|
_ => rusqlite::Connection::open(uri)?,
|
||||||
|
|
|
@ -82,6 +82,7 @@ pub use entids::{
|
||||||
pub use db::{
|
pub use db::{
|
||||||
TypedSQLValue,
|
TypedSQLValue,
|
||||||
new_connection,
|
new_connection,
|
||||||
|
make_connection,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "sqlcipher")]
|
#[cfg(feature = "sqlcipher")]
|
||||||
|
|
|
@ -107,6 +107,7 @@ pub use mentat::{
|
||||||
QueryResults,
|
QueryResults,
|
||||||
RelResult,
|
RelResult,
|
||||||
Store,
|
Store,
|
||||||
|
Stores,
|
||||||
Syncable,
|
Syncable,
|
||||||
TypedValue,
|
TypedValue,
|
||||||
TxObserver,
|
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 {
|
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 uri = c_char_to_string(uri);
|
||||||
let key = c_char_to_string(key);
|
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
|
// 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 {
|
pub unsafe extern "C" fn typed_value_into_entid(typed_value: *mut Binding) -> Entid {
|
||||||
assert_not_null!(typed_value);
|
assert_not_null!(typed_value);
|
||||||
let typed_value = Box::from_raw(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)
|
unwrap_conversion(typed_value.into_entid(), ValueType::Ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std; // To refer to std::result::Result.
|
use std; // To refer to std::result::Result.
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use rusqlite;
|
use rusqlite;
|
||||||
|
|
||||||
|
@ -87,6 +87,15 @@ pub enum MentatError {
|
||||||
#[fail(display = "provided value of type {} doesn't match attribute value type {}", _0, _1)]
|
#[fail(display = "provided value of type {} doesn't match attribute value type {}", _0, _1)]
|
||||||
ValueTypeMismatch(ValueType, ValueType),
|
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)]
|
#[fail(display = "{}", _0)]
|
||||||
IoError(#[cause] std::io::Error),
|
IoError(#[cause] std::io::Error),
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ public interface JNA extends Library {
|
||||||
class EntityBuilder extends PointerType {}
|
class EntityBuilder extends PointerType {}
|
||||||
|
|
||||||
Store store_open(String dbPath, RustError.ByReference err);
|
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 destroy(Pointer obj);
|
||||||
void uuid_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); }
|
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() {
|
public static Mentat open() {
|
||||||
return open("");
|
return open("");
|
||||||
|
@ -51,6 +51,16 @@ public class Mentat extends RustObject<JNA.Store> {
|
||||||
return new Mentat(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
|
* Add an attribute to the cache. The {@link CacheDirection} determines how that attribute can be
|
||||||
* looked up.
|
* looked up.
|
||||||
|
|
|
@ -116,9 +116,9 @@ public class FFIIntegrationTest {
|
||||||
return mentat.transact(seattleData);
|
return mentat.transact(seattleData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mentat openAndInitializeCitiesStore() {
|
public Mentat openAndInitializeCitiesStore(String name) {
|
||||||
if (this.mentat == null) {
|
if (this.mentat == null) {
|
||||||
this.mentat = Mentat.open();
|
this.mentat = Mentat.namedInMemoryStore(name);
|
||||||
this.transactCitiesSchema(mentat);
|
this.transactCitiesSchema(mentat);
|
||||||
this.transactSeattleData(mentat);
|
this.transactSeattleData(mentat);
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void transactingVocabularySucceeds() {
|
public void transactingVocabularySucceeds() {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("transactingVocabularySucceeds");
|
||||||
TxReport schemaReport = this.transactCitiesSchema(mentat);
|
TxReport schemaReport = this.transactCitiesSchema(mentat);
|
||||||
assertNotNull(schemaReport);
|
assertNotNull(schemaReport);
|
||||||
assertTrue(schemaReport.getTxId() > 0);
|
assertTrue(schemaReport.getTxId() > 0);
|
||||||
|
@ -189,7 +189,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void transactingEntitiesSucceeds() {
|
public void transactingEntitiesSucceeds() {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("transactingEntitiesSucceeds");
|
||||||
this.transactCitiesSchema(mentat);
|
this.transactCitiesSchema(mentat);
|
||||||
TxReport dataReport = this.transactSeattleData(mentat);
|
TxReport dataReport = this.transactSeattleData(mentat);
|
||||||
assertNotNull(dataReport);
|
assertNotNull(dataReport);
|
||||||
|
@ -200,7 +200,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runScalarSucceeds() throws InterruptedException {
|
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]]]]";
|
String query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]";
|
||||||
final CountDownLatch expectation = new CountDownLatch(1);
|
final CountDownLatch expectation = new CountDownLatch(1);
|
||||||
mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() {
|
mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() {
|
||||||
|
@ -216,7 +216,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runCollSucceeds() throws InterruptedException {
|
public void runCollSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = openAndInitializeCitiesStore();
|
Mentat mentat = openAndInitializeCitiesStore("runCollSucceeds");
|
||||||
String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]";
|
String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]";
|
||||||
final CountDownLatch expectation = new CountDownLatch(1);
|
final CountDownLatch expectation = new CountDownLatch(1);
|
||||||
mentat.query(query).run(new CollResultHandler() {
|
mentat.query(query).run(new CollResultHandler() {
|
||||||
|
@ -234,7 +234,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runCollResultIteratorSucceeds() throws InterruptedException {
|
public void runCollResultIteratorSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = openAndInitializeCitiesStore();
|
Mentat mentat = openAndInitializeCitiesStore("runCollResultIteratorSucceeds");
|
||||||
String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]";
|
String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]";
|
||||||
final CountDownLatch expectation = new CountDownLatch(1);
|
final CountDownLatch expectation = new CountDownLatch(1);
|
||||||
mentat.query(query).run(new CollResultHandler() {
|
mentat.query(query).run(new CollResultHandler() {
|
||||||
|
@ -253,7 +253,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runTupleSucceeds() throws InterruptedException {
|
public void runTupleSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = openAndInitializeCitiesStore();
|
Mentat mentat = openAndInitializeCitiesStore("runTupleSucceeds");
|
||||||
String query = "[:find [?name ?cat]\n" +
|
String query = "[:find [?name ?cat]\n" +
|
||||||
" :where\n" +
|
" :where\n" +
|
||||||
" [?c :community/name ?name]\n" +
|
" [?c :community/name ?name]\n" +
|
||||||
|
@ -276,7 +276,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runRelIteratorSucceeds() throws InterruptedException {
|
public void runRelIteratorSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = openAndInitializeCitiesStore();
|
Mentat mentat = openAndInitializeCitiesStore("runRelIteratorSucceeds");
|
||||||
String query = "[:find ?name ?cat\n" +
|
String query = "[:find ?name ?cat\n" +
|
||||||
" :where\n" +
|
" :where\n" +
|
||||||
" [?c :community/name ?name]\n" +
|
" [?c :community/name ?name]\n" +
|
||||||
|
@ -313,7 +313,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runRelSucceeds() throws InterruptedException {
|
public void runRelSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = openAndInitializeCitiesStore();
|
Mentat mentat = openAndInitializeCitiesStore("runRelSucceeds");
|
||||||
String query = "[:find ?name ?cat\n" +
|
String query = "[:find ?name ?cat\n" +
|
||||||
" :where\n" +
|
" :where\n" +
|
||||||
" [?c :community/name ?name]\n" +
|
" [?c :community/name ?name]\n" +
|
||||||
|
@ -349,7 +349,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindingLongValueSucceeds() throws InterruptedException {
|
public void bindingLongValueSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("bindingLongValueSucceeds");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]";
|
String query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]";
|
||||||
|
@ -367,7 +367,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindingRefValueSucceeds() throws InterruptedException {
|
public void bindingRefValueSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("bindingRefValueSucceeds");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
long stringEntid = mentat.entIdForAttribute(":foo/string");
|
long stringEntid = mentat.entIdForAttribute(":foo/string");
|
||||||
final Long bEntid = report.getEntidForTempId("b");
|
final Long bEntid = report.getEntidForTempId("b");
|
||||||
|
@ -386,7 +386,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindingRefKwValueSucceeds() throws InterruptedException {
|
public void bindingRefKwValueSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("bindingRefKwValueSucceeds");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
String refKeyword = ":foo/string";
|
String refKeyword = ":foo/string";
|
||||||
final Long bEntid = report.getEntidForTempId("b");
|
final Long bEntid = report.getEntidForTempId("b");
|
||||||
|
@ -405,7 +405,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindingKwValueSucceeds() throws InterruptedException {
|
public void bindingKwValueSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("bindingKwValueSucceeds");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]";
|
String query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]";
|
||||||
|
@ -422,8 +422,8 @@ public class FFIIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindingDateValueSucceeds() throws InterruptedException {
|
public void bindingDateValueSucceeds() throws InterruptedException, ParseException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("bindingDateValueSucceeds");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
|
|
||||||
|
@ -445,7 +445,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindingStringValueSucceeds() throws InterruptedException {
|
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]]]]";
|
String query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]";
|
||||||
final CountDownLatch expectation = new CountDownLatch(1);
|
final CountDownLatch expectation = new CountDownLatch(1);
|
||||||
mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() {
|
mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() {
|
||||||
|
@ -461,7 +461,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindingUuidValueSucceeds() throws InterruptedException {
|
public void bindingUuidValueSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("bindingUuidValueSucceeds");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]";
|
String query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]";
|
||||||
|
@ -480,7 +480,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindingBooleanValueSucceeds() throws InterruptedException {
|
public void bindingBooleanValueSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("bindingBooleanValueSucceeds");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]";
|
String query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]";
|
||||||
|
@ -499,7 +499,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void bindingDoubleValueSucceeds() throws InterruptedException {
|
public void bindingDoubleValueSucceeds() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("bindingDoubleValueSucceeds");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]";
|
String query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]";
|
||||||
|
@ -517,7 +517,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void typedValueConvertsToLong() throws InterruptedException {
|
public void typedValueConvertsToLong() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToLong");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]";
|
String query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]";
|
||||||
|
@ -536,7 +536,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void typedValueConvertsToRef() throws InterruptedException {
|
public void typedValueConvertsToRef() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToRef");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?e . :where [?e :foo/long 25]]";
|
String query = "[:find ?e . :where [?e :foo/long 25]]";
|
||||||
|
@ -555,7 +555,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void typedValueConvertsToKeyword() throws InterruptedException {
|
public void typedValueConvertsToKeyword() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToKeyword");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]";
|
String query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]";
|
||||||
|
@ -574,7 +574,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void typedValueConvertsToBoolean() throws InterruptedException {
|
public void typedValueConvertsToBoolean() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToBoolean");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]";
|
String query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]";
|
||||||
|
@ -593,7 +593,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void typedValueConvertsToDouble() throws InterruptedException {
|
public void typedValueConvertsToDouble() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToDouble");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]";
|
String query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]";
|
||||||
|
@ -612,7 +612,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void typedValueConvertsToDate() throws InterruptedException, ParseException {
|
public void typedValueConvertsToDate() throws InterruptedException, ParseException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToDate");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]";
|
String query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]";
|
||||||
|
@ -638,7 +638,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void typedValueConvertsToString() throws InterruptedException {
|
public void typedValueConvertsToString() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToString");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]";
|
String query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]";
|
||||||
|
@ -657,7 +657,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void typedValueConvertsToUUID() throws InterruptedException {
|
public void typedValueConvertsToUUID() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToUUID");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
String query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]";
|
String query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]";
|
||||||
|
@ -676,8 +676,8 @@ public class FFIIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void valueForAttributeOfEntitySucceeds() {
|
public void valueForAttributeOfEntitySucceeds() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("valueForAttributeOfEntitySucceeds");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
final Long aEntid = report.getEntidForTempId("a");
|
final Long aEntid = report.getEntidForTempId("a");
|
||||||
TypedValue value = mentat.valueForAttributeOfEntity(":foo/long", aEntid);
|
TypedValue value = mentat.valueForAttributeOfEntity(":foo/long", aEntid);
|
||||||
|
@ -695,7 +695,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInProgressTransact() {
|
public void testInProgressTransact() {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("testInProgressTransact");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
assertNotNull(report);
|
assertNotNull(report);
|
||||||
|
|
||||||
|
@ -703,7 +703,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInProgressRollback() {
|
public void testInProgressRollback() {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("testInProgressRollback");
|
||||||
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
|
||||||
assertNotNull(report);
|
assertNotNull(report);
|
||||||
long aEntid = report.getEntidForTempId("a");
|
long aEntid = report.getEntidForTempId("a");
|
||||||
|
@ -721,7 +721,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInProgressEntityBuilder() throws InterruptedException {
|
public void testInProgressEntityBuilder() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("testInProgressEntityBuilder");
|
||||||
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
||||||
long bEntid = reports.dataReport.getEntidForTempId("b");
|
long bEntid = reports.dataReport.getEntidForTempId("b");
|
||||||
final long longEntid = reports.schemaReport.getEntidForTempId("l");
|
final long longEntid = reports.schemaReport.getEntidForTempId("l");
|
||||||
|
@ -795,7 +795,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEntityBuilderForEntid() throws InterruptedException {
|
public void testEntityBuilderForEntid() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderForEntid");
|
||||||
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
||||||
long bEntid = reports.dataReport.getEntidForTempId("b");
|
long bEntid = reports.dataReport.getEntidForTempId("b");
|
||||||
final long longEntid = reports.schemaReport.getEntidForTempId("l");
|
final long longEntid = reports.schemaReport.getEntidForTempId("l");
|
||||||
|
@ -869,7 +869,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEntityBuilderForTempid() throws InterruptedException {
|
public void testEntityBuilderForTempid() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderForTempid");
|
||||||
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
||||||
final long longEntid = reports.schemaReport.getEntidForTempId("l");
|
final long longEntid = reports.schemaReport.getEntidForTempId("l");
|
||||||
|
|
||||||
|
@ -922,7 +922,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInProgressBuilderTransact() throws InterruptedException {
|
public void testInProgressBuilderTransact() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("testInProgressBuilderTransact");
|
||||||
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
||||||
long aEntid = reports.dataReport.getEntidForTempId("a");
|
long aEntid = reports.dataReport.getEntidForTempId("a");
|
||||||
long bEntid = reports.dataReport.getEntidForTempId("b");
|
long bEntid = reports.dataReport.getEntidForTempId("b");
|
||||||
|
@ -984,7 +984,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEntityBuilderTransact() throws InterruptedException {
|
public void testEntityBuilderTransact() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderTransact");
|
||||||
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
||||||
long aEntid = reports.dataReport.getEntidForTempId("a");
|
long aEntid = reports.dataReport.getEntidForTempId("a");
|
||||||
long bEntid = reports.dataReport.getEntidForTempId("b");
|
long bEntid = reports.dataReport.getEntidForTempId("b");
|
||||||
|
@ -1047,7 +1047,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEntityBuilderRetract() throws InterruptedException {
|
public void testEntityBuilderRetract() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderRetract");
|
||||||
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
||||||
long bEntid = reports.dataReport.getEntidForTempId("b");
|
long bEntid = reports.dataReport.getEntidForTempId("b");
|
||||||
final long longEntid = reports.schemaReport.getEntidForTempId("l");
|
final long longEntid = reports.schemaReport.getEntidForTempId("l");
|
||||||
|
@ -1111,7 +1111,7 @@ public class FFIIntegrationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInProgressBuilderRetract() throws InterruptedException {
|
public void testInProgressBuilderRetract() throws InterruptedException {
|
||||||
Mentat mentat = Mentat.open();
|
Mentat mentat = Mentat.namedInMemoryStore("testInProgressBuilderRetract");
|
||||||
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
DBSetupResult reports = this.populateWithTypesSchema(mentat);
|
||||||
long bEntid = reports.dataReport.getEntidForTempId("b");
|
long bEntid = reports.dataReport.getEntidForTempId("b");
|
||||||
final long longEntid = reports.schemaReport.getEntidForTempId("l");
|
final long longEntid = reports.schemaReport.getEntidForTempId("l");
|
||||||
|
@ -1180,7 +1180,7 @@ public class FFIIntegrationTest {
|
||||||
" [?neighborhood :neighborhood/district ?d]\n" +
|
" [?neighborhood :neighborhood/district ?d]\n" +
|
||||||
" [?d :district/name ?district]]";
|
" [?d :district/name ?district]]";
|
||||||
|
|
||||||
Mentat mentat = openAndInitializeCitiesStore();
|
Mentat mentat = openAndInitializeCitiesStore("testCaching");
|
||||||
|
|
||||||
final CountDownLatch expectation1 = new CountDownLatch(1);
|
final CountDownLatch expectation1 = new CountDownLatch(1);
|
||||||
final QueryTimer uncachedTimer = new QueryTimer();
|
final QueryTimer uncachedTimer = new QueryTimer();
|
||||||
|
|
|
@ -61,6 +61,17 @@ open class Mentat: RustObject {
|
||||||
public class func open(storeURI: String = "") throws -> Mentat {
|
public class func open(storeURI: String = "") throws -> Mentat {
|
||||||
return Mentat(raw: try RustError.unwrap({err in store_open(storeURI, err) }))
|
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
|
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
|
// Store
|
||||||
struct Store*_Nonnull store_open(const char*_Nonnull uri, struct RustError* _Nonnull error);
|
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.
|
// Destructors.
|
||||||
void destroy(void* _Nullable obj);
|
void destroy(void* _Nullable obj);
|
||||||
|
|
|
@ -32,6 +32,11 @@ class MentatTests: XCTestCase {
|
||||||
func testOpenInMemoryStore() {
|
func testOpenInMemoryStore() {
|
||||||
XCTAssertNotNil(try Mentat.open().raw)
|
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
|
// test that a store can be opened in a specific location
|
||||||
func testOpenStoreInLocation() {
|
func testOpenStoreInLocation() {
|
||||||
|
@ -77,9 +82,9 @@ class MentatTests: XCTestCase {
|
||||||
return report
|
return report
|
||||||
}
|
}
|
||||||
|
|
||||||
func openAndInitializeCitiesStore() -> Mentat {
|
func openAndInitializeCitiesStore() throws -> Mentat {
|
||||||
guard let mentat = self.store else {
|
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.transactCitiesSchema(mentat: mentat)
|
||||||
let _ = try! self.transactSeattleData(mentat: mentat)
|
let _ = try! self.transactSeattleData(mentat: mentat)
|
||||||
self.store = mentat
|
self.store = mentat
|
||||||
|
@ -153,7 +158,7 @@ class MentatTests: XCTestCase {
|
||||||
|
|
||||||
func test1TransactVocabulary() {
|
func test1TransactVocabulary() {
|
||||||
do {
|
do {
|
||||||
let mentat = try Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "test1TransactVocabulary")
|
||||||
let vocab = try readCitiesSchema()
|
let vocab = try readCitiesSchema()
|
||||||
let report = try mentat.transact(transaction: vocab)
|
let report = try mentat.transact(transaction: vocab)
|
||||||
XCTAssertNotNil(report)
|
XCTAssertNotNil(report)
|
||||||
|
@ -165,7 +170,7 @@ class MentatTests: XCTestCase {
|
||||||
|
|
||||||
func test2TransactEntities() {
|
func test2TransactEntities() {
|
||||||
do {
|
do {
|
||||||
let mentat = try Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "test2TransactEntities")
|
||||||
let vocab = try readCitiesSchema()
|
let vocab = try readCitiesSchema()
|
||||||
let _ = try mentat.transact(transaction: vocab)
|
let _ = try mentat.transact(transaction: vocab)
|
||||||
let data = try readSeattleData()
|
let data = try readSeattleData()
|
||||||
|
@ -179,8 +184,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testQueryScalar() {
|
func testQueryScalar() throws {
|
||||||
let mentat = openAndInitializeCitiesStore()
|
let mentat = try openAndInitializeCitiesStore()
|
||||||
let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"
|
let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"
|
||||||
let expect = expectation(description: "Query is executed")
|
let expect = expectation(description: "Query is executed")
|
||||||
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?name", toString: "Wallingford").runScalar(callback: { scalarResult in
|
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?name", toString: "Wallingford").runScalar(callback: { scalarResult in
|
||||||
|
@ -197,8 +202,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testQueryColl() {
|
func testQueryColl() throws {
|
||||||
let mentat = openAndInitializeCitiesStore()
|
let mentat = try openAndInitializeCitiesStore()
|
||||||
let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"
|
let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"
|
||||||
let expect = expectation(description: "Query is executed")
|
let expect = expectation(description: "Query is executed")
|
||||||
XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in
|
XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in
|
||||||
|
@ -219,8 +224,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testQueryCollResultIterator() {
|
func testQueryCollResultIterator() throws {
|
||||||
let mentat = openAndInitializeCitiesStore()
|
let mentat = try openAndInitializeCitiesStore()
|
||||||
let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"
|
let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"
|
||||||
let expect = expectation(description: "Query is executed")
|
let expect = expectation(description: "Query is executed")
|
||||||
XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in
|
XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in
|
||||||
|
@ -240,8 +245,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testQueryTuple() {
|
func testQueryTuple() throws {
|
||||||
let mentat = openAndInitializeCitiesStore()
|
let mentat = try openAndInitializeCitiesStore()
|
||||||
let query = """
|
let query = """
|
||||||
[:find [?name ?cat]
|
[:find [?name ?cat]
|
||||||
:where
|
:where
|
||||||
|
@ -267,8 +272,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testQueryRel() {
|
func testQueryRel() throws {
|
||||||
let mentat = openAndInitializeCitiesStore()
|
let mentat = try openAndInitializeCitiesStore()
|
||||||
let query = """
|
let query = """
|
||||||
[:find ?name ?cat
|
[:find ?name ?cat
|
||||||
:where
|
:where
|
||||||
|
@ -300,8 +305,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testQueryRelResultIterator() {
|
func testQueryRelResultIterator() throws {
|
||||||
let mentat = openAndInitializeCitiesStore()
|
let mentat = try openAndInitializeCitiesStore()
|
||||||
let query = """
|
let query = """
|
||||||
[:find ?name ?cat
|
[:find ?name ?cat
|
||||||
:where
|
:where
|
||||||
|
@ -336,8 +341,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBindLong() {
|
func testBindLong() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testBindLong")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")
|
let aEntid = report!.entid(forTempId: "a")
|
||||||
let query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]"
|
let query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]"
|
||||||
|
@ -356,8 +361,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBindRef() {
|
func testBindRef() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testBindRef")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let stringEntid = mentat.entidForAttribute(attribute: ":foo/string")
|
let stringEntid = mentat.entidForAttribute(attribute: ":foo/string")
|
||||||
let bEntid = report!.entid(forTempId: "b")
|
let bEntid = report!.entid(forTempId: "b")
|
||||||
|
@ -377,8 +382,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBindKwRef() {
|
func testBindKwRef() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testBindKwRef")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let bEntid = report!.entid(forTempId: "b")
|
let bEntid = report!.entid(forTempId: "b")
|
||||||
let query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]"
|
let query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]"
|
||||||
|
@ -397,8 +402,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBindKw() {
|
func testBindKw() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testBindKw")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")
|
let aEntid = report!.entid(forTempId: "a")
|
||||||
let query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]"
|
let query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]"
|
||||||
|
@ -417,8 +422,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBindDate() {
|
func testBindDate() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testBindDate")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")
|
let aEntid = report!.entid(forTempId: "a")
|
||||||
let query = "[:find [?e ?d] :in ?now :where [?e :foo/instant ?d] [(< ?d ?now)]]"
|
let query = "[:find [?e ?d] :in ?now :where [?e :foo/instant ?d] [(< ?d ?now)]]"
|
||||||
|
@ -443,8 +448,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func testBindString() {
|
func testBindString() throws {
|
||||||
let mentat = openAndInitializeCitiesStore()
|
let mentat = try openAndInitializeCitiesStore()
|
||||||
let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"
|
let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"
|
||||||
let expect = expectation(description: "Query is executed")
|
let expect = expectation(description: "Query is executed")
|
||||||
XCTAssertNoThrow(try mentat.query(query: query)
|
XCTAssertNoThrow(try mentat.query(query: query)
|
||||||
|
@ -463,8 +468,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBindUuid() {
|
func testBindUuid() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testBindUuid")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")
|
let aEntid = report!.entid(forTempId: "a")
|
||||||
let query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]"
|
let query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]"
|
||||||
|
@ -484,8 +489,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBindBoolean() {
|
func testBindBoolean() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testBindBoolean")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")
|
let aEntid = report!.entid(forTempId: "a")
|
||||||
let query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]"
|
let query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]"
|
||||||
|
@ -504,8 +509,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBindDouble() {
|
func testBindDouble() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testBindDouble")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")
|
let aEntid = report!.entid(forTempId: "a")
|
||||||
let query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]"
|
let query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]"
|
||||||
|
@ -524,8 +529,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTypedValueAsLong() {
|
func testTypedValueAsLong() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsLong")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")!
|
let aEntid = report!.entid(forTempId: "a")!
|
||||||
let query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]"
|
let query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]"
|
||||||
|
@ -545,8 +550,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTypedValueAsRef() {
|
func testTypedValueAsRef() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsRef")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")!
|
let aEntid = report!.entid(forTempId: "a")!
|
||||||
let query = "[:find ?e . :where [?e :foo/long 25]]"
|
let query = "[:find ?e . :where [?e :foo/long 25]]"
|
||||||
|
@ -565,8 +570,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTypedValueAsKw() {
|
func testTypedValueAsKw() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsKw")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")!
|
let aEntid = report!.entid(forTempId: "a")!
|
||||||
let query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]"
|
let query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]"
|
||||||
|
@ -586,8 +591,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTypedValueAsBoolean() {
|
func testTypedValueAsBoolean() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsBoolean")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")!
|
let aEntid = report!.entid(forTempId: "a")!
|
||||||
let query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]"
|
let query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]"
|
||||||
|
@ -607,8 +612,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTypedValueAsDouble() {
|
func testTypedValueAsDouble() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsDouble")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")!
|
let aEntid = report!.entid(forTempId: "a")!
|
||||||
let query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]"
|
let query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]"
|
||||||
|
@ -628,8 +633,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTypedValueAsDate() {
|
func testTypedValueAsDate() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsDate")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")!
|
let aEntid = report!.entid(forTempId: "a")!
|
||||||
let query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]"
|
let query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]"
|
||||||
|
@ -654,8 +659,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTypedValueAsString() {
|
func testTypedValueAsString() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsString")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")!
|
let aEntid = report!.entid(forTempId: "a")!
|
||||||
let query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]"
|
let query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]"
|
||||||
|
@ -675,8 +680,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTypedValueAsUuid() {
|
func testTypedValueAsUuid() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsUuid")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")!
|
let aEntid = report!.entid(forTempId: "a")!
|
||||||
let query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]"
|
let query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]"
|
||||||
|
@ -697,8 +702,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testValueForAttributeOfEntity() {
|
func testValueForAttributeOfEntity() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testValueForAttributeOfEntity")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = report!.entid(forTempId: "a")!
|
let aEntid = report!.entid(forTempId: "a")!
|
||||||
var value: TypedValue? = nil;
|
var value: TypedValue? = nil;
|
||||||
|
@ -707,15 +712,15 @@ class MentatTests: XCTestCase {
|
||||||
assert(value?.asLong() == 25)
|
assert(value?.asLong() == 25)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEntidForAttribute() {
|
func testEntidForAttribute() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testEntidForAttribute")
|
||||||
let _ = self.populateWithTypesSchema(mentat: mentat)
|
let _ = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let entid = mentat.entidForAttribute(attribute: ":foo/long")
|
let entid = mentat.entidForAttribute(attribute: ":foo/long")
|
||||||
assert(entid == 65540)
|
assert(entid == 65540)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMultipleQueries() {
|
func testMultipleQueries() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testMultipleQueries")
|
||||||
let _ = self.populateWithTypesSchema(mentat: mentat)
|
let _ = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]")
|
let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]")
|
||||||
|
|
||||||
|
@ -739,8 +744,8 @@ class MentatTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNestedQueries() {
|
func testNestedQueries() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testNestedQueries")
|
||||||
let _ = self.populateWithTypesSchema(mentat: mentat)
|
let _ = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]")
|
let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]")
|
||||||
let q2 = 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() {
|
func test3InProgressTransact() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "test3InProgressTransact")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
XCTAssertNotNil(report)
|
XCTAssertNotNil(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInProgressRollback() {
|
func testInProgressRollback() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testInProgressRollback")
|
||||||
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
XCTAssertNotNil(report)
|
XCTAssertNotNil(report)
|
||||||
let aEntid = report!.entid(forTempId: "a")!
|
let aEntid = report!.entid(forTempId: "a")!
|
||||||
|
@ -785,8 +790,8 @@ class MentatTests: XCTestCase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInProgressEntityBuilder() {
|
func testInProgressEntityBuilder() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testInProgressEntityBuilder")
|
||||||
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let bEntid = dataReport!.entid(forTempId: "b")!
|
let bEntid = dataReport!.entid(forTempId: "b")!
|
||||||
let longEntid = schemaReport!.entid(forTempId: "l")!
|
let longEntid = schemaReport!.entid(forTempId: "l")!
|
||||||
|
@ -850,8 +855,8 @@ class MentatTests: XCTestCase {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEntityBuilderForEntid() {
|
func testEntityBuilderForEntid() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderForEntid")
|
||||||
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let bEntid = dataReport!.entid(forTempId: "b")!
|
let bEntid = dataReport!.entid(forTempId: "b")!
|
||||||
let longEntid = schemaReport!.entid(forTempId: "l")!
|
let longEntid = schemaReport!.entid(forTempId: "l")!
|
||||||
|
@ -915,8 +920,8 @@ class MentatTests: XCTestCase {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEntityBuilderForTempid() {
|
func testEntityBuilderForTempid() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderForTempid")
|
||||||
let (schemaReport, _) = self.populateWithTypesSchema(mentat: mentat)
|
let (schemaReport, _) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let longEntid = schemaReport!.entid(forTempId: "l")!
|
let longEntid = schemaReport!.entid(forTempId: "l")!
|
||||||
// test that the values are as expected
|
// test that the values are as expected
|
||||||
|
@ -962,8 +967,8 @@ class MentatTests: XCTestCase {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInProgressBuilderTransact() {
|
func testInProgressBuilderTransact() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testInProgressBuilderTransact")
|
||||||
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = dataReport!.entid(forTempId: "a")!
|
let aEntid = dataReport!.entid(forTempId: "a")!
|
||||||
let bEntid = dataReport!.entid(forTempId: "b")!
|
let bEntid = dataReport!.entid(forTempId: "b")!
|
||||||
|
@ -1018,8 +1023,8 @@ class MentatTests: XCTestCase {
|
||||||
XCTAssertEqual(22, longValue?.asLong())
|
XCTAssertEqual(22, longValue?.asLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEntityBuilderTransact() {
|
func testEntityBuilderTransact() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderTransact")
|
||||||
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let aEntid = dataReport!.entid(forTempId: "a")!
|
let aEntid = dataReport!.entid(forTempId: "a")!
|
||||||
let bEntid = dataReport!.entid(forTempId: "b")!
|
let bEntid = dataReport!.entid(forTempId: "b")!
|
||||||
|
@ -1074,8 +1079,8 @@ class MentatTests: XCTestCase {
|
||||||
XCTAssertEqual(22, longValue?.asLong())
|
XCTAssertEqual(22, longValue?.asLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEntityBuilderRetract() {
|
func testEntityBuilderRetract() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderRetract")
|
||||||
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let bEntid = dataReport!.entid(forTempId: "b")!
|
let bEntid = dataReport!.entid(forTempId: "b")!
|
||||||
let stringEntid = schemaReport!.entid(forTempId: "s")!
|
let stringEntid = schemaReport!.entid(forTempId: "s")!
|
||||||
|
@ -1124,8 +1129,8 @@ class MentatTests: XCTestCase {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInProgressEntityBuilderRetract() {
|
func testInProgressEntityBuilderRetract() throws {
|
||||||
let mentat = try! Mentat.open()
|
let mentat = try Mentat(namedInMemoryStore: "testInProgressEntityBuilderRetract")
|
||||||
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
|
||||||
let bEntid = dataReport!.entid(forTempId: "b")!
|
let bEntid = dataReport!.entid(forTempId: "b")!
|
||||||
let stringEntid = schemaReport!.entid(forTempId: "s")!
|
let stringEntid = schemaReport!.entid(forTempId: "s")!
|
||||||
|
@ -1174,7 +1179,7 @@ class MentatTests: XCTestCase {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCaching() {
|
func testCaching() throws {
|
||||||
let query = """
|
let query = """
|
||||||
[:find ?district :where
|
[:find ?district :where
|
||||||
[?neighborhood :neighborhood/name \"Beacon Hill\"]
|
[?neighborhood :neighborhood/name \"Beacon Hill\"]
|
||||||
|
@ -1182,7 +1187,7 @@ class MentatTests: XCTestCase {
|
||||||
[?d :district/name ?district]]
|
[?d :district/name ?district]]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
let mentat = openAndInitializeCitiesStore()
|
let mentat = try openAndInitializeCitiesStore()
|
||||||
|
|
||||||
struct QueryTimer {
|
struct QueryTimer {
|
||||||
private var _start: UInt64
|
private var _start: UInt64
|
||||||
|
|
43
src/conn.rs
43
src/conn.rs
|
@ -10,10 +10,6 @@
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::borrow::{
|
|
||||||
Borrow,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::collections::{
|
use std::collections::{
|
||||||
BTreeMap,
|
BTreeMap,
|
||||||
};
|
};
|
||||||
|
@ -134,7 +130,6 @@ impl Conn {
|
||||||
/// Prepare the provided SQLite handle for use as a Mentat store. Creates tables but
|
/// 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
|
/// _does not_ write the bootstrap schema. This constructor should only be used by
|
||||||
/// consumers that expect to populate raw transaction data themselves.
|
/// consumers that expect to populate raw transaction data themselves.
|
||||||
|
|
||||||
pub(crate) fn empty(sqlite: &mut rusqlite::Connection) -> Result<Conn> {
|
pub(crate) fn empty(sqlite: &mut rusqlite::Connection) -> Result<Conn> {
|
||||||
let (tx, db) = db::create_empty_current_version(sqlite)?;
|
let (tx, db) = db::create_empty_current_version(sqlite)?;
|
||||||
tx.commit()?;
|
tx.commit()?;
|
||||||
|
@ -276,7 +271,7 @@ impl Conn {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take a SQLite transaction.
|
/// 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 tx = sqlite.transaction_with_behavior(behavior)?;
|
||||||
let (current_generation, current_partition_map, current_schema, cache_cow) =
|
let (current_generation, current_partition_map, current_schema, cache_cow) =
|
||||||
{
|
{
|
||||||
|
@ -305,12 +300,12 @@ impl Conn {
|
||||||
|
|
||||||
// Helper to avoid passing connections around.
|
// Helper to avoid passing connections around.
|
||||||
// Make both args mutable so that we can't have parallel access.
|
// 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)
|
self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Deferred)
|
||||||
.map(|ip| InProgressRead { in_progress: ip })
|
.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)
|
self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Deferred)
|
||||||
.map(|mut ip| {
|
.map(|mut ip| {
|
||||||
ip.use_caching(false);
|
ip.use_caching(false);
|
||||||
|
@ -322,20 +317,20 @@ impl Conn {
|
||||||
/// connections from taking immediate or exclusive transactions. This is appropriate for our
|
/// 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
|
/// 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.
|
/// 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)
|
self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transact entities against the Mentat store, using the given connection and the current
|
/// Transact entities against the Mentat store, using the given connection and the current
|
||||||
/// metadata.
|
/// metadata.
|
||||||
pub fn transact<B>(&mut self,
|
pub fn transact<T>(&self,
|
||||||
sqlite: &mut rusqlite::Connection,
|
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
|
// 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
|
// 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
|
// 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.
|
// 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 mut in_progress = self.begin_transaction(sqlite)?;
|
||||||
let report = in_progress.transact_entities(entities)?;
|
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.
|
/// `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::Add is idempotent - each attribute is only added once.
|
||||||
/// CacheAction::Remove throws an error if the attribute does not currently exist in the cache.
|
/// 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,
|
sqlite: &mut rusqlite::Connection,
|
||||||
schema: &Schema,
|
schema: &Schema,
|
||||||
attribute: &Keyword,
|
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);
|
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);
|
self.tx_observer_service.lock().unwrap().deregister(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -428,7 +423,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transact_does_not_collide_existing_entids() {
|
fn test_transact_does_not_collide_existing_entids() {
|
||||||
let mut sqlite = db::new_connection("").unwrap();
|
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's find out the next ID that'll be allocated. We're going to try to collide with it
|
||||||
// a bit later.
|
// a bit later.
|
||||||
|
@ -454,7 +449,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transact_does_not_collide_new_entids() {
|
fn test_transact_does_not_collide_new_entids() {
|
||||||
let mut sqlite = db::new_connection("").unwrap();
|
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'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();
|
let next = conn.metadata.lock().expect("metadata").partition_map[":db.part/user"].next_entid();
|
||||||
|
@ -488,7 +483,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compound_transact() {
|
fn test_compound_transact() {
|
||||||
let mut sqlite = db::new_connection("").unwrap();
|
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);
|
let tempid_offset = get_next_entid(&conn);
|
||||||
|
|
||||||
|
@ -529,7 +524,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_prepared_query() {
|
fn test_simple_prepared_query() {
|
||||||
let mut c = db::new_connection("").expect("Couldn't open conn.");
|
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#"[
|
conn.transact(&mut c, r#"[
|
||||||
[:db/add "s" :db/ident :foo/boolean]
|
[:db/add "s" :db/ident :foo/boolean]
|
||||||
[:db/add "s" :db/valueType :db.type/boolean]
|
[:db/add "s" :db/valueType :db.type/boolean]
|
||||||
|
@ -566,7 +561,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compound_rollback() {
|
fn test_compound_rollback() {
|
||||||
let mut sqlite = db::new_connection("").unwrap();
|
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);
|
let tempid_offset = get_next_entid(&conn);
|
||||||
|
|
||||||
|
@ -616,7 +611,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transact_errors() {
|
fn test_transact_errors() {
|
||||||
let mut sqlite = db::new_connection("").unwrap();
|
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.
|
// Good: empty transaction.
|
||||||
let report = conn.transact(&mut sqlite, "[]").unwrap();
|
let report = conn.transact(&mut sqlite, "[]").unwrap();
|
||||||
|
@ -661,7 +656,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_to_cache_failure_no_attribute() {
|
fn test_add_to_cache_failure_no_attribute() {
|
||||||
let mut sqlite = db::new_connection("").unwrap();
|
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#"[
|
let _report = conn.transact(&mut sqlite, r#"[
|
||||||
{ :db/ident :foo/bar
|
{ :db/ident :foo/bar
|
||||||
:db/valueType :db.type/long },
|
:db/valueType :db.type/long },
|
||||||
|
@ -682,7 +677,7 @@ mod tests {
|
||||||
fn test_lookup_attribute_with_caching() {
|
fn test_lookup_attribute_with_caching() {
|
||||||
|
|
||||||
let mut sqlite = db::new_connection("").unwrap();
|
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#"[
|
let _report = conn.transact(&mut sqlite, r#"[
|
||||||
{ :db/ident :foo/bar
|
{ :db/ident :foo/bar
|
||||||
:db/valueType :db.type/long },
|
:db/valueType :db.type/long },
|
||||||
|
@ -741,7 +736,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cache_usage() {
|
fn test_cache_usage() {
|
||||||
let mut sqlite = db::new_connection("").unwrap();
|
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_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;
|
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 conn;
|
||||||
pub mod query_builder;
|
pub mod query_builder;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
pub mod stores;
|
||||||
pub mod vocabulary;
|
pub mod vocabulary;
|
||||||
|
|
||||||
pub use query_builder::{
|
pub use query_builder::{
|
||||||
|
@ -199,6 +200,10 @@ pub use store::{
|
||||||
Store,
|
Store,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use stores::{
|
||||||
|
Stores,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use edn::symbols::Keyword;
|
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
|
/// A convenience wrapper around a single SQLite connection and a Conn. This is suitable
|
||||||
/// for applications that don't require complex connection management.
|
/// for applications that don't require complex connection management.
|
||||||
pub struct Store {
|
pub struct Store {
|
||||||
conn: Conn,
|
conn: Arc<Conn>,
|
||||||
sqlite: rusqlite::Connection,
|
sqlite: rusqlite::Connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Store {
|
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.
|
/// Open a store at the supplied path, ensuring that it includes the bootstrap schema.
|
||||||
pub fn open(path: &str) -> Result<Store> {
|
pub fn open(path: &str) -> Result<Store> {
|
||||||
let mut connection = ::new_connection(path)?;
|
let mut connection = ::new_connection(path)?;
|
||||||
let conn = Conn::connect(&mut connection)?;
|
let conn = Conn::connect(&mut connection)?;
|
||||||
Ok(Store {
|
Ok(Store {
|
||||||
conn: conn,
|
conn: Arc::new(conn),
|
||||||
sqlite: connection,
|
sqlite: connection,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -104,14 +112,33 @@ impl Store {
|
||||||
let mut connection = ::new_connection_with_key(path, encryption_key)?;
|
let mut connection = ::new_connection_with_key(path, encryption_key)?;
|
||||||
let conn = Conn::connect(&mut connection)?;
|
let conn = Conn::connect(&mut connection)?;
|
||||||
Ok(Store {
|
Ok(Store {
|
||||||
conn: conn,
|
conn: Arc::new(conn),
|
||||||
sqlite: connection,
|
sqlite: connection,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the key for a database that was opened using `open_with_key` (using `PRAGMA
|
/// Variant of `open_empty` that allows a key (for encryption/decryption) to
|
||||||
/// rekey`). Fails unless linked against sqlcipher (or something else that supports the Sqlite
|
/// be supplied. Fails unless linked against sqlcipher (or something else
|
||||||
/// Encryption Extension).
|
/// 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<()> {
|
pub fn change_encryption_key(&mut self, new_encryption_key: &str) -> Result<()> {
|
||||||
::change_encryption_key(&self.sqlite, new_encryption_key)?;
|
::change_encryption_key(&self.sqlite, new_encryption_key)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -131,12 +158,12 @@ impl Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Store {
|
impl Store {
|
||||||
pub fn dismantle(self) -> (rusqlite::Connection, Conn) {
|
pub fn dismantle(self) -> (rusqlite::Connection, Arc<Conn>) {
|
||||||
(self.sqlite, self.conn)
|
(self.sqlite, self.conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn conn(&self) -> &Conn {
|
pub fn conn(&self) -> Arc<Conn> {
|
||||||
&self.conn
|
self.conn.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin_read<'m>(&'m mut self) -> Result<InProgressRead<'m, 'm>> {
|
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.
|
// Now try to add them to a real store.
|
||||||
let mut sqlite = mentat_db::db::new_connection("").unwrap();
|
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");
|
let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully");
|
||||||
|
|
||||||
// This should fail: unrecognized entid.
|
// This should fail: unrecognized entid.
|
||||||
|
@ -84,7 +84,7 @@ fn test_entity_builder_bogus_entids() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_in_progress_builder() {
|
fn test_in_progress_builder() {
|
||||||
let mut sqlite = mentat_db::db::new_connection("").unwrap();
|
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!
|
// Give ourselves a schema to work with!
|
||||||
conn.transact(&mut sqlite, r#"[
|
conn.transact(&mut sqlite, r#"[
|
||||||
|
@ -116,7 +116,7 @@ fn test_in_progress_builder() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_entity_builder() {
|
fn test_entity_builder() {
|
||||||
let mut sqlite = mentat_db::db::new_connection("").unwrap();
|
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_one = kw!(:foo/one);
|
||||||
let foo_many = kw!(:foo/many);
|
let foo_many = kw!(:foo/many);
|
||||||
|
|
|
@ -254,7 +254,7 @@ fn test_instants_and_uuids() {
|
||||||
let start = Utc::now() + FixedOffset::west(60 * 60);
|
let start = Utc::now() + FixedOffset::west(60 * 60);
|
||||||
|
|
||||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
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#"[
|
conn.transact(&mut c, r#"[
|
||||||
[:db/add "s" :db/ident :foo/uuid]
|
[:db/add "s" :db/ident :foo/uuid]
|
||||||
[:db/add "s" :db/valueType :db.type/uuid]
|
[:db/add "s" :db/valueType :db.type/uuid]
|
||||||
|
@ -291,7 +291,7 @@ fn test_instants_and_uuids() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tx() {
|
fn test_tx() {
|
||||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
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#"[
|
conn.transact(&mut c, r#"[
|
||||||
[:db/add "s" :db/ident :foo/uuid]
|
[:db/add "s" :db/ident :foo/uuid]
|
||||||
[:db/add "s" :db/valueType :db.type/uuid]
|
[:db/add "s" :db/valueType :db.type/uuid]
|
||||||
|
@ -324,7 +324,7 @@ fn test_tx() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tx_as_input() {
|
fn test_tx_as_input() {
|
||||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
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#"[
|
conn.transact(&mut c, r#"[
|
||||||
[:db/add "s" :db/ident :foo/uuid]
|
[:db/add "s" :db/ident :foo/uuid]
|
||||||
[:db/add "s" :db/valueType :db.type/uuid]
|
[:db/add "s" :db/valueType :db.type/uuid]
|
||||||
|
@ -361,7 +361,7 @@ fn test_tx_as_input() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fulltext() {
|
fn test_fulltext() {
|
||||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
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#"[
|
conn.transact(&mut c, r#"[
|
||||||
[:db/add "a" :db/ident :foo/term]
|
[:db/add "a" :db/ident :foo/term]
|
||||||
|
@ -467,7 +467,7 @@ fn test_fulltext() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_instant_range_query() {
|
fn test_instant_range_query() {
|
||||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
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#"[
|
conn.transact(&mut c, r#"[
|
||||||
[:db/add "a" :db/ident :foo/date]
|
[:db/add "a" :db/ident :foo/date]
|
||||||
|
@ -503,7 +503,7 @@ fn test_instant_range_query() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lookup() {
|
fn test_lookup() {
|
||||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
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#"[
|
conn.transact(&mut c, r#"[
|
||||||
[:db/add "a" :db/ident :foo/date]
|
[:db/add "a" :db/ident :foo/date]
|
||||||
|
@ -656,7 +656,7 @@ fn test_aggregates_type_handling() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_type_reqs() {
|
fn test_type_reqs() {
|
||||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
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#"[
|
conn.transact(&mut c, r#"[
|
||||||
{:db/ident :test/boolean :db/valueType :db.type/boolean :db/cardinality :db.cardinality/one}
|
{: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.
|
// so the specific test we use doesn't matter that much.
|
||||||
run_tx_data_test(Store::open_with_key("", "secret").expect("opened"));
|
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]
|
#[test]
|
||||||
fn test_reader() {
|
fn test_reader() {
|
||||||
let mut c = new_connection("").expect("Couldn't open conn.");
|
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");
|
let db_tx = c.transaction().expect("db tx");
|
||||||
// Don't inspect the bootstrap transaction, but we'd like to see it's there.
|
// 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 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 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]
|
let foo_version_query = r#"[:find [?version ?aa]
|
||||||
:where
|
:where
|
||||||
|
|
|
@ -187,7 +187,7 @@ impl InputReader {
|
||||||
fn read_line(&mut self, prompt: &str) -> UserAction {
|
fn read_line(&mut self, prompt: &str) -> UserAction {
|
||||||
match self.interface {
|
match self.interface {
|
||||||
Some(ref mut r) => {
|
Some(ref mut r) => {
|
||||||
r.set_prompt(prompt);
|
let _ = r.set_prompt(prompt);
|
||||||
r.read_line().ok().map_or(UserAction::Quit, |line|
|
r.read_line().ok().map_or(UserAction::Quit, |line|
|
||||||
match line {
|
match line {
|
||||||
ReadResult::Input(s) => UserAction::TextInput(s),
|
ReadResult::Input(s) => UserAction::TextInput(s),
|
||||||
|
|
Loading…
Reference in a new issue