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:
parent
dddcd1c17f
commit
312d3bf4e0
4 changed files with 107 additions and 2 deletions
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue