Wrap caching FFI functions in Swift library.

`CacheDirection` enum is used only on the Swift side to provide a usable interface. FFI calls are more explicit.

Tests ensure that a cached query is faster than the uncached one.
This commit is contained in:
Emily Toop 2018-04-27 11:26:24 +01:00
parent dddcd1c17f
commit 312d3bf4e0
4 changed files with 107 additions and 2 deletions

View file

@ -29,6 +29,12 @@ protocol Observable {
func unregister(key: String)
}
enum CacheDirection {
case forward;
case reverse;
case both;
}
/**
The primary class for accessing Mentat's API.
This class provides all of the basic API that can be found in Mentat's Store struct.
@ -56,11 +62,36 @@ class Mentat: RustObject {
self.init(raw: store_open(storeURI))
}
/**
Add an attribute to the cache. The {@link CacheDirection} determines how that attribute can be
looked up.
- Parameter attribute: The attribute to cache
- Parameter direction: The direction the attribute should be keyed.
`FORWARD` caches values for an attribute keyed by entity
(i.e. find values and entities that have this attribute, or find values of attribute for an entity)
`REVERSE` caches entities for an attribute keyed by value.
(i.e. find entities that have a particular value for an attribute).
`BOTH` adds an attribute such that it is cached in both directions.
- Throws: `ResultError.error` if an error occured while trying to cache the attribute.
*/
func cache(attribute: String, direction: CacheDirection) throws {
switch direction {
case .forward:
try store_cache_attribute_forward(self.raw, attribute).pointee.tryUnwrap()
case .reverse:
try store_cache_attribute_reverse(self.raw, attribute).pointee.tryUnwrap()
case .both:
try store_cache_attribute_bi_directional(self.raw, attribute).pointee.tryUnwrap()
}
}
/**
Simple transact of an EDN string.
- Parameter transaction: The string, as EDN, to be transacted
- Throws: `MentatError` if the an error occured during the transaction, or the TxReport is nil.
- Throws: `ResultError.error` if the an error occured during the transaction, or the TxReport is nil.
- Returns: The `TxReport` of the completed transaction
*/
@ -99,6 +130,8 @@ class Mentat: RustObject {
Creates a new transaction (`InProgress`) and returns an `EntityBuilder` for the entity with `entid`
for that transaction.
- Parameter entid: The `Entid` for this entity.
- Throws: `ResultError.error` if the creation of the transaction fails.
- Throws: `ResultError.empty` if no `EntityBuilder` is created.
@ -113,6 +146,8 @@ class Mentat: RustObject {
Creates a new transaction (`InProgress`) and returns an `EntityBuilder` for a new entity with `tempId`
for that transaction.
- Parameter tempId: The temporary identifier for this entity.
- Throws: `ResultError.error` if the creation of the transaction fails.
- Throws: `ResultError.empty` if no `EntityBuilder` is created.

View file

@ -98,7 +98,7 @@ class InProgress: OptionalRustObject {
/**
Creates an `EntityBuilder` using this `InProgress` for the entity with `entid`.
- Parameter tempId: The `Entid` for this entity.
- Parameter entid: The `Entid` for this entity.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the `InProgress`
has already been committed, or converted into a Builder.

View file

@ -114,6 +114,11 @@ void in_progress_destroy(struct InProgress* _Nullable obj);
void in_progress_builder_destroy(struct InProgressBuilder* _Nullable obj);
void entity_builder_destroy(struct EntityBuilder* _Nullable obj);
// caching
struct Result*_Nonnull store_cache_attribute_forward(struct Store*_Nonnull store, const char* _Nonnull attribute);
struct Result*_Nonnull store_cache_attribute_reverse(struct Store*_Nonnull store, const char* _Nonnull attribute);
struct Result*_Nonnull store_cache_attribute_bi_directional(struct Store*_Nonnull store, const char* _Nonnull attribute);
// transact
struct Result*_Nonnull store_transact(struct Store*_Nonnull store, const char* _Nonnull transaction);
const int64_t* _Nullable tx_report_entity_for_temp_id(const struct TxReport* _Nonnull report, const char* _Nonnull tempid);

View file

@ -1175,5 +1175,70 @@ class MentatTests: XCTestCase {
})
}
func testCaching() {
let query = """
[:find ?district :where
[?neighborhood :neighborhood/name \"Beacon Hill\"]
[?neighborhood :neighborhood/district ?d]
[?d :district/name ?district]]
"""
let mentat = openAndInitializeCitiesStore()
struct QueryTimer {
private var _start: UInt64
private var _end: UInt64
init() {
self._start = 0
self._end = 0
}
private func currentTimeNanos() -> UInt64 {
var info = mach_timebase_info()
guard mach_timebase_info(&info) == KERN_SUCCESS else { return 0 }
let currentTime = mach_absolute_time()
return currentTime * UInt64(info.numer) / UInt64(info.denom)
}
mutating func start() {
self._start = self.currentTimeNanos()
}
mutating func end() {
self._end = self.currentTimeNanos()
}
func duration() -> UInt64 {
return self._end - self._start
}
}
var uncachedTimer = QueryTimer()
uncachedTimer.start()
XCTAssertNoThrow(try mentat.query(query: query).run { (result) in
uncachedTimer.end()
XCTAssertNotNil(result)
})
XCTAssertNoThrow(try mentat.cache(attribute: ":neighborhood/name", direction: CacheDirection.reverse))
XCTAssertNoThrow(try mentat.cache(attribute: ":neighborhood/district", direction: CacheDirection.forward))
var cachedTimer = QueryTimer()
cachedTimer.start()
XCTAssertNoThrow(try mentat.query(query: query).run { (result) in
cachedTimer.end()
XCTAssertNotNil(result)
})
let timingDifference = uncachedTimer.duration() - cachedTimer.duration()
print("Cached query is \(timingDifference) nanoseconds faster than the uncached query")
XCTAssertLessThan(cachedTimer.duration(), uncachedTimer.duration())
}
// TODO: Add tests for transaction observation
}