Compare commits

...

4 commits

Author SHA1 Message Date
Emily Toop
5ead1d3989 Fixing tests broken by rebase 2018-08-29 17:34:58 +01:00
Emily Toop
b361ea8119 Update Android and iOS SDKs to reflect new named in memory functions. Utilize named in memory store in tests to ensure isolation when running. 2018-08-29 17:12:52 +01:00
Emily Toop
e1c2c9ee77 Add Stores to manage Conn and creation of rusqlite::Connections.
Enable ability to create named in memory stores and in memory stores with shared caches.
Include ability to create encrypted connections.
Update `Store` to take an `Arc<Conn>` so references can be shared.
Update FFI to use `Stores` instead of `Store`.
Add `store_open_named_in_memory_store` to open a named in-memory store over FFI (useful for tests).
2018-08-29 16:28:36 +01:00
Emily Toop
d056c7cc10 Fold in @ncalexan's changes to make Conn manage its own mutability.
ref: https://github.com/ncalexan/mentat/tree/stores/src
2018-08-29 11:29:43 +01:00
19 changed files with 730 additions and 169 deletions

View file

@ -89,7 +89,7 @@ fn escape_string_for_pragma(s: &str) -> String {
s.replace("'", "''")
}
fn make_connection(uri: &Path, maybe_encryption_key: Option<&str>) -> rusqlite::Result<rusqlite::Connection> {
pub fn make_connection(uri: &Path, maybe_encryption_key: Option<&str>) -> rusqlite::Result<rusqlite::Connection> {
let conn = match uri.to_string_lossy().len() {
0 => rusqlite::Connection::open_in_memory()?,
_ => rusqlite::Connection::open(uri)?,

View file

@ -82,6 +82,7 @@ pub use entids::{
pub use db::{
TypedSQLValue,
new_connection,
make_connection,
};
#[cfg(feature = "sqlcipher")]

View file

@ -107,6 +107,7 @@ pub use mentat::{
QueryResults,
RelResult,
Store,
Stores,
Syncable,
TypedValue,
TxObserver,
@ -220,7 +221,14 @@ pub unsafe extern "C" fn store_open(uri: *const c_char, error: *mut ExternError)
pub unsafe extern "C" fn store_open_encrypted(uri: *const c_char, key: *const c_char, error: *mut ExternError) -> *mut Store {
let uri = c_char_to_string(uri);
let key = c_char_to_string(key);
translate_result(Store::open_with_key(&uri, &key), error)
translate_result(Stores::open_with_key(&uri, &key), error)
}
/// Variant of store_open that opens a named in-memory database.
#[no_mangle]
pub unsafe extern "C" fn store_open_named_in_memory_store(name: *const c_char, error: *mut ExternError) -> *mut Store {
let name = c_char_to_string(name);
translate_result(Stores::open_named_in_memory_store(name), error)
}
// TODO: open empty
@ -1556,7 +1564,6 @@ pub unsafe extern "C" fn typed_value_into_long(typed_value: *mut Binding) -> c_l
pub unsafe extern "C" fn typed_value_into_entid(typed_value: *mut Binding) -> Entid {
assert_not_null!(typed_value);
let typed_value = Box::from_raw(typed_value);
println!("typed value as entid {:?}", typed_value);
unwrap_conversion(typed_value.into_entid(), ValueType::Ref)
}

View file

@ -11,8 +11,8 @@
#![allow(dead_code)]
use std; // To refer to std::result::Result.
use std::collections::BTreeSet;
use std::path::PathBuf;
use rusqlite;
@ -87,6 +87,15 @@ pub enum MentatError {
#[fail(display = "provided value of type {} doesn't match attribute value type {}", _0, _1)]
ValueTypeMismatch(ValueType, ValueType),
#[fail(display = "Cannot open store {} at path {:?} as it does not match previous store location {:?}", _0, _1, _2)]
StorePathMismatch(String, PathBuf, PathBuf),
#[fail(display = "The Store at {} does not exist or is not yet open.", _0)]
StoreNotFound(String),
#[fail(display = "The Store at {:?} has active connections and cannot be closed.", _0)]
StoresLockPoisoned(String),
#[fail(display = "{}", _0)]
IoError(#[cause] std::io::Error),

View file

@ -43,6 +43,7 @@ public interface JNA extends Library {
class EntityBuilder extends PointerType {}
Store store_open(String dbPath, RustError.ByReference err);
Store store_open_named_in_memory_store(String name, RustError.ByReference err);
void destroy(Pointer obj);
void uuid_destroy(Pointer obj);

View file

@ -28,7 +28,7 @@ public class Mentat extends RustObject<JNA.Store> {
private Mentat(JNA.Store rawPointer) { super(rawPointer); }
/**
* Open a connection to an in-memory Mentat Store.
* Open a connection to an anonymous in-memory Store.
*/
public static Mentat open() {
return open("");
@ -51,6 +51,16 @@ public class Mentat extends RustObject<JNA.Store> {
return new Mentat(store);
}
/**
* Open a connection to a named in-memory Store.
* @param name The named to be given to the in memory store to open.
* @return An instance of Mentat connected to a named in memory store.
*/
public static Mentat namedInMemoryStore(String name) {
RustError.ByReference err = new RustError.ByReference();
return new Mentat(JNA.INSTANCE.store_open_named_in_memory_store(name, err));
}
/**
* Add an attribute to the cache. The {@link CacheDirection} determines how that attribute can be
* looked up.

View file

@ -116,9 +116,9 @@ public class FFIIntegrationTest {
return mentat.transact(seattleData);
}
public Mentat openAndInitializeCitiesStore() {
public Mentat openAndInitializeCitiesStore(String name) {
if (this.mentat == null) {
this.mentat = Mentat.open();
this.mentat = Mentat.namedInMemoryStore(name);
this.transactCitiesSchema(mentat);
this.transactSeattleData(mentat);
}
@ -181,7 +181,7 @@ public class FFIIntegrationTest {
@Test
public void transactingVocabularySucceeds() {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("transactingVocabularySucceeds");
TxReport schemaReport = this.transactCitiesSchema(mentat);
assertNotNull(schemaReport);
assertTrue(schemaReport.getTxId() > 0);
@ -189,7 +189,7 @@ public class FFIIntegrationTest {
@Test
public void transactingEntitiesSucceeds() {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("transactingEntitiesSucceeds");
this.transactCitiesSchema(mentat);
TxReport dataReport = this.transactSeattleData(mentat);
assertNotNull(dataReport);
@ -200,7 +200,7 @@ public class FFIIntegrationTest {
@Test
public void runScalarSucceeds() throws InterruptedException {
Mentat mentat = openAndInitializeCitiesStore();
Mentat mentat = openAndInitializeCitiesStore("runScalarSucceeds");
String query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]";
final CountDownLatch expectation = new CountDownLatch(1);
mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() {
@ -216,7 +216,7 @@ public class FFIIntegrationTest {
@Test
public void runCollSucceeds() throws InterruptedException {
Mentat mentat = openAndInitializeCitiesStore();
Mentat mentat = openAndInitializeCitiesStore("runCollSucceeds");
String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]";
final CountDownLatch expectation = new CountDownLatch(1);
mentat.query(query).run(new CollResultHandler() {
@ -234,7 +234,7 @@ public class FFIIntegrationTest {
@Test
public void runCollResultIteratorSucceeds() throws InterruptedException {
Mentat mentat = openAndInitializeCitiesStore();
Mentat mentat = openAndInitializeCitiesStore("runCollResultIteratorSucceeds");
String query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]";
final CountDownLatch expectation = new CountDownLatch(1);
mentat.query(query).run(new CollResultHandler() {
@ -253,7 +253,7 @@ public class FFIIntegrationTest {
@Test
public void runTupleSucceeds() throws InterruptedException {
Mentat mentat = openAndInitializeCitiesStore();
Mentat mentat = openAndInitializeCitiesStore("runTupleSucceeds");
String query = "[:find [?name ?cat]\n" +
" :where\n" +
" [?c :community/name ?name]\n" +
@ -276,7 +276,7 @@ public class FFIIntegrationTest {
@Test
public void runRelIteratorSucceeds() throws InterruptedException {
Mentat mentat = openAndInitializeCitiesStore();
Mentat mentat = openAndInitializeCitiesStore("runRelIteratorSucceeds");
String query = "[:find ?name ?cat\n" +
" :where\n" +
" [?c :community/name ?name]\n" +
@ -313,7 +313,7 @@ public class FFIIntegrationTest {
@Test
public void runRelSucceeds() throws InterruptedException {
Mentat mentat = openAndInitializeCitiesStore();
Mentat mentat = openAndInitializeCitiesStore("runRelSucceeds");
String query = "[:find ?name ?cat\n" +
" :where\n" +
" [?c :community/name ?name]\n" +
@ -349,7 +349,7 @@ public class FFIIntegrationTest {
@Test
public void bindingLongValueSucceeds() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("bindingLongValueSucceeds");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]";
@ -367,7 +367,7 @@ public class FFIIntegrationTest {
@Test
public void bindingRefValueSucceeds() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("bindingRefValueSucceeds");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
long stringEntid = mentat.entIdForAttribute(":foo/string");
final Long bEntid = report.getEntidForTempId("b");
@ -386,7 +386,7 @@ public class FFIIntegrationTest {
@Test
public void bindingRefKwValueSucceeds() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("bindingRefKwValueSucceeds");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
String refKeyword = ":foo/string";
final Long bEntid = report.getEntidForTempId("b");
@ -405,7 +405,7 @@ public class FFIIntegrationTest {
@Test
public void bindingKwValueSucceeds() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("bindingKwValueSucceeds");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]";
@ -422,8 +422,8 @@ public class FFIIntegrationTest {
}
@Test
public void bindingDateValueSucceeds() throws InterruptedException {
Mentat mentat = Mentat.open();
public void bindingDateValueSucceeds() throws InterruptedException, ParseException {
Mentat mentat = Mentat.namedInMemoryStore("bindingDateValueSucceeds");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
@ -445,7 +445,7 @@ public class FFIIntegrationTest {
@Test
public void bindingStringValueSucceeds() throws InterruptedException {
Mentat mentat = this.openAndInitializeCitiesStore();
Mentat mentat = this.openAndInitializeCitiesStore("bindingStringValueSucceeds");
String query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]";
final CountDownLatch expectation = new CountDownLatch(1);
mentat.query(query).bind("?name", "Wallingford").run(new ScalarResultHandler() {
@ -461,7 +461,7 @@ public class FFIIntegrationTest {
@Test
public void bindingUuidValueSucceeds() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("bindingUuidValueSucceeds");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]";
@ -480,7 +480,7 @@ public class FFIIntegrationTest {
@Test
public void bindingBooleanValueSucceeds() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("bindingBooleanValueSucceeds");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]";
@ -499,7 +499,7 @@ public class FFIIntegrationTest {
@Test
public void bindingDoubleValueSucceeds() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("bindingDoubleValueSucceeds");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]";
@ -517,7 +517,7 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToLong() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToLong");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]";
@ -536,7 +536,7 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToRef() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToRef");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?e . :where [?e :foo/long 25]]";
@ -555,7 +555,7 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToKeyword() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToKeyword");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]";
@ -574,7 +574,7 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToBoolean() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToBoolean");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]";
@ -593,7 +593,7 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToDouble() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToDouble");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]";
@ -612,7 +612,7 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToDate() throws InterruptedException, ParseException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToDate");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]";
@ -638,7 +638,7 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToString() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToString");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]";
@ -657,7 +657,7 @@ public class FFIIntegrationTest {
@Test
public void typedValueConvertsToUUID() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("typedValueConvertsToUUID");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
String query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]";
@ -676,8 +676,8 @@ public class FFIIntegrationTest {
}
@Test
public void valueForAttributeOfEntitySucceeds() {
Mentat mentat = Mentat.open();
public void valueForAttributeOfEntitySucceeds() throws InterruptedException {
Mentat mentat = Mentat.namedInMemoryStore("valueForAttributeOfEntitySucceeds");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
final Long aEntid = report.getEntidForTempId("a");
TypedValue value = mentat.valueForAttributeOfEntity(":foo/long", aEntid);
@ -695,7 +695,7 @@ public class FFIIntegrationTest {
@Test
public void testInProgressTransact() {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("testInProgressTransact");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
assertNotNull(report);
@ -703,7 +703,7 @@ public class FFIIntegrationTest {
@Test
public void testInProgressRollback() {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("testInProgressRollback");
TxReport report = this.populateWithTypesSchema(mentat).dataReport;
assertNotNull(report);
long aEntid = report.getEntidForTempId("a");
@ -721,7 +721,7 @@ public class FFIIntegrationTest {
@Test
public void testInProgressEntityBuilder() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("testInProgressEntityBuilder");
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long bEntid = reports.dataReport.getEntidForTempId("b");
final long longEntid = reports.schemaReport.getEntidForTempId("l");
@ -795,7 +795,7 @@ public class FFIIntegrationTest {
@Test
public void testEntityBuilderForEntid() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderForEntid");
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long bEntid = reports.dataReport.getEntidForTempId("b");
final long longEntid = reports.schemaReport.getEntidForTempId("l");
@ -869,7 +869,7 @@ public class FFIIntegrationTest {
@Test
public void testEntityBuilderForTempid() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderForTempid");
DBSetupResult reports = this.populateWithTypesSchema(mentat);
final long longEntid = reports.schemaReport.getEntidForTempId("l");
@ -922,7 +922,7 @@ public class FFIIntegrationTest {
@Test
public void testInProgressBuilderTransact() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("testInProgressBuilderTransact");
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long aEntid = reports.dataReport.getEntidForTempId("a");
long bEntid = reports.dataReport.getEntidForTempId("b");
@ -984,7 +984,7 @@ public class FFIIntegrationTest {
@Test
public void testEntityBuilderTransact() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderTransact");
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long aEntid = reports.dataReport.getEntidForTempId("a");
long bEntid = reports.dataReport.getEntidForTempId("b");
@ -1047,7 +1047,7 @@ public class FFIIntegrationTest {
@Test
public void testEntityBuilderRetract() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("testEntityBuilderRetract");
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long bEntid = reports.dataReport.getEntidForTempId("b");
final long longEntid = reports.schemaReport.getEntidForTempId("l");
@ -1111,7 +1111,7 @@ public class FFIIntegrationTest {
@Test
public void testInProgressBuilderRetract() throws InterruptedException {
Mentat mentat = Mentat.open();
Mentat mentat = Mentat.namedInMemoryStore("testInProgressBuilderRetract");
DBSetupResult reports = this.populateWithTypesSchema(mentat);
long bEntid = reports.dataReport.getEntidForTempId("b");
final long longEntid = reports.schemaReport.getEntidForTempId("l");
@ -1180,7 +1180,7 @@ public class FFIIntegrationTest {
" [?neighborhood :neighborhood/district ?d]\n" +
" [?d :district/name ?district]]";
Mentat mentat = openAndInitializeCitiesStore();
Mentat mentat = openAndInitializeCitiesStore("testCaching");
final CountDownLatch expectation1 = new CountDownLatch(1);
final QueryTimer uncachedTimer = new QueryTimer();

View file

@ -61,6 +61,17 @@ open class Mentat: RustObject {
public class func open(storeURI: String = "") throws -> Mentat {
return Mentat(raw: try RustError.unwrap({err in store_open(storeURI, err) }))
}
/**
Open a connection to a Store in a given location.
If the store does not already exist, one will be created.
- Parameter storeURI: The URI as a String of the store to open.
If no store URI is provided, an in-memory store will be opened.
*/
public convenience init(namedInMemoryStore name: String) throws {
self.init(raw: try RustError.unwrap({err in store_open_named_in_memory_store(name, err) }))
}
/**
Add an attribute to the cache. The {@link CacheDirection} determines how that attribute can be

View file

@ -103,6 +103,7 @@ typedef NS_ENUM(NSInteger, ValueType) {
// Store
struct Store*_Nonnull store_open(const char*_Nonnull uri, struct RustError* _Nonnull error);
struct Store*_Nonnull store_open_named_in_memory_store(const char*_Nonnull name, struct RustError* _Nonnull error);
// Destructors.
void destroy(void* _Nullable obj);

View file

@ -32,6 +32,11 @@ class MentatTests: XCTestCase {
func testOpenInMemoryStore() {
XCTAssertNotNil(try Mentat.open().raw)
}
// test that a store can be opened in memory
func testOpenNamedInMemoryStore() {
XCTAssertNotNil(try Mentat(namedInMemoryStore: "testOpenInMemoryStore").raw)
}
// test that a store can be opened in a specific location
func testOpenStoreInLocation() {
@ -77,9 +82,9 @@ class MentatTests: XCTestCase {
return report
}
func openAndInitializeCitiesStore() -> Mentat {
func openAndInitializeCitiesStore() throws -> Mentat {
guard let mentat = self.store else {
let mentat = try! Mentat.open()
let mentat = try Mentat(namedInMemoryStore: "openAndInitializeCitiesStore")
let _ = try! self.transactCitiesSchema(mentat: mentat)
let _ = try! self.transactSeattleData(mentat: mentat)
self.store = mentat
@ -153,7 +158,7 @@ class MentatTests: XCTestCase {
func test1TransactVocabulary() {
do {
let mentat = try Mentat.open()
let mentat = try Mentat(namedInMemoryStore: "test1TransactVocabulary")
let vocab = try readCitiesSchema()
let report = try mentat.transact(transaction: vocab)
XCTAssertNotNil(report)
@ -165,7 +170,7 @@ class MentatTests: XCTestCase {
func test2TransactEntities() {
do {
let mentat = try Mentat.open()
let mentat = try Mentat(namedInMemoryStore: "test2TransactEntities")
let vocab = try readCitiesSchema()
let _ = try mentat.transact(transaction: vocab)
let data = try readSeattleData()
@ -179,8 +184,8 @@ class MentatTests: XCTestCase {
}
}
func testQueryScalar() {
let mentat = openAndInitializeCitiesStore()
func testQueryScalar() throws {
let mentat = try openAndInitializeCitiesStore()
let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"
let expect = expectation(description: "Query is executed")
XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?name", toString: "Wallingford").runScalar(callback: { scalarResult in
@ -197,8 +202,8 @@ class MentatTests: XCTestCase {
}
}
func testQueryColl() {
let mentat = openAndInitializeCitiesStore()
func testQueryColl() throws {
let mentat = try openAndInitializeCitiesStore()
let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"
let expect = expectation(description: "Query is executed")
XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in
@ -219,8 +224,8 @@ class MentatTests: XCTestCase {
}
}
func testQueryCollResultIterator() {
let mentat = openAndInitializeCitiesStore()
func testQueryCollResultIterator() throws {
let mentat = try openAndInitializeCitiesStore()
let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"
let expect = expectation(description: "Query is executed")
XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in
@ -240,8 +245,8 @@ class MentatTests: XCTestCase {
}
}
func testQueryTuple() {
let mentat = openAndInitializeCitiesStore()
func testQueryTuple() throws {
let mentat = try openAndInitializeCitiesStore()
let query = """
[:find [?name ?cat]
:where
@ -267,8 +272,8 @@ class MentatTests: XCTestCase {
}
}
func testQueryRel() {
let mentat = openAndInitializeCitiesStore()
func testQueryRel() throws {
let mentat = try openAndInitializeCitiesStore()
let query = """
[:find ?name ?cat
:where
@ -300,8 +305,8 @@ class MentatTests: XCTestCase {
}
}
func testQueryRelResultIterator() {
let mentat = openAndInitializeCitiesStore()
func testQueryRelResultIterator() throws {
let mentat = try openAndInitializeCitiesStore()
let query = """
[:find ?name ?cat
:where
@ -336,8 +341,8 @@ class MentatTests: XCTestCase {
}
}
func testBindLong() {
let mentat = try! Mentat.open()
func testBindLong() throws {
let mentat = try Mentat(namedInMemoryStore: "testBindLong")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")
let query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]"
@ -356,8 +361,8 @@ class MentatTests: XCTestCase {
}
}
func testBindRef() {
let mentat = try! Mentat.open()
func testBindRef() throws {
let mentat = try Mentat(namedInMemoryStore: "testBindRef")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let stringEntid = mentat.entidForAttribute(attribute: ":foo/string")
let bEntid = report!.entid(forTempId: "b")
@ -377,8 +382,8 @@ class MentatTests: XCTestCase {
}
}
func testBindKwRef() {
let mentat = try! Mentat.open()
func testBindKwRef() throws {
let mentat = try Mentat(namedInMemoryStore: "testBindKwRef")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let bEntid = report!.entid(forTempId: "b")
let query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]"
@ -397,8 +402,8 @@ class MentatTests: XCTestCase {
}
}
func testBindKw() {
let mentat = try! Mentat.open()
func testBindKw() throws {
let mentat = try Mentat(namedInMemoryStore: "testBindKw")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")
let query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]"
@ -417,8 +422,8 @@ class MentatTests: XCTestCase {
}
}
func testBindDate() {
let mentat = try! Mentat.open()
func testBindDate() throws {
let mentat = try Mentat(namedInMemoryStore: "testBindDate")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")
let query = "[:find [?e ?d] :in ?now :where [?e :foo/instant ?d] [(< ?d ?now)]]"
@ -443,8 +448,8 @@ class MentatTests: XCTestCase {
}
func testBindString() {
let mentat = openAndInitializeCitiesStore()
func testBindString() throws {
let mentat = try openAndInitializeCitiesStore()
let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"
let expect = expectation(description: "Query is executed")
XCTAssertNoThrow(try mentat.query(query: query)
@ -463,8 +468,8 @@ class MentatTests: XCTestCase {
}
}
func testBindUuid() {
let mentat = try! Mentat.open()
func testBindUuid() throws {
let mentat = try Mentat(namedInMemoryStore: "testBindUuid")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")
let query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]"
@ -484,8 +489,8 @@ class MentatTests: XCTestCase {
}
}
func testBindBoolean() {
let mentat = try! Mentat.open()
func testBindBoolean() throws {
let mentat = try Mentat(namedInMemoryStore: "testBindBoolean")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")
let query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]"
@ -504,8 +509,8 @@ class MentatTests: XCTestCase {
}
}
func testBindDouble() {
let mentat = try! Mentat.open()
func testBindDouble() throws {
let mentat = try Mentat(namedInMemoryStore: "testBindDouble")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")
let query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]"
@ -524,8 +529,8 @@ class MentatTests: XCTestCase {
}
}
func testTypedValueAsLong() {
let mentat = try! Mentat.open()
func testTypedValueAsLong() throws {
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsLong")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")!
let query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]"
@ -545,8 +550,8 @@ class MentatTests: XCTestCase {
}
}
func testTypedValueAsRef() {
let mentat = try! Mentat.open()
func testTypedValueAsRef() throws {
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsRef")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")!
let query = "[:find ?e . :where [?e :foo/long 25]]"
@ -565,8 +570,8 @@ class MentatTests: XCTestCase {
}
}
func testTypedValueAsKw() {
let mentat = try! Mentat.open()
func testTypedValueAsKw() throws {
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsKw")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")!
let query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]"
@ -586,8 +591,8 @@ class MentatTests: XCTestCase {
}
}
func testTypedValueAsBoolean() {
let mentat = try! Mentat.open()
func testTypedValueAsBoolean() throws {
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsBoolean")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")!
let query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]"
@ -607,8 +612,8 @@ class MentatTests: XCTestCase {
}
}
func testTypedValueAsDouble() {
let mentat = try! Mentat.open()
func testTypedValueAsDouble() throws {
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsDouble")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")!
let query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]"
@ -628,8 +633,8 @@ class MentatTests: XCTestCase {
}
}
func testTypedValueAsDate() {
let mentat = try! Mentat.open()
func testTypedValueAsDate() throws {
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsDate")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")!
let query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]"
@ -654,8 +659,8 @@ class MentatTests: XCTestCase {
}
}
func testTypedValueAsString() {
let mentat = try! Mentat.open()
func testTypedValueAsString() throws {
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsString")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")!
let query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]"
@ -675,8 +680,8 @@ class MentatTests: XCTestCase {
}
}
func testTypedValueAsUuid() {
let mentat = try! Mentat.open()
func testTypedValueAsUuid() throws {
let mentat = try Mentat(namedInMemoryStore: "testTypedValueAsUuid")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")!
let query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]"
@ -697,8 +702,8 @@ class MentatTests: XCTestCase {
}
}
func testValueForAttributeOfEntity() {
let mentat = try! Mentat.open()
func testValueForAttributeOfEntity() throws {
let mentat = try Mentat(namedInMemoryStore: "testValueForAttributeOfEntity")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = report!.entid(forTempId: "a")!
var value: TypedValue? = nil;
@ -707,15 +712,15 @@ class MentatTests: XCTestCase {
assert(value?.asLong() == 25)
}
func testEntidForAttribute() {
let mentat = try! Mentat.open()
func testEntidForAttribute() throws {
let mentat = try Mentat(namedInMemoryStore: "testEntidForAttribute")
let _ = self.populateWithTypesSchema(mentat: mentat)
let entid = mentat.entidForAttribute(attribute: ":foo/long")
assert(entid == 65540)
}
func testMultipleQueries() {
let mentat = try! Mentat.open()
func testMultipleQueries() throws {
let mentat = try Mentat(namedInMemoryStore: "testMultipleQueries")
let _ = self.populateWithTypesSchema(mentat: mentat)
let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]")
@ -739,8 +744,8 @@ class MentatTests: XCTestCase {
}
}
func testNestedQueries() {
let mentat = try! Mentat.open()
func testNestedQueries() throws {
let mentat = try Mentat(namedInMemoryStore: "testNestedQueries")
let _ = self.populateWithTypesSchema(mentat: mentat)
let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]")
let q2 = mentat.query(query: "[:find ?x :where [_ _ ?x]]")
@ -761,14 +766,14 @@ class MentatTests: XCTestCase {
}
}
func test3InProgressTransact() {
let mentat = try! Mentat.open()
func test3InProgressTransact() throws {
let mentat = try Mentat(namedInMemoryStore: "test3InProgressTransact")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
XCTAssertNotNil(report)
}
func testInProgressRollback() {
let mentat = try! Mentat.open()
func testInProgressRollback() throws {
let mentat = try Mentat(namedInMemoryStore: "testInProgressRollback")
let (_, report) = self.populateWithTypesSchema(mentat: mentat)
XCTAssertNotNil(report)
let aEntid = report!.entid(forTempId: "a")!
@ -785,8 +790,8 @@ class MentatTests: XCTestCase {
}
func testInProgressEntityBuilder() {
let mentat = try! Mentat.open()
func testInProgressEntityBuilder() throws {
let mentat = try Mentat(namedInMemoryStore: "testInProgressEntityBuilder")
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let bEntid = dataReport!.entid(forTempId: "b")!
let longEntid = schemaReport!.entid(forTempId: "l")!
@ -850,8 +855,8 @@ class MentatTests: XCTestCase {
})
}
func testEntityBuilderForEntid() {
let mentat = try! Mentat.open()
func testEntityBuilderForEntid() throws {
let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderForEntid")
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let bEntid = dataReport!.entid(forTempId: "b")!
let longEntid = schemaReport!.entid(forTempId: "l")!
@ -915,8 +920,8 @@ class MentatTests: XCTestCase {
})
}
func testEntityBuilderForTempid() {
let mentat = try! Mentat.open()
func testEntityBuilderForTempid() throws {
let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderForTempid")
let (schemaReport, _) = self.populateWithTypesSchema(mentat: mentat)
let longEntid = schemaReport!.entid(forTempId: "l")!
// test that the values are as expected
@ -962,8 +967,8 @@ class MentatTests: XCTestCase {
})
}
func testInProgressBuilderTransact() {
let mentat = try! Mentat.open()
func testInProgressBuilderTransact() throws {
let mentat = try Mentat(namedInMemoryStore: "testInProgressBuilderTransact")
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = dataReport!.entid(forTempId: "a")!
let bEntid = dataReport!.entid(forTempId: "b")!
@ -1018,8 +1023,8 @@ class MentatTests: XCTestCase {
XCTAssertEqual(22, longValue?.asLong())
}
func testEntityBuilderTransact() {
let mentat = try! Mentat.open()
func testEntityBuilderTransact() throws {
let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderTransact")
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let aEntid = dataReport!.entid(forTempId: "a")!
let bEntid = dataReport!.entid(forTempId: "b")!
@ -1074,8 +1079,8 @@ class MentatTests: XCTestCase {
XCTAssertEqual(22, longValue?.asLong())
}
func testEntityBuilderRetract() {
let mentat = try! Mentat.open()
func testEntityBuilderRetract() throws {
let mentat = try Mentat(namedInMemoryStore: "testEntityBuilderRetract")
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let bEntid = dataReport!.entid(forTempId: "b")!
let stringEntid = schemaReport!.entid(forTempId: "s")!
@ -1124,8 +1129,8 @@ class MentatTests: XCTestCase {
})
}
func testInProgressEntityBuilderRetract() {
let mentat = try! Mentat.open()
func testInProgressEntityBuilderRetract() throws {
let mentat = try Mentat(namedInMemoryStore: "testInProgressEntityBuilderRetract")
let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat)
let bEntid = dataReport!.entid(forTempId: "b")!
let stringEntid = schemaReport!.entid(forTempId: "s")!
@ -1174,7 +1179,7 @@ class MentatTests: XCTestCase {
})
}
func testCaching() {
func testCaching() throws {
let query = """
[:find ?district :where
[?neighborhood :neighborhood/name \"Beacon Hill\"]
@ -1182,7 +1187,7 @@ class MentatTests: XCTestCase {
[?d :district/name ?district]]
"""
let mentat = openAndInitializeCitiesStore()
let mentat = try openAndInitializeCitiesStore()
struct QueryTimer {
private var _start: UInt64

View file

@ -10,10 +10,6 @@
#![allow(dead_code)]
use std::borrow::{
Borrow,
};
use std::collections::{
BTreeMap,
};
@ -134,7 +130,6 @@ impl Conn {
/// Prepare the provided SQLite handle for use as a Mentat store. Creates tables but
/// _does not_ write the bootstrap schema. This constructor should only be used by
/// consumers that expect to populate raw transaction data themselves.
pub(crate) fn empty(sqlite: &mut rusqlite::Connection) -> Result<Conn> {
let (tx, db) = db::create_empty_current_version(sqlite)?;
tx.commit()?;
@ -276,7 +271,7 @@ impl Conn {
}
/// Take a SQLite transaction.
fn begin_transaction_with_behavior<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection, behavior: TransactionBehavior) -> Result<InProgress<'m, 'conn>> {
fn begin_transaction_with_behavior<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection, behavior: TransactionBehavior) -> Result<InProgress<'m, 'conn>> {
let tx = sqlite.transaction_with_behavior(behavior)?;
let (current_generation, current_partition_map, current_schema, cache_cow) =
{
@ -305,12 +300,12 @@ impl Conn {
// Helper to avoid passing connections around.
// Make both args mutable so that we can't have parallel access.
pub fn begin_read<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgressRead<'m, 'conn>> {
pub fn begin_read<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgressRead<'m, 'conn>> {
self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Deferred)
.map(|ip| InProgressRead { in_progress: ip })
}
pub fn begin_uncached_read<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgressRead<'m, 'conn>> {
pub fn begin_uncached_read<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgressRead<'m, 'conn>> {
self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Deferred)
.map(|mut ip| {
ip.use_caching(false);
@ -322,20 +317,20 @@ impl Conn {
/// connections from taking immediate or exclusive transactions. This is appropriate for our
/// writes and `InProgress`: it means we are ready to write whenever we want to, and nobody else
/// can start a transaction that's not `DEFERRED`, but we don't need exclusivity yet.
pub fn begin_transaction<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgress<'m, 'conn>> {
pub fn begin_transaction<'m, 'conn>(&'m self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgress<'m, 'conn>> {
self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Immediate)
}
/// Transact entities against the Mentat store, using the given connection and the current
/// metadata.
pub fn transact<B>(&mut self,
pub fn transact<T>(&self,
sqlite: &mut rusqlite::Connection,
transaction: B) -> Result<TxReport> where B: Borrow<str> {
transaction: T) -> Result<TxReport> where T: AsRef<str> {
// Parse outside the SQL transaction. This is a tradeoff: we are limiting the scope of the
// transaction, and indeed we don't even create a SQL transaction if the provided input is
// invalid, but it means SQLite errors won't be found until the parse is complete, and if
// there's a race for the database (don't do that!) we are less likely to win it.
let entities = edn::parse::entities(transaction.borrow())?;
let entities = edn::parse::entities(transaction.as_ref())?;
let mut in_progress = self.begin_transaction(sqlite)?;
let report = in_progress.transact_entities(entities)?;
@ -349,7 +344,7 @@ impl Conn {
/// `cache_action` determines if the attribute should be added or removed from the cache.
/// CacheAction::Add is idempotent - each attribute is only added once.
/// CacheAction::Remove throws an error if the attribute does not currently exist in the cache.
pub fn cache(&mut self,
pub fn cache(&self,
sqlite: &mut rusqlite::Connection,
schema: &Schema,
attribute: &Keyword,
@ -381,11 +376,11 @@ impl Conn {
}
}
pub fn register_observer(&mut self, key: String, observer: Arc<TxObserver>) {
pub fn register_observer(&self, key: String, observer: Arc<TxObserver>) {
self.tx_observer_service.lock().unwrap().register(key, observer);
}
pub fn unregister_observer(&mut self, key: &String) {
pub fn unregister_observer(&self, key: &String) {
self.tx_observer_service.lock().unwrap().deregister(key);
}
}
@ -428,7 +423,7 @@ mod tests {
#[test]
fn test_transact_does_not_collide_existing_entids() {
let mut sqlite = db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
// Let's find out the next ID that'll be allocated. We're going to try to collide with it
// a bit later.
@ -454,7 +449,7 @@ mod tests {
#[test]
fn test_transact_does_not_collide_new_entids() {
let mut sqlite = db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
// Let's find out the next ID that'll be allocated. We're going to try to collide with it.
let next = conn.metadata.lock().expect("metadata").partition_map[":db.part/user"].next_entid();
@ -488,7 +483,7 @@ mod tests {
#[test]
fn test_compound_transact() {
let mut sqlite = db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
let tempid_offset = get_next_entid(&conn);
@ -529,7 +524,7 @@ mod tests {
#[test]
fn test_simple_prepared_query() {
let mut c = db::new_connection("").expect("Couldn't open conn.");
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
let conn = Conn::connect(&mut c).expect("Couldn't open DB.");
conn.transact(&mut c, r#"[
[:db/add "s" :db/ident :foo/boolean]
[:db/add "s" :db/valueType :db.type/boolean]
@ -566,7 +561,7 @@ mod tests {
#[test]
fn test_compound_rollback() {
let mut sqlite = db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
let tempid_offset = get_next_entid(&conn);
@ -616,7 +611,7 @@ mod tests {
#[test]
fn test_transact_errors() {
let mut sqlite = db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
// Good: empty transaction.
let report = conn.transact(&mut sqlite, "[]").unwrap();
@ -661,7 +656,7 @@ mod tests {
#[test]
fn test_add_to_cache_failure_no_attribute() {
let mut sqlite = db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
let _report = conn.transact(&mut sqlite, r#"[
{ :db/ident :foo/bar
:db/valueType :db.type/long },
@ -682,7 +677,7 @@ mod tests {
fn test_lookup_attribute_with_caching() {
let mut sqlite = db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
let _report = conn.transact(&mut sqlite, r#"[
{ :db/ident :foo/bar
:db/valueType :db.type/long },
@ -741,7 +736,7 @@ mod tests {
#[test]
fn test_cache_usage() {
let mut sqlite = db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
let db_ident = (*conn.current_schema()).get_entid(&kw!(:db/ident)).expect("db_ident").0;
let db_type = (*conn.current_schema()).get_entid(&kw!(:db/valueType)).expect("db_ident").0;

View file

@ -176,6 +176,7 @@ pub use mentat_transaction::query::{
pub mod conn;
pub mod query_builder;
pub mod store;
pub mod stores;
pub mod vocabulary;
pub use query_builder::{
@ -199,6 +200,10 @@ pub use store::{
Store,
};
pub use stores::{
Stores,
};
#[cfg(test)]
mod tests {
use edn::symbols::Keyword;

View file

@ -72,17 +72,25 @@ use mentat_transaction::query::{
/// A convenience wrapper around a single SQLite connection and a Conn. This is suitable
/// for applications that don't require complex connection management.
pub struct Store {
conn: Conn,
conn: Arc<Conn>,
sqlite: rusqlite::Connection,
}
impl Store {
/// Create a Store from a connection and Conn.
pub fn new(conn: Arc<Conn>, connection: rusqlite::Connection) -> Result<Store> {
Ok(Store {
conn: conn,
sqlite: connection,
})
}
/// Open a store at the supplied path, ensuring that it includes the bootstrap schema.
pub fn open(path: &str) -> Result<Store> {
let mut connection = ::new_connection(path)?;
let conn = Conn::connect(&mut connection)?;
Ok(Store {
conn: conn,
conn: Arc::new(conn),
sqlite: connection,
})
}
@ -104,14 +112,33 @@ impl Store {
let mut connection = ::new_connection_with_key(path, encryption_key)?;
let conn = Conn::connect(&mut connection)?;
Ok(Store {
conn: conn,
conn: Arc::new(conn),
sqlite: connection,
})
}
/// Change the key for a database that was opened using `open_with_key` (using `PRAGMA
/// rekey`). Fails unless linked against sqlcipher (or something else that supports the Sqlite
/// Encryption Extension).
/// Variant of `open_empty` that allows a key (for encryption/decryption) to
/// be supplied. Fails unless linked against sqlcipher (or something else
/// that supports the Sqlite Encryption Extension).
pub fn open_empty_with_key(path: &str, encryption_key: &str) -> Result<Store> {
if !path.is_empty() {
if Path::new(path).exists() {
bail!(MentatError::PathAlreadyExists(path.to_string()));
}
}
let mut connection = ::new_connection_with_key(path, encryption_key)?;
let conn = Conn::empty(&mut connection)?;
Ok(Store {
conn: Arc::new(conn),
sqlite: connection,
})
}
/// Change the key for a database that was opened using `open_with_key` or
/// `open_empty_with_key` (using `PRAGMA rekey`). Fails unless linked
/// against sqlcipher (or something else that supports the Sqlite Encryption
/// Extension).
pub fn change_encryption_key(&mut self, new_encryption_key: &str) -> Result<()> {
::change_encryption_key(&self.sqlite, new_encryption_key)?;
Ok(())
@ -131,12 +158,12 @@ impl Store {
}
impl Store {
pub fn dismantle(self) -> (rusqlite::Connection, Conn) {
pub fn dismantle(self) -> (rusqlite::Connection, Arc<Conn>) {
(self.sqlite, self.conn)
}
pub fn conn(&self) -> &Conn {
&self.conn
pub fn conn(&self) -> Arc<Conn> {
self.conn.clone()
}
pub fn begin_read<'m>(&'m mut self) -> Result<InProgressRead<'m, 'm>> {

429
src/stores.rs Normal file
View 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));
}
}

View file

@ -69,7 +69,7 @@ fn test_entity_builder_bogus_entids() {
// Now try to add them to a real store.
let mut sqlite = mentat_db::db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully");
// This should fail: unrecognized entid.
@ -84,7 +84,7 @@ fn test_entity_builder_bogus_entids() {
#[test]
fn test_in_progress_builder() {
let mut sqlite = mentat_db::db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
// Give ourselves a schema to work with!
conn.transact(&mut sqlite, r#"[
@ -116,7 +116,7 @@ fn test_in_progress_builder() {
#[test]
fn test_entity_builder() {
let mut sqlite = mentat_db::db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
let foo_one = kw!(:foo/one);
let foo_many = kw!(:foo/many);

View file

@ -254,7 +254,7 @@ fn test_instants_and_uuids() {
let start = Utc::now() + FixedOffset::west(60 * 60);
let mut c = new_connection("").expect("Couldn't open conn.");
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
let conn = Conn::connect(&mut c).expect("Couldn't open DB.");
conn.transact(&mut c, r#"[
[:db/add "s" :db/ident :foo/uuid]
[:db/add "s" :db/valueType :db.type/uuid]
@ -291,7 +291,7 @@ fn test_instants_and_uuids() {
#[test]
fn test_tx() {
let mut c = new_connection("").expect("Couldn't open conn.");
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
let conn = Conn::connect(&mut c).expect("Couldn't open DB.");
conn.transact(&mut c, r#"[
[:db/add "s" :db/ident :foo/uuid]
[:db/add "s" :db/valueType :db.type/uuid]
@ -324,7 +324,7 @@ fn test_tx() {
#[test]
fn test_tx_as_input() {
let mut c = new_connection("").expect("Couldn't open conn.");
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
let conn = Conn::connect(&mut c).expect("Couldn't open DB.");
conn.transact(&mut c, r#"[
[:db/add "s" :db/ident :foo/uuid]
[:db/add "s" :db/valueType :db.type/uuid]
@ -361,7 +361,7 @@ fn test_tx_as_input() {
#[test]
fn test_fulltext() {
let mut c = new_connection("").expect("Couldn't open conn.");
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
let conn = Conn::connect(&mut c).expect("Couldn't open DB.");
conn.transact(&mut c, r#"[
[:db/add "a" :db/ident :foo/term]
@ -467,7 +467,7 @@ fn test_fulltext() {
#[test]
fn test_instant_range_query() {
let mut c = new_connection("").expect("Couldn't open conn.");
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
let conn = Conn::connect(&mut c).expect("Couldn't open DB.");
conn.transact(&mut c, r#"[
[:db/add "a" :db/ident :foo/date]
@ -503,7 +503,7 @@ fn test_instant_range_query() {
#[test]
fn test_lookup() {
let mut c = new_connection("").expect("Couldn't open conn.");
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
let conn = Conn::connect(&mut c).expect("Couldn't open DB.");
conn.transact(&mut c, r#"[
[:db/add "a" :db/ident :foo/date]
@ -656,7 +656,7 @@ fn test_aggregates_type_handling() {
#[test]
fn test_type_reqs() {
let mut c = new_connection("").expect("Couldn't open conn.");
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
let conn = Conn::connect(&mut c).expect("Couldn't open DB.");
conn.transact(&mut c, r#"[
{:db/ident :test/boolean :db/valueType :db.type/boolean :db/cardinality :db.cardinality/one}
@ -1526,3 +1526,63 @@ fn test_encrypted() {
// so the specific test we use doesn't matter that much.
run_tx_data_test(Store::open_with_key("", "secret").expect("opened"));
}
#[test]
fn test_conn_cross_thread() {
let file = "file:memdb?mode=memory&cache=shared";
let mut sqlite = mentat_db::db::new_connection(file).expect("Couldn't open in-memory db");
let conn = Conn::connect(&mut sqlite).expect("to connect");
conn.transact(&mut sqlite, r#"[
[:db/add "a" :db/ident :foo/term]
[:db/add "a" :db/valueType :db.type/string]
[:db/add "a" :db/fulltext false]
[:db/add "a" :db/cardinality :db.cardinality/many]
]"#).unwrap();
let _tx1 = conn.transact(&mut sqlite, r#"[
[:db/add "e" :foo/term "1"]
]"#).expect("tx1 to apply");
let _tx2 = conn.transact(&mut sqlite, r#"[
[:db/add "e" :foo/term "2"]
]"#).expect("tx2 to apply");
use std::sync::Arc;
use std::sync::mpsc::channel;
let conn = Arc::new(conn);
let (tx1, rx1) = channel();
let (txs, rxs) = channel();
let (tx2, rx2) = channel();
std::thread::spawn(move || {
let shared_conn: Arc<Conn> = rx1.recv().expect("rx1");
let mut sqlite1 = mentat_db::db::new_connection(file).expect("Couldn't open in-memory db");
shared_conn.transact(&mut sqlite1, r#"[
[:db/add "a" :db/ident :foo/bar]
[:db/add "a" :db/valueType :db.type/long]
[:db/add "a" :db/cardinality :db.cardinality/many]
]"#).unwrap();
txs.send(()).expect("to sync");
});
tx1.send(conn.clone()).expect("tx1");
rxs.recv().expect("to sync");
std::thread::spawn(move || {
let shared_conn: Arc<Conn> = rx2.recv().expect("rx1");
let mut sqlite2 = mentat_db::db::new_connection(file).expect("Couldn't open in-memory db");
assert_eq!(None, shared_conn.current_schema().get_entid(&Keyword::namespaced("foo", "bar")));
shared_conn.q_once(&mut sqlite2, "[:find ?e :where [?e _ _]]", None).expect("to prepare");
});
tx2.send(conn.clone()).expect("tx2");
}

View file

@ -101,7 +101,7 @@ mod tests {
#[test]
fn test_reader() {
let mut c = new_connection("").expect("Couldn't open conn.");
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
let conn = Conn::connect(&mut c).expect("Couldn't open DB.");
{
let db_tx = c.transaction().expect("db tx");
// Don't inspect the bootstrap transaction, but we'd like to see it's there.

View file

@ -186,7 +186,7 @@ fn test_add_vocab() {
let foo_v1_b = vocabulary::Definition::new(kw!(:org.mozilla/foo), 1, bar_and_baz.clone());
let mut sqlite = mentat_db::db::new_connection("").unwrap();
let mut conn = Conn::connect(&mut sqlite).unwrap();
let conn = Conn::connect(&mut sqlite).unwrap();
let foo_version_query = r#"[:find [?version ?aa]
:where

View file

@ -187,7 +187,7 @@ impl InputReader {
fn read_line(&mut self, prompt: &str) -> UserAction {
match self.interface {
Some(ref mut r) => {
r.set_prompt(prompt);
let _ = r.set_prompt(prompt);
r.read_line().ok().map_or(UserAction::Quit, |line|
match line {
ReadResult::Input(s) => UserAction::TextInput(s),