diff --git a/sdks/swift/Mentat/Mentat/Mentat.swift b/sdks/swift/Mentat/Mentat/Mentat.swift index e734c72f..06a30b76 100644 --- a/sdks/swift/Mentat/Mentat/Mentat.swift +++ b/sdks/swift/Mentat/Mentat/Mentat.swift @@ -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. diff --git a/sdks/swift/Mentat/Mentat/Transact/InProgress.swift b/sdks/swift/Mentat/Mentat/Transact/InProgress.swift index e443bb40..77b5d496 100644 --- a/sdks/swift/Mentat/Mentat/Transact/InProgress.swift +++ b/sdks/swift/Mentat/Mentat/Transact/InProgress.swift @@ -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. diff --git a/sdks/swift/Mentat/Mentat/store.h b/sdks/swift/Mentat/Mentat/store.h index 5476adfe..04fa9a1f 100644 --- a/sdks/swift/Mentat/Mentat/store.h +++ b/sdks/swift/Mentat/Mentat/store.h @@ -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); diff --git a/sdks/swift/Mentat/MentatTests/MentatTests.swift b/sdks/swift/Mentat/MentatTests/MentatTests.swift index 026ffff7..f33df12c 100644 --- a/sdks/swift/Mentat/MentatTests/MentatTests.swift +++ b/sdks/swift/Mentat/MentatTests/MentatTests.swift @@ -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 }