From 0dfb712ef7afa75cba85f05f245e39bba0e9d0dc Mon Sep 17 00:00:00 2001 From: Emily Toop Date: Thu, 12 Apr 2018 11:23:11 +0100 Subject: [PATCH] Add tests for FFI functions Improve API --- .../Mentat/Mentat.xcodeproj/project.pbxproj | 34 +- .../swift/Mentat/Mentat/Core/TypedValue.swift | 7 +- sdks/swift/Mentat/Mentat/Mentat.swift | 98 ++- sdks/swift/Mentat/Mentat/Query/Query.swift | 83 +- .../Mentat/Mentat/Query/TupleResult.swift | 40 +- .../swift/Mentat/Mentat/Rust/RustObject.swift | 2 +- .../Mentat/Mentat/Transact/TxReport.swift | 24 +- sdks/swift/Mentat/Mentat/store.h | 41 +- .../Mentat/MentatTests/MentatTests.swift | 793 +++++++++++++++++- 9 files changed, 1001 insertions(+), 121 deletions(-) diff --git a/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj b/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj index 2af90b65..1cad4c9d 100644 --- a/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj +++ b/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj @@ -17,13 +17,13 @@ 7BDB96B32077C38E009D0651 /* RustObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AA2077C38E009D0651 /* RustObject.swift */; }; 7BDB96B42077C38E009D0651 /* OptionalRustObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AB2077C38E009D0651 /* OptionalRustObject.swift */; }; 7BDB96B52077C38E009D0651 /* TupleResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AC2077C38E009D0651 /* TupleResult.swift */; }; - 7BDB96B62077C38E009D0651 /* TxReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AD2077C38E009D0651 /* TxReport.swift */; }; 7BDB96B72077C38E009D0651 /* TypedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AE2077C38E009D0651 /* TypedValue.swift */; }; - 7BDB96C02077CD7A009D0651 /* libmentat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BDB96BF2077CD7A009D0651 /* libmentat.a */; }; 7BDB96C22077CD98009D0651 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BDB96C12077CD98009D0651 /* libresolv.tbd */; }; 7BDB96C62077D347009D0651 /* Date+Int64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96C52077D346009D0651 /* Date+Int64.swift */; }; 7BDB96C9207B735A009D0651 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 7BDB96C8207B735A009D0651 /* fixtures */; }; 7BDB96CC207B7684009D0651 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96CB207B7684009D0651 /* Errors.swift */; }; + 7BEB7D25207BE3BF000369AD /* libtoodle.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BEB7D21207BDDEF000369AD /* libtoodle.a */; }; + 7BEB7D2C207D03DA000369AD /* TxReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEB7D2B207D03DA000369AD /* TxReport.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -50,7 +50,6 @@ 7BDB96AA2077C38E009D0651 /* RustObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RustObject.swift; sourceTree = ""; }; 7BDB96AB2077C38E009D0651 /* OptionalRustObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalRustObject.swift; sourceTree = ""; }; 7BDB96AC2077C38E009D0651 /* TupleResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TupleResult.swift; sourceTree = ""; }; - 7BDB96AD2077C38E009D0651 /* TxReport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TxReport.swift; sourceTree = ""; }; 7BDB96AE2077C38E009D0651 /* TypedValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypedValue.swift; sourceTree = ""; }; 7BDB96BF2077CD7A009D0651 /* libmentat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmentat.a; path = ../../../target/universal/release/libmentat.a; sourceTree = ""; }; 7BDB96C12077CD98009D0651 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; @@ -58,6 +57,9 @@ 7BDB96C52077D346009D0651 /* Date+Int64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Int64.swift"; sourceTree = ""; }; 7BDB96C8207B735A009D0651 /* fixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; name = fixtures; path = ../../../../fixtures; sourceTree = ""; }; 7BDB96CB207B7684009D0651 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 7BEB7D21207BDDEF000369AD /* libtoodle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtoodle.a; path = ../../../target/universal/release/libtoodle.a; sourceTree = ""; }; + 7BEB7D23207BE2AF000369AD /* libmentat_ffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmentat_ffi.a; path = ../../../target/universal/release/libmentat_ffi.a; sourceTree = ""; }; + 7BEB7D2B207D03DA000369AD /* TxReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxReport.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -66,7 +68,7 @@ buildActionMask = 2147483647; files = ( 7BDB96C22077CD98009D0651 /* libresolv.tbd in Frameworks */, - 7BDB96C02077CD7A009D0651 /* libmentat.a in Frameworks */, + 7BEB7D25207BE3BF000369AD /* libtoodle.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -106,11 +108,11 @@ 7BDB96CA207B7672009D0651 /* Errors */, 7BDB96C42077D346009D0651 /* Extensions */, 7BDB96BA2077C42B009D0651 /* Core */, + 7BDB96A42077C301009D0651 /* Query */, 7BDB96B92077C403009D0651 /* Rust */, 7BDB96A82077C38E009D0651 /* store.h */, 7BDB96A72077C38D009D0651 /* Mentat.swift */, - 7BDB96A42077C301009D0651 /* Query */, - 7BDB96B82077C3B2009D0651 /* Transact */, + 7BEB7D26207BE5BB000369AD /* Transact */, 7BDB968D2077C299009D0651 /* Mentat.h */, 7BDB968E2077C299009D0651 /* Info.plist */, 7BDB96C32077D090009D0651 /* module.map */, @@ -138,14 +140,6 @@ path = Query; sourceTree = ""; }; - 7BDB96B82077C3B2009D0651 /* Transact */ = { - isa = PBXGroup; - children = ( - 7BDB96AD2077C38E009D0651 /* TxReport.swift */, - ); - path = Transact; - sourceTree = ""; - }; 7BDB96B92077C403009D0651 /* Rust */ = { isa = PBXGroup; children = ( @@ -166,6 +160,8 @@ 7BDB96BE2077CD7A009D0651 /* Frameworks */ = { isa = PBXGroup; children = ( + 7BEB7D23207BE2AF000369AD /* libmentat_ffi.a */, + 7BEB7D21207BDDEF000369AD /* libtoodle.a */, 7BDB96C12077CD98009D0651 /* libresolv.tbd */, 7BDB96BF2077CD7A009D0651 /* libmentat.a */, ); @@ -188,6 +184,14 @@ path = Errors; sourceTree = ""; }; + 7BEB7D26207BE5BB000369AD /* Transact */ = { + isa = PBXGroup; + children = ( + 7BEB7D2B207D03DA000369AD /* TxReport.swift */, + ); + path = Transact; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -300,8 +304,8 @@ buildActionMask = 2147483647; files = ( 7BDB96B32077C38E009D0651 /* RustObject.swift in Sources */, - 7BDB96B62077C38E009D0651 /* TxReport.swift in Sources */, 7BDB96C62077D347009D0651 /* Date+Int64.swift in Sources */, + 7BEB7D2C207D03DA000369AD /* TxReport.swift in Sources */, 7BDB96B42077C38E009D0651 /* OptionalRustObject.swift in Sources */, 7BDB96B22077C38E009D0651 /* RelResult.swift in Sources */, 7BDB96AF2077C38E009D0651 /* Query.swift in Sources */, diff --git a/sdks/swift/Mentat/Mentat/Core/TypedValue.swift b/sdks/swift/Mentat/Mentat/Core/TypedValue.swift index cac861c2..59a211f5 100644 --- a/sdks/swift/Mentat/Mentat/Core/TypedValue.swift +++ b/sdks/swift/Mentat/Mentat/Core/TypedValue.swift @@ -7,6 +7,10 @@ import Mentatlib class TypedValue: OptionalRustObject { + var valueType: ValueType { + return typed_value_value_type(self.raw!) + } + func asLong() -> Int64 { defer { self.raw = nil @@ -32,7 +36,8 @@ class TypedValue: OptionalRustObject { defer { self.raw = nil } - return typed_value_as_boolean(self.raw!) == 0 ? false : true + let v = typed_value_as_boolean(self.raw!) + return v > 0 } func asDouble() -> Double { diff --git a/sdks/swift/Mentat/Mentat/Mentat.swift b/sdks/swift/Mentat/Mentat/Mentat.swift index f0489c02..acda583d 100644 --- a/sdks/swift/Mentat/Mentat/Mentat.swift +++ b/sdks/swift/Mentat/Mentat/Mentat.swift @@ -8,7 +8,7 @@ import Mentatlib protocol Observing { // define functions for store observation - func transactionDidOccur(key: String, reports: [TxReport]) + func transactionDidOccur(key: String, reports: [TransactionChange]) } protocol Observable { @@ -27,16 +27,15 @@ class Mentat: RustObject { self.init(raw: store_open(storeURI)) } - func transact(transaction: String) throws -> Bool { + func transact(transaction: String) throws -> TxReport { let result = store_transact(self.raw, transaction).pointee - guard let _ = result.ok else { + guard let success = result.ok else { if let error = result.err { throw MentatError(message: String(cString: error)) } throw MentatError(message: "Unspecified Error") } - print("Successfull") - return true + return TxReport(raw: success) } func entidForAttribute(attribute: String) -> Int64 { @@ -70,6 +69,78 @@ class Mentat: RustObject { return TypedValue(raw: success) } + func set(date value: Date, forAttribute attribute: String, onEntity entid: Int64) throws { + let result = store_set_timestamp_for_attribute_on_entid(self.intoRaw(), entid, attribute, value.toMicroseconds()) + guard let err = result.pointee.err else { + return + } + + throw QueryError.executionFailed(message: String(cString: err)) + } + + func set(long value: Int64, forAttribute attribute: String, onEntity entid: Int64) throws { + let result = store_set_long_for_attribute_on_entid(self.intoRaw(), entid, attribute, value) + guard let err = result.pointee.err else { + return + } + + throw QueryError.executionFailed(message: String(cString: err)) + } + + func set(reference value: Int64, forAttribute attribute: String, onEntity entid: Int64) throws { + let result = store_set_entid_for_attribute_on_entid(self.intoRaw(), entid, attribute, value) + guard let err = result.pointee.err else { + return + } + + throw QueryError.executionFailed(message: String(cString: err)) + } + + func setKeywordReference(value: String, forAttribute attribute: String, onEntity entid: Int64) throws { + let result = store_set_kw_ref_for_attribute_on_entid(self.intoRaw(), entid, attribute, value) + guard let err = result.pointee.err else { + return + } + + throw QueryError.executionFailed(message: String(cString: err)) + } + + func set(boolean value: Bool, forAttribute attribute: String, onEntity entid: Int64) throws { + let result = store_set_boolean_for_attribute_on_entid(self.intoRaw(), entid, attribute, value ? 1 : 0) + guard let err = result.pointee.err else { + return + } + + throw QueryError.executionFailed(message: String(cString: err)) + } + + func set(double value: Double, forAttribute attribute: String, onEntity entid: Int64) throws { + let result = store_set_double_for_attribute_on_entid(self.intoRaw(), entid, attribute, value) + guard let err = result.pointee.err else { + return + } + + throw QueryError.executionFailed(message: String(cString: err)) + } + + func set(string value: String, forAttribute attribute: String, onEntity entid: Int64) throws { + let result = store_set_string_for_attribute_on_entid(self.intoRaw(), entid, attribute, value) + guard let err = result.pointee.err else { + return + } + + throw QueryError.executionFailed(message: String(cString: err)) + } + + func set(uuid value: UUID, forAttribute attribute: String, onEntity entid: Int64) throws { + let result = store_set_uuid_for_attribute_on_entid(self.intoRaw(), entid, attribute, value.uuidString) + guard let err = result.pointee.err else { + return + } + + throw QueryError.executionFailed(message: String(cString: err)) + } + override func cleanup(pointer: OpaquePointer) { store_destroy(pointer) } @@ -99,20 +170,19 @@ extension Mentat: Observable { } } -private func transactionObserverCallback(key: UnsafePointer, reports: UnsafePointer) { +private func transactionObserverCallback(key: UnsafePointer, reports: UnsafePointer) { // needs to be done in the same thread as the calling thread otherwise the TxReportList might be released before // we can reference it. let key = String(cString: key) guard let observer = Mentat.observers[key] else { return } - let len = Int(reports.pointee.len) - var txReports = [TxReport]() - for i in 0.. { - case error(Error) - case success(T) -} - class Query: OptionalRustObject { - func bind(varName: String, toInt value: Int32) throws { - guard let r = self.raw else { - throw QueryError.builderConsumed - } - query_builder_bind_int(r, varName, value) - } - - func bind(varName: String, toLong value: Int64) throws { + func bind(varName: String, toLong value: Int64) throws -> Query { guard let r = self.raw else { throw QueryError.builderConsumed } query_builder_bind_long(r, varName, value) + return self } - func bind(varName: String, toReference value: Int64) throws { + func bind(varName: String, toReference value: Int64) throws -> Query { guard let r = self.raw else { throw QueryError.builderConsumed } query_builder_bind_ref(r, varName, value) + return self } - func bind(varName: String, toReference value: String) throws { + func bind(varName: String, toReference value: String) throws -> Query { guard let r = self.raw else { throw QueryError.builderConsumed } query_builder_bind_ref_kw(r, varName, value) + return self } - func bind(varName: String, toKeyword value: String) throws { + func bind(varName: String, toKeyword value: String) throws -> Query { guard let r = self.raw else { throw QueryError.builderConsumed } query_builder_bind_kw(r, varName, value) + return self } - func bind(varName: String, toBoolean value: Bool) throws { + func bind(varName: String, toBoolean value: Bool) throws -> Query { guard let r = self.raw else { throw QueryError.builderConsumed } query_builder_bind_boolean(r, varName, value ? 1 : 0) + return self } - func bind(varName: String, toDouble value: Double) throws { + func bind(varName: String, toDouble value: Double) throws -> Query { guard let r = self.raw else { throw QueryError.builderConsumed } query_builder_bind_double(r, varName, value) + return self } - func bind(varName: String, toDate value: Date) throws { + func bind(varName: String, toDate value: Date) throws -> Query { guard let r = self.raw else { throw QueryError.builderConsumed } query_builder_bind_timestamp(r, varName, value.toMicroseconds()) + return self } - func bind(varName: String, toString value: String) throws { + func bind(varName: String, toString value: String) throws -> Query { guard let r = self.raw else { throw QueryError.builderConsumed } query_builder_bind_string(r, varName, value) + return self } - func bind(varName: String, toUuid value: UUID) throws { + func bind(varName: String, toUuid value: UUID) throws -> Query { guard let r = self.raw else { throw QueryError.builderConsumed } query_builder_bind_uuid(r, varName, value.uuidString) + return self } - func executeMap(map: @escaping (QueryResult) -> Void) throws { + func executeMap(map: @escaping (TupleResult?, QueryError?) -> Void) throws { guard let r = self.raw else { throw QueryError.builderConsumed } @@ -93,7 +90,7 @@ class Query: OptionalRustObject { if let err = result.pointee.err { let message = String(cString: err) - map(QueryResult.error(QueryError.executionFailed(message: message))) + map(nil, QueryError.executionFailed(message: message)) return } guard let rowsPtr = result.pointee.ok else { @@ -101,12 +98,12 @@ class Query: OptionalRustObject { } let rows = RelResult(raw: rowsPtr) for row in rows { - map(QueryResult.success(row)) + map(row, nil) } } } - func execute(callback: @escaping (QueryResult) -> Void) throws { + func execute(callback: @escaping (RelResult?, QueryError?) -> Void) throws { guard let r = self.raw else { throw QueryError.builderConsumed } @@ -117,18 +114,18 @@ class Query: OptionalRustObject { if let err = result.pointee.err { let message = String(cString: err) - callback(QueryResult.error(QueryError.executionFailed(message: message))) + callback(nil, QueryError.executionFailed(message: message)) return } guard let results = result.pointee.ok else { - callback(QueryResult.success(nil)) + callback(nil, nil) return } - callback(QueryResult.success(RelResult(raw: results))) + callback(RelResult(raw: results), nil) } } - func executeScalar(callback: @escaping (QueryResult) -> Void) throws { + func executeScalar(callback: @escaping (TypedValue?, QueryError?) -> Void) throws { guard let r = self.raw else { throw QueryError.builderConsumed } @@ -139,17 +136,17 @@ class Query: OptionalRustObject { if let err = result.pointee.err { let message = String(cString: err) - callback(QueryResult.error(QueryError.executionFailed(message: message))) + callback(nil, QueryError.executionFailed(message: message)) } guard let results = result.pointee.ok else { - callback(QueryResult.success(nil)) + callback(nil, nil) return } - callback(QueryResult.success(TypedValue(raw: OpaquePointer(results)))) + callback(TypedValue(raw: OpaquePointer(results)), nil) } } - func executeColl(callback: @escaping (QueryResult) -> Void) throws { + func executeColl(callback: @escaping (ColResult?, QueryError?) -> Void) throws { guard let r = self.raw else { throw QueryError.builderConsumed } @@ -160,17 +157,17 @@ class Query: OptionalRustObject { if let err = result.pointee.err { let message = String(cString: err) - callback(QueryResult.error(QueryError.executionFailed(message: message))) + callback(nil, QueryError.executionFailed(message: message)) } guard let results = result.pointee.ok else { - callback(QueryResult.success(nil)) + callback(nil, nil) return } - callback(QueryResult.success(ColResult(raw: results))) + callback(ColResult(raw: results), nil) } } - func executeCollMap(map: @escaping (QueryResult) -> Void) throws { + func executeCollMap(map: @escaping (TypedValue?, QueryError?) -> Void) throws { guard let r = self.raw else { throw QueryError.builderConsumed } @@ -181,7 +178,7 @@ class Query: OptionalRustObject { if let err = result.pointee.err { let message = String(cString: err) - map(QueryResult.error(QueryError.executionFailed(message: message))) + map(nil, QueryError.executionFailed(message: message)) return } guard let cols = result.pointee.ok else { @@ -189,12 +186,12 @@ class Query: OptionalRustObject { } let rowList = ColResult(raw: cols) for row in rowList { - map(QueryResult.success(row)) + map(row, nil) } } } - func executeTuple(callback: @escaping (QueryResult) -> Void) throws { + func executeTuple(callback: @escaping (TupleResult?, QueryError?) -> Void) throws { guard let r = self.raw else { throw QueryError.builderConsumed } @@ -205,13 +202,13 @@ class Query: OptionalRustObject { if let err = result.pointee.err { let message = String(cString: err) - callback(QueryResult.error(QueryError.executionFailed(message: message))) + callback(nil, QueryError.executionFailed(message: message)) } guard let results = result.pointee.ok else { - callback(QueryResult.success(nil)) + callback(nil, nil) return } - callback(QueryResult.success(TupleResult(raw: OpaquePointer(results)))) + callback(TupleResult(raw: OpaquePointer(results)), nil) } } diff --git a/sdks/swift/Mentat/Mentat/Query/TupleResult.swift b/sdks/swift/Mentat/Mentat/Query/TupleResult.swift index 66cef7aa..0c8f7235 100644 --- a/sdks/swift/Mentat/Mentat/Query/TupleResult.swift +++ b/sdks/swift/Mentat/Mentat/Query/TupleResult.swift @@ -7,40 +7,40 @@ import Mentatlib class TupleResult: OptionalRustObject { - func get(index: Int32) -> TypedValue { - return TypedValue(raw: value_at_index(self.raw!, index)) + func get(index: Int) -> TypedValue { + return TypedValue(raw: value_at_index(self.raw!, Int32(index))) } - func asLong(index: Int32) -> Int64 { - return value_at_index_as_long(self.raw!, index) + func asLong(index: Int) -> Int64 { + return value_at_index_as_long(self.raw!, Int32(index)) } - func asEntid(index: Int32) -> Int64 { - return value_at_index_as_entid(self.raw!, index) + func asEntid(index: Int) -> Int64 { + return value_at_index_as_entid(self.raw!, Int32(index)) } - func asKeyword(index: Int32) -> String { - return String(cString: value_at_index_as_kw(self.raw!, index)) + func asKeyword(index: Int) -> String { + return String(cString: value_at_index_as_kw(self.raw!, Int32(index))) } - func asBool(index: Int32) -> Bool { - return value_at_index_as_boolean(self.raw!, index) == 0 ? false : true + func asBool(index: Int) -> Bool { + return value_at_index_as_boolean(self.raw!, Int32(index)) == 0 ? false : true } - func asDouble(index: Int32) -> Double { - return value_at_index_as_double(self.raw!, index) + func asDouble(index: Int) -> Double { + return value_at_index_as_double(self.raw!, Int32(index)) } - func asDate(index: Int32) -> Date { - return Date(timeIntervalSince1970: TimeInterval(value_at_index_as_timestamp(self.raw!, index))) + func asDate(index: Int) -> Date { + return Date(timeIntervalSince1970: TimeInterval(value_at_index_as_timestamp(self.raw!, Int32(index)))) } - func asString(index: Int32) -> String { - return String(cString: value_at_index_as_string(self.raw!, index)) + func asString(index: Int) -> String { + return String(cString: value_at_index_as_string(self.raw!, Int32(index))) } - func asUUID(index: Int32) -> UUID? { - return UUID(uuidString: String(cString: value_at_index_as_uuid(self.raw!, index))) + func asUUID(index: Int) -> UUID? { + return UUID(uuidString: String(cString: value_at_index_as_uuid(self.raw!, Int32(index)))) } override func cleanup(pointer: OpaquePointer) { @@ -73,8 +73,10 @@ class ColResultIterator: OptionalRustObject, IteratorProtocol { extension ColResult: Sequence { func makeIterator() -> ColResultIterator { + defer { + self.raw = nil + } guard let raw = self.raw else { - print("list pointer destroyed") return ColResultIterator(iter: nil) } let rowIter = values_iter(raw) diff --git a/sdks/swift/Mentat/Mentat/Rust/RustObject.swift b/sdks/swift/Mentat/Mentat/Rust/RustObject.swift index ad3a0b45..ca5c67c7 100644 --- a/sdks/swift/Mentat/Mentat/Rust/RustObject.swift +++ b/sdks/swift/Mentat/Mentat/Rust/RustObject.swift @@ -9,7 +9,7 @@ protocol Destroyable { func cleanup(pointer: OpaquePointer) } -class RustObject: Destroyable { +public class RustObject: Destroyable { var raw: OpaquePointer lazy var uniqueId: ObjectIdentifier = { diff --git a/sdks/swift/Mentat/Mentat/Transact/TxReport.swift b/sdks/swift/Mentat/Mentat/Transact/TxReport.swift index 0bb6708c..d7eef3dd 100644 --- a/sdks/swift/Mentat/Mentat/Transact/TxReport.swift +++ b/sdks/swift/Mentat/Mentat/Transact/TxReport.swift @@ -1,22 +1,30 @@ +// /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Foundation + import Mentatlib -class TxReport { - var raw: UnsafePointer +class TxReport: RustObject { - required init(raw: UnsafePointer) { - self.raw = raw + public var txId: Int64 { + return tx_report_get_entid(self.raw) } - func intoRaw() -> UnsafePointer { - return self.raw + public var txInstant: Date { + return Date(timeIntervalSince1970: TimeInterval(tx_report_get_tx_instant(self.raw))) } - deinit { - destroy(UnsafeMutableRawPointer(mutating: raw)) + public func entidForTmpId(tmpId: String) -> Int64? { + guard let entidPtr = tx_report_entity_for_temp_id(self.raw, tmpId) else { + return nil + } + return entidPtr.pointee + } + + override func cleanup(pointer: OpaquePointer) { + tx_report_destroy(pointer) } } diff --git a/sdks/swift/Mentat/Mentat/store.h b/sdks/swift/Mentat/Mentat/store.h index 46a5f474..46ef3a23 100644 --- a/sdks/swift/Mentat/Mentat/store.h +++ b/sdks/swift/Mentat/Mentat/store.h @@ -5,13 +5,20 @@ #ifndef store_h #define store_h #include +#include -struct ExternTxReport { +struct TransactionChange { int64_t txid; int64_t*_Nonnull* _Nonnull changes; uint64_t len; }; +struct TxChangeList { + struct TransactionChange*_Nonnull* _Nonnull reports; + uint64_t len; +}; +typedef struct TxChangeList TxChangeList; + struct Result { void* _Nullable ok; char* _Nullable err; @@ -23,20 +30,25 @@ struct Option { }; typedef struct Option Option; -struct Store; - -struct TxReportList { - struct ExternTxReport*_Nonnull* _Nonnull reports; - uint64_t len; +typedef NS_ENUM(NSInteger, ValueType) { + ValueTypeRef = 1, + ValueTypeBoolean, + ValueTypeInstant, + ValueTypeLong, + ValueTypeDouble, + ValueTypeString, + ValueTypeKeyword, + ValueTypeUuid }; -typedef struct TxReportList TxReportList; struct Query; -struct TypedValue; struct QueryResultRow; struct QueryResultRows; struct QueryRowsIterator; struct QueryRowIterator; +struct Store; +struct TxReport; +struct TypedValue; // Store struct Store*_Nonnull store_open(const char*_Nonnull uri); @@ -44,6 +56,7 @@ struct Store*_Nonnull store_open(const char*_Nonnull uri); void destroy(void* _Nullable obj); void query_builder_destroy(struct Query* _Nullable obj); void store_destroy(struct Store* _Nonnull obj); +void tx_report_destroy(struct TxReport* _Nonnull obj); void typed_value_destroy(struct TypedValue* _Nullable obj); void typed_value_list_destroy(struct QueryResultRow* _Nullable obj); void typed_value_list_iter_destroy(struct QueryRowIterator* _Nullable obj); @@ -52,15 +65,18 @@ void typed_value_result_set_iter_destroy(struct QueryRowsIterator* _Nullable obj // 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); +int64_t tx_report_get_entid(const struct TxReport* _Nonnull report); +int64_t tx_report_get_tx_instant(const struct TxReport* _Nonnull report); // Sync struct Result*_Nonnull store_sync(struct Store*_Nonnull store, const char* _Nonnull user_uuid, const char* _Nonnull server_uri); // Observers -void store_register_observer(struct Store*_Nonnull store, const char* _Nonnull key, const int64_t* _Nonnull attributes, const int64_t len, void (*_Nonnull callback_fn)(const char* _Nonnull key, const struct TxReportList* _Nonnull reports)); +void store_register_observer(struct Store*_Nonnull store, const char* _Nonnull key, const int64_t* _Nonnull attributes, const int64_t len, void (*_Nonnull callback_fn)(const char* _Nonnull key, const struct TxChangeList* _Nonnull reports)); void store_unregister_observer(struct Store*_Nonnull store, const char* _Nonnull key); int64_t store_entid_for_attribute(struct Store*_Nonnull store, const char*_Nonnull attr); -const struct int64_t changelist_entry_at(const struct ExternTxReport* _Nonnull report, size_t index); +int64_t changelist_entry_at(const struct TransactionChange* _Nonnull report, size_t index); // Query struct Query*_Nonnull store_query(struct Store*_Nonnull store, const char* _Nonnull query); @@ -93,6 +109,7 @@ double typed_value_as_double(struct TypedValue*_Nonnull value); int64_t typed_value_as_timestamp(struct TypedValue*_Nonnull value); const char* _Nonnull typed_value_as_string(struct TypedValue*_Nonnull value); const char* _Nonnull typed_value_as_uuid(struct TypedValue*_Nonnull value); +enum ValueType typed_value_value_type(struct TypedValue*_Nonnull value); struct QueryResultRow* _Nullable row_at_index(struct QueryResultRows* _Nonnull rows, const int32_t index); struct QueryRowsIterator* _Nonnull rows_iter(struct QueryResultRows* _Nonnull rows); @@ -128,7 +145,7 @@ struct Result*_Nonnull store_set_timestamp_for_attribute_on_entid(struct Store*_ struct Result*_Nonnull store_set_string_for_attribute_on_entid(struct Store*_Nonnull store, const int64_t entid, const char* _Nonnull attribute, const char* _Nonnull value); struct Result*_Nonnull store_set_uuid_for_attribute_on_entid(struct Store*_Nonnull store, const int64_t entid, const char* _Nonnull attribute, const char* _Nonnull value); -// TxReports -const struct ExternTxReport* _Nullable tx_report_list_entry_at(const struct TxReportList* _Nonnull list, size_t index); +// Transaction change lists +const struct TransactionChange* _Nullable tx_change_list_entry_at(const struct TxChangeList* _Nonnull list, size_t index); #endif /* store_h */ diff --git a/sdks/swift/Mentat/MentatTests/MentatTests.swift b/sdks/swift/Mentat/MentatTests/MentatTests.swift index cbee39cc..50452d9f 100644 --- a/sdks/swift/Mentat/MentatTests/MentatTests.swift +++ b/sdks/swift/Mentat/MentatTests/MentatTests.swift @@ -3,11 +3,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import XCTest + @testable import Mentat class MentatTests: XCTestCase { var schema: String? + var edn: String? + var store: Mentat? override func setUp() { super.setUp() @@ -35,8 +38,8 @@ class MentatTests: XCTestCase { func readSchema() throws -> String { guard let schema = self.schema else { let bundle = Bundle(for: type(of: self)) - guard let schemaPath = bundle.path(forResource: "cities", ofType: "schema") else { return "" } - let schema = try String(contentsOf: URL(fileURLWithPath: schemaPath)) + let schemaUrl = bundle.url(forResource: "cities", withExtension: "schema", subdirectory: "fixtures")! + let schema = try String(contentsOf: schemaUrl) self.schema = schema return schema } @@ -44,16 +47,790 @@ class MentatTests: XCTestCase { return schema } - func testTransactVocabulary() { - do { - let vocab = try readSchema() + func readEdn() throws -> String { + guard let edn = self.edn else { + let bundle = Bundle(for: type(of: self)) + let ednUrl = bundle.url(forResource: "all_seattle", withExtension: "edn", subdirectory: "fixtures")! + let edn = try String(contentsOf: ednUrl) + self.edn = edn + return edn + } + + return edn + } + + func transactSchema(mentat: Mentat) throws -> TxReport { + let vocab = try readSchema() + let report = try mentat.transact(transaction: vocab) + return report + } + + func transactEdn(mentat: Mentat) throws -> TxReport { + let edn = try readEdn() + let report = try mentat.transact(transaction: edn) + return report + } + + func newStore() -> Mentat { + guard let mentat = self.store else { let mentat = Mentat() - let success = try mentat.transact(transaction: vocab) - assert( success ) + let _ = try! self.transactSchema(mentat: mentat) + let _ = try! self.transactEdn(mentat: mentat) + self.store = mentat + return mentat + } + + return mentat + } + + func populateWithTypesSchema(mentat: Mentat) -> TxReport? { + do { + let schema = """ + [ + [:db/add "b" :db/ident :foo/boolean] + [:db/add "b" :db/valueType :db.type/boolean] + [:db/add "b" :db/cardinality :db.cardinality/one] + [:db/add "l" :db/ident :foo/long] + [:db/add "l" :db/valueType :db.type/long] + [:db/add "l" :db/cardinality :db.cardinality/one] + [:db/add "r" :db/ident :foo/ref] + [:db/add "r" :db/valueType :db.type/ref] + [:db/add "r" :db/cardinality :db.cardinality/one] + [:db/add "i" :db/ident :foo/instant] + [:db/add "i" :db/valueType :db.type/instant] + [:db/add "i" :db/cardinality :db.cardinality/one] + [:db/add "d" :db/ident :foo/double] + [:db/add "d" :db/valueType :db.type/double] + [:db/add "d" :db/cardinality :db.cardinality/one] + [:db/add "s" :db/ident :foo/string] + [:db/add "s" :db/valueType :db.type/string] + [:db/add "s" :db/cardinality :db.cardinality/one] + [:db/add "k" :db/ident :foo/keyword] + [:db/add "k" :db/valueType :db.type/keyword] + [:db/add "k" :db/cardinality :db.cardinality/one] + [:db/add "u" :db/ident :foo/uuid] + [:db/add "u" :db/valueType :db.type/uuid] + [:db/add "u" :db/cardinality :db.cardinality/one] + ] + """ + let report = try mentat.transact(transaction: schema) + let stringEntid = report.entidForTmpId(tmpId: "s")! + + let data = """ + [ + [:db/add "a" :foo/boolean true] + [:db/add "a" :foo/long 25] + [:db/add "a" :foo/instant #inst "2017-01-01T11:00:00.000Z"] + [:db/add "a" :foo/double 11.23] + [:db/add "a" :foo/string "The higher we soar the smaller we appear to those who cannot fly."] + [:db/add "a" :foo/keyword :foo/string] + [:db/add "a" :foo/uuid #uuid "550e8400-e29b-41d4-a716-446655440000"] + [:db/add "b" :foo/boolean false] + [:db/add "b" :foo/ref \(stringEntid)] + [:db/add "b" :foo/long 50] + [:db/add "b" :foo/instant #inst "2018-01-01T11:00:00.000Z"] + [:db/add "b" :foo/double 22.46] + [:db/add "b" :foo/string "Silence is worse; all truths that are kept silent become poisonous."] + [:db/add "b" :foo/uuid #uuid "4cb3f828-752d-497a-90c9-b1fd516d5644"] + ] + """ + return try mentat.transact(transaction: data) + } catch { + assertionFailure(error.localizedDescription) + } + return nil + } + + func test1TransactVocabulary() { + do { + let mentat = Mentat() + let report = try transactSchema(mentat: mentat) + XCTAssertNotNil(report) + assert(report.txId > 0) } catch { assertionFailure(error.localizedDescription) } } - // TODO: Add more tests once we are able to add vocabulary and transact entities + func test2TransactEntities() { + do { + let mentat = Mentat() + let _ = try self.transactSchema(mentat: mentat) + let report = try self.transactEdn(mentat: mentat) + XCTAssertNotNil(report) + assert(report.txId > 0) + let entid = report.entidForTmpId(tmpId: "a17592186045438") + assert(entid == 65566) + } catch { + assertionFailure(error.localizedDescription) + } + } + + func testQueryScalar() { + let mentat = newStore() + let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query).bind(varName: "?name", toString: "Wallingford").executeScalar(callback: { (scalarResult, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + guard let result = scalarResult?.asString() else { + return assertionFailure("No String value received") + } + assert(result == "KOMO Communities - Wallingford") + expect.fulfill() + }) + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testQueryColl() { + let mentat = newStore() + let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query).executeColl(callback: { (collResult, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + guard let rows = collResult else { + return assertionFailure("No results received") + } + // we are expecting 3 results + for i in 0..<3 { + let _ = rows.asDate(index: i) + assert(true) + } + expect.fulfill() + }) + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testQueryCollResultIterator() { + let mentat = newStore() + let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query).executeColl(callback: { (collResult, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + guard let rows = collResult else { + return assertionFailure("No results received") + } + + rows.forEach({ (value) in + assert(value.valueType.rawValue == 2) + }) + expect.fulfill() + }) + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testQueryTuple() { + let mentat = newStore() + let query = """ + [:find [?name ?cat] + :where + [?c :community/name ?name] + [?c :community/type :community.type/website] + [(fulltext $ :community/category "food") [[?c ?cat]]]] + """ + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query).executeTuple(callback: { (tupleResult, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + guard let tuple = tupleResult else { + return assertionFailure("expecting a result") + } + let name = tuple.asString(index: 0) + let category = tuple.asString(index: 1) + assert(name == "Community Harvest of Southwest Seattle") + assert(category == "sustainable food") + expect.fulfill() + }) + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testQueryRel() { + let mentat = newStore() + let query = """ + [:find ?name ?cat + :where + [?c :community/name ?name] + [?c :community/type :community.type/website] + [(fulltext $ :community/category "food") [[?c ?cat]]]] + """ + let expect = expectation(description: "Query is executed") + let expectedResults = [("InBallard", "food"), + ("Seattle Chinatown Guide", "food"), + ("Community Harvest of Southwest Seattle", "sustainable food"), + ("University District Food Bank", "food bank")] + try! mentat.query(query: query).execute(callback: { (relResult, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + guard let rows = relResult else { + return assertionFailure("No results received") + } + + for (i, row) in rows.enumerated() { + let (name, category) = expectedResults[i] + assert( row.asString(index: 0) == name) + assert(row.asString(index: 1) == category) + } + expect.fulfill() + }) + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testQueryRelResultIterator() { + let mentat = newStore() + let query = """ + [:find ?name ?cat + :where + [?c :community/name ?name] + [?c :community/type :community.type/website] + [(fulltext $ :community/category "food") [[?c ?cat]]]] + """ + let expect = expectation(description: "Query is executed") + let expectedResults = [("InBallard", "food"), + ("Seattle Chinatown Guide", "food"), + ("Community Harvest of Southwest Seattle", "sustainable food"), + ("University District Food Bank", "food bank")] + try! mentat.query(query: query).execute(callback: { (relResult, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + guard let rows = relResult else { + return assertionFailure("No results received") + } + + var i = 0 + rows.forEach({ (row) in + let (name, category) = expectedResults[i] + i += 1 + assert(row.asString(index: 0) == name) + assert(row.asString(index: 1) == category) + }) + assert(i == 4) + expect.fulfill() + }) + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testBindLong() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a") + let query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?bool", toBoolean: true) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asEntid() == aEntid) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testBindRef() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let stringEntid = mentat.entidForAttribute(attribute: ":foo/string") + let bEntid = report.entidForTmpId(tmpId: "b") + let query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?ref", toReference: stringEntid) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asEntid() == bEntid) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testBindKwRef() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let bEntid = report.entidForTmpId(tmpId: "b") + let query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?ref", toReference: ":foo/string") + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asEntid() == bEntid) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testBindKw() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a") + let query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?kw", toKeyword: ":foo/string") + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asEntid() == aEntid) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testBindDate() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a") + let query = "[:find [?e ?d] :in ?now :where [?e :foo/instant ?d] [(< ?d ?now)]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?now", toDate: Date()) + .executeTuple { (row, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(row) + assert(row?.asEntid(index: 0) == aEntid) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + + func testBindString() { + let mentat = newStore() + let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?name", toString: "Wallingford") + .executeScalar(callback: { (scalarResult, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + guard let result = scalarResult?.asString() else { + return assertionFailure("No String value received") + } + assert(result == "KOMO Communities - Wallingford") + expect.fulfill() + }) + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testBindUuid() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a") + let query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]" + let uuid = UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")! + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?uuid", toUuid: uuid) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asEntid() == aEntid) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testBindBoolean() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a") + let query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?bool", toBoolean: true) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asEntid() == aEntid) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testBindDouble() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a") + let query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?double", toDouble: 11.23) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asEntid() == aEntid) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testTypedValueAsLong() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?e", toReference: aEntid) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asLong() == 25) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testTypedValueAsRef() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let query = "[:find ?e . :where [?e :foo/long 25]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asEntid() == aEntid) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testTypedValueAsKw() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?e", toReference: aEntid) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asKeyword() == ":foo/string") + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testTypedValueAsBoolean() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?e", toReference: aEntid) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asBool() == true) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testTypedValueAsDouble() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?e", toReference: aEntid) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asDouble() == 11.23) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testTypedValueAsDate() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]" + let expect = expectation(description: "Query is executed") + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + let expectedDate = formatter.date(from: "2017-01-01T11:00:00+00:00") + + try! mentat.query(query: query) + .bind(varName: "?e", toReference: aEntid) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asDate() == expectedDate) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testTypedValueAsString() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]" + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?e", toReference: aEntid) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asString() == "The higher we soar the smaller we appear to those who cannot fly.") + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testTypedValueAsUuid() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]" + let expectedUuid = UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")! + let expect = expectation(description: "Query is executed") + try! mentat.query(query: query) + .bind(varName: "?e", toReference: aEntid) + .executeScalar { (value, error) in + assert(error == nil, "Unexpected error: \(String(describing: error))") + XCTAssertNotNil(value) + assert(value?.asUUID() == expectedUuid) + expect.fulfill() + } + waitForExpectations(timeout: 1) { error in + if let error = error { + assertionFailure("waitForExpectationsWithTimeout errored: \(error)") + } + } + } + + func testValueForAttributeOfEntity() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let value = mentat.value(forAttribute: ":foo/long", ofEntity: aEntid) + XCTAssertNotNil(value) + assert(value?.asLong() == 25) + } + + func testEntidForAttribute() { + let mentat = Mentat() + let _ = self.populateWithTypesSchema(mentat: mentat)! + let entid = mentat.entidForAttribute(attribute: ":foo/long") + assert(entid == 65540) + } + + func testSetLongForAttributeOfEntity() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let attr = ":foo/long" + let pre = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(pre) + assert(pre?.asLong() == 25) + XCTAssertNoThrow(try mentat.set(long: 100, forAttribute: attr, onEntity: aEntid)) + let post = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(post) + assert(post?.asLong() == 100) + } + + func testSetBooleanForAttributeOfEntity() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let attr = ":foo/boolean" + let pre = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(pre) + assert(pre?.asBool() == true) + XCTAssertNoThrow(try mentat.set(boolean: false, forAttribute: attr, onEntity: aEntid)) + let post = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(post) + let p = post?.asBool() + assert(p == false) + } + + func testSetRefForAttributeOfEntity() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let bEntid = report.entidForTmpId(tmpId: "b")! + let attr = ":foo/ref" + let pre = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNil(pre) + XCTAssertNoThrow(try mentat.set(reference: bEntid, forAttribute: attr, onEntity: aEntid)) + let post = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(post) + assert(post?.asEntid() == bEntid) + } + + func testSetRefKwForAttributeOfEntity() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let attr = ":foo/ref" + let pre = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNil(pre) + XCTAssertNoThrow(try mentat.setKeywordReference(value: ":foo/long", forAttribute: attr, onEntity: aEntid)) + let post = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(post) + assert(post?.asEntid() == 65540) + } + + func testSetDateForAttributeOfEntity() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let attr = ":foo/instant" + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + let previousDate = formatter.date(from: "2017-01-01T11:00:00+00:00") + + let pre = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(pre) + assert(pre?.asDate() == previousDate) + + let now = Date() + XCTAssertNoThrow(try mentat.set(date: now, forAttribute: attr, onEntity: aEntid)) + let post = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(post) + let p = post?.asDate() + + assert(formatter.string(from: p!) == formatter.string(from: now)) + } + + func testSetDoubleForAttributeOfEntity() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let attr = ":foo/double" + let pre = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(pre) + assert(pre?.asDouble() == 11.23) + XCTAssertNoThrow(try mentat.set(double: 22.0, forAttribute: attr, onEntity: aEntid)) + let post = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(post) + assert(post?.asDouble() == 22.0) + } + + func testSetStringForAttributeOfEntity() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let attr = ":foo/string" + let pre = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(pre) + assert(pre?.asString() == "The higher we soar the smaller we appear to those who cannot fly.") + XCTAssertNoThrow(try mentat.set(string: "Become who you are!", forAttribute: attr, onEntity: aEntid)) + let post = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(post) + assert(post?.asString() == "Become who you are!") + } + + func testSetUuidForAttributeOfEntity() { + let mentat = Mentat() + let report = self.populateWithTypesSchema(mentat: mentat)! + let aEntid = report.entidForTmpId(tmpId: "a")! + let previousUuid = UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")! + let attr = ":foo/uuid" + let pre = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(pre) + assert(pre?.asUUID() == previousUuid) + + let newUuid = UUID() + XCTAssertNoThrow(try mentat.set(uuid: newUuid, forAttribute: attr, onEntity: aEntid)) + let post = mentat.value(forAttribute: attr, ofEntity: aEntid) + XCTAssertNotNil(post) + assert(post?.asUUID() == newUuid) + } }