From 386580398123e08dd0eb2c2d501c86b9027c576d Mon Sep 17 00:00:00 2001 From: Emily Toop Date: Thu, 26 Apr 2018 17:28:14 +0100 Subject: [PATCH] Implement InProgress transactions and InProgress and Entity builders on iOS --- .../Mentat/Mentat.xcodeproj/project.pbxproj | 12 + .../xcschemes/Mentat Debug.xcscheme | 8 - sdks/swift/Mentat/Mentat/Errors/Errors.swift | 1 - sdks/swift/Mentat/Mentat/Mentat.swift | 55 +++ .../Mentat/Transact/EntityBuilder.swift | 355 +++++++++++++++ .../Mentat/Mentat/Transact/InProgress.swift | 184 ++++++++ .../Mentat/Transact/InProgressBuilder.swift | 372 +++++++++++++++ .../Mentat/Mentat/Transact/TxReport.swift | 2 +- sdks/swift/Mentat/Mentat/store.h | 64 +++ .../Mentat/MentatTests/MentatTests.swift | 424 +++++++++++++++++- 10 files changed, 1462 insertions(+), 15 deletions(-) create mode 100644 sdks/swift/Mentat/Mentat/Transact/EntityBuilder.swift create mode 100644 sdks/swift/Mentat/Mentat/Transact/InProgress.swift create mode 100644 sdks/swift/Mentat/Mentat/Transact/InProgressBuilder.swift diff --git a/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj b/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj index 37886d23..8e75cee3 100644 --- a/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj +++ b/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 7B64E44D209094520063909F /* InProgressBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B64E44A209094510063909F /* InProgressBuilder.swift */; }; + 7B64E44E209094520063909F /* EntityBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B64E44B209094510063909F /* EntityBuilder.swift */; }; + 7B64E44F209094520063909F /* InProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B64E44C209094520063909F /* InProgress.swift */; }; 7B74483D208DF667006CFFB0 /* Result+Unwrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B74483C208DF667006CFFB0 /* Result+Unwrap.swift */; }; 7BAE75A22089020E00895D37 /* libmentat_ffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BEB7D23207BE2AF000369AD /* libmentat_ffi.a */; }; 7BAE75A42089022B00895D37 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BAE75A32089022B00895D37 /* libsqlite3.tbd */; }; @@ -39,6 +42,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7B64E44A209094510063909F /* InProgressBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InProgressBuilder.swift; sourceTree = ""; }; + 7B64E44B209094510063909F /* EntityBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityBuilder.swift; sourceTree = ""; }; + 7B64E44C209094520063909F /* InProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InProgress.swift; sourceTree = ""; }; 7B74483C208DF667006CFFB0 /* Result+Unwrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Result+Unwrap.swift"; path = "Mentat/Extensions/Result+Unwrap.swift"; sourceTree = SOURCE_ROOT; }; 7B911E1A2085081D000998CB /* libtoodle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtoodle.a; path = "../../../../sync-storage-prototype/rust/target/universal/release/libtoodle.a"; sourceTree = ""; }; 7BAE75A32089022B00895D37 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; @@ -196,6 +202,9 @@ 7BEB7D26207BE5BB000369AD /* Transact */ = { isa = PBXGroup; children = ( + 7B64E44B209094510063909F /* EntityBuilder.swift */, + 7B64E44C209094520063909F /* InProgress.swift */, + 7B64E44A209094510063909F /* InProgressBuilder.swift */, 7BEB7D2B207D03DA000369AD /* TxReport.swift */, ); path = Transact; @@ -321,8 +330,11 @@ 7BDB96CC207B7684009D0651 /* Errors.swift in Sources */, 7BDB96B02077C38E009D0651 /* Mentat.swift in Sources */, 7BDB96B72077C38E009D0651 /* TypedValue.swift in Sources */, + 7B64E44E209094520063909F /* EntityBuilder.swift in Sources */, + 7B64E44D209094520063909F /* InProgressBuilder.swift in Sources */, 7BDB96B52077C38E009D0651 /* TupleResult.swift in Sources */, 7B74483D208DF667006CFFB0 /* Result+Unwrap.swift in Sources */, + 7B64E44F209094520063909F /* InProgress.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat Debug.xcscheme b/sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat Debug.xcscheme index b08914c4..589794d4 100644 --- a/sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat Debug.xcscheme +++ b/sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat Debug.xcscheme @@ -37,14 +37,6 @@ BlueprintName = "MentatTests" ReferencedContainer = "container:Mentat.xcodeproj"> - - - - - - diff --git a/sdks/swift/Mentat/Mentat/Errors/Errors.swift b/sdks/swift/Mentat/Mentat/Errors/Errors.swift index 9091219b..67946c21 100644 --- a/sdks/swift/Mentat/Mentat/Errors/Errors.swift +++ b/sdks/swift/Mentat/Mentat/Errors/Errors.swift @@ -1,4 +1,3 @@ -// /* Copyright 2018 Mozilla * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use diff --git a/sdks/swift/Mentat/Mentat/Mentat.swift b/sdks/swift/Mentat/Mentat/Mentat.swift index 5dd6fe66..e734c72f 100644 --- a/sdks/swift/Mentat/Mentat/Mentat.swift +++ b/sdks/swift/Mentat/Mentat/Mentat.swift @@ -69,8 +69,63 @@ class Mentat: RustObject { return TxReport(raw: try result.unwrap()) } + /** + Start a new transaction. + + - Throws: `ResultError.error` if the creation of the transaction fails. + - Throws: `ResultError.empty` if no `InProgress` is created. + + - Returns: The `InProgress` used to manage the transaction + */ + func beginTransaction() throws -> InProgress { + let result = store_begin_transaction(self.raw).pointee; + return InProgress(raw: try result.unwrap()) + } + + /** + Creates a new transaction (`InProgress`) and returns an `InProgressBuilder` for that transaction. + + - Throws: `ResultError.error` if the creation of the transaction fails. + - Throws: `ResultError.empty` if no `InProgressBuilder` is created. + + - Returns: an `InProgressBuilder` for this `InProgress` + */ + func entityBuilder() throws -> InProgressBuilder { + let result = store_in_progress_builder(self.raw).pointee + return InProgressBuilder(raw: try result.unwrap()) + } + + /** + Creates a new transaction (`InProgress`) and returns an `EntityBuilder` for the entity with `entid` + for that transaction. + + - Throws: `ResultError.error` if the creation of the transaction fails. + - Throws: `ResultError.empty` if no `EntityBuilder` is created. + + - Returns: an `EntityBuilder` for this `InProgress` + */ + func entityBuilder(forEntid entid: Entid) throws -> EntityBuilder { + let result = store_entity_builder_from_entid(self.raw, entid).pointee + return EntityBuilder(raw: try result.unwrap()) + } + + /** + Creates a new transaction (`InProgress`) and returns an `EntityBuilder` for a new entity with `tempId` + for that transaction. + + - Throws: `ResultError.error` if the creation of the transaction fails. + - Throws: `ResultError.empty` if no `EntityBuilder` is created. + + - Returns: an `EntityBuilder` for this `InProgress` + */ + func entityBuilder(forTempId tempId: String) throws -> EntityBuilder { + let result = store_entity_builder_from_temp_id(self.raw, tempId).pointee + return EntityBuilder(raw: try result.unwrap()) + } + /** Get the the `Entid` of the attribute. + - Parameter attribute: The string represeting the attribute whose `Entid` we are after. The string is represented as `:namespace/name`. diff --git a/sdks/swift/Mentat/Mentat/Transact/EntityBuilder.swift b/sdks/swift/Mentat/Mentat/Transact/EntityBuilder.swift new file mode 100644 index 00000000..72cba6d6 --- /dev/null +++ b/sdks/swift/Mentat/Mentat/Transact/EntityBuilder.swift @@ -0,0 +1,355 @@ +/* Copyright 2018 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. */ + +import Foundation +import MentatStore + +/** + This class wraps a raw pointer that points to a Rust `EntityBuilder` object. + + `EntityBuilder` provides a programmatic interface to performing assertions on a specific entity. + It provides functions for adding and retracting values for attributes for an entity within + an in progress transaction. + + The `transact` function will transact the assertions that have been added to the `EntityBuilder` + and pass back the `TxReport` that was generated by this transact and the `InProgress` that was + used to perform the transact. This enables you to perform further transacts on the same `InProgress` + before committing. + + ``` + let aEntid = txReport.entid(forTempId: "a") + let bEntid = txReport.entid(forTempId: "b") + do { + let builder = try mentat.entityBuilder(forEntid: bEntid) + try builder.add(keyword: ":foo/boolean", boolean: true) + try builder.add(keyword: ":foo/instant", date: newDate) + let (inProgress, report) = try builder.transact() + try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]") + try inProgress.commit() + } catch { + ... + } + ``` + + The `commit` function will transact and commit the assertions that have been added to the `EntityBuilder`. + It will consume the `InProgress` used to perform the transact. It returns the `TxReport` generated by + the transact. After calling `commit`, a new transaction must be started by calling `Mentat.beginTransaction()` + in order to perform further actions. + + ``` + let aEntid = txReport.entid(forTempId: "a") + do { + let builder = try mentat.entityBuilder(forEntid: aEntid) + try builder.add(keyword: ":foo/boolean", boolean: true) + try builder.add(keyword: ":foo/instant", date: newDate) + let report = try builder.commit() + ... + } catch { + ... + } + ``` + */ +class EntityBuilder: OptionalRustObject { + /** + Asserts the value of attribute `keyword` to be the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/long`. + */ + func add(keyword: String, long value: Int64) throws { + try entity_builder_add_long(try self.validPointer(), keyword, value).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/ref`. + */ + func add(keyword: String, reference value: Int64) throws { + try entity_builder_add_ref(try self.validPointer(), keyword, value).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/keyword`. + */ + func add(keyword: String, keyword value: String) throws { + try entity_builder_add_keyword(try self.validPointer(), keyword, value).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/boolean`. + */ + func add(keyword: String, boolean value: Bool) throws { + try entity_builder_add_boolean(try self.validPointer(), keyword, value ? 1 : 0).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/double`. + */ + func add(keyword: String, double value: Double) throws { + try entity_builder_add_double(try self.validPointer(), keyword, value).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/instant`. + */ + func add(keyword: String, date value: Date) throws { + try entity_builder_add_timestamp(try self.validPointer(), keyword, value.toMicroseconds()).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/string`. + */ + func add(keyword: String, string value: String) throws { + try entity_builder_add_string(try self.validPointer(), keyword, value).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/uuid`. + */ + func add(keyword: String, uuid value: UUID) throws { + var rawUuid = value.uuid + let _ = try withUnsafePointer(to: &rawUuid) { uuidPtr in + try entity_builder_add_uuid(try self.validPointer(), keyword, uuidPtr).pointee.tryUnwrap() + } + } + + /** + Retracts the value of attribute `keyword` from the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/long`. + */ + func retract(keyword: String, long value: Int64) throws { + try entity_builder_retract_long(try self.validPointer(), keyword, value).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/ref`. + */ + func retract(keyword: String, reference value: Int64) throws { + try entity_builder_retract_ref(try self.validPointer(), keyword, value).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/keyword`. + */ + func retract(keyword: String, keyword value: String) throws { + try entity_builder_retract_keyword(try self.validPointer(), keyword, value).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/boolean`. + */ + func retract(keyword: String, boolean value: Bool) throws { + try entity_builder_retract_boolean(try self.validPointer(), keyword, value ? 1 : 0).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/double`. + */ + func retract(keyword: String, double value: Double) throws { + try entity_builder_retract_double(try self.validPointer(), keyword, value).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/instant`. + */ + func retract(keyword: String, date value: Date) throws { + try entity_builder_retract_timestamp(try self.validPointer(), keyword, value.toMicroseconds()).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/string`. + */ + func retract(keyword: String, string value: String) throws { + try entity_builder_retract_string(try self.validPointer(), keyword, value).pointee.tryUnwrap() + } + + + /** + Retracts the value of attribute `keyword` from the provided `value`. + + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/uuid`. + */ + func retract(keyword: String, uuid value: UUID) throws { + var rawUuid = value.uuid + let _ = try withUnsafePointer(to: &rawUuid) { uuidPtr in + try entity_builder_retract_uuid(try self.validPointer(), keyword, uuidPtr).pointee.tryUnwrap() + } + } + + /** + Transacts the added assertions. This consumes the pointer associated with this `EntityBuilder` + such that no further assertions can be added after the `transact` has completed. To perform + further assertions, use the `InProgress` returned from this function. + + This does not commit the transaction. In order to do so, `commit` can be called on the `InProgress` returned + from this function. + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if an error occured during the execution of the transact. + + - Returns: The current `InProgress` and the `TxReport` generated by the transact. + */ + func transact() throws -> (InProgress, TxReport?) { + defer { + self.raw = nil + } + let result = entity_builder_transact(try self.validPointer()).pointee + let inProgress = InProgress(raw: result.inProgress) + guard let report = try result.result.pointee.tryUnwrap() else { + return (inProgress, nil) + } + return (inProgress, TxReport(raw: report)) + } + + /** + Transacts the added assertions and commits. This consumes the pointer associated with this `EntityBuilder` + and the associated `InProgress` such that no further assertions can be added after the `commit` has completed. + To perform further assertions, a new `InProgress` or `EntityBuilder` should be created. + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if an error occured during the execution of the transact. + + - Returns: The `TxReport` generated by the transact. + */ + func commit() throws -> TxReport { + defer { + self.raw = nil + } + return TxReport(raw: try entity_builder_commit(try self.validPointer()).pointee.unwrap()) + } + + override func cleanup(pointer: OpaquePointer) { + entity_builder_destroy(pointer) + } +} diff --git a/sdks/swift/Mentat/Mentat/Transact/InProgress.swift b/sdks/swift/Mentat/Mentat/Transact/InProgress.swift new file mode 100644 index 00000000..e443bb40 --- /dev/null +++ b/sdks/swift/Mentat/Mentat/Transact/InProgress.swift @@ -0,0 +1,184 @@ +/* Copyright 2018 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. */ + +import Foundation +import MentatStore + +/** + This class wraps a raw pointer that points to a Rust `InProgress` object. + + `InProgress` allows for multiple transacts to be performed in a single transaction. + Each transact performed results in a `TxReport` that can be used to gather information + to be used in subsequent transacts. + + Committing an `InProgress` commits all the transacts that have been performed using + that `InProgress`. + + Rolling back and `InProgress` rolls back all the transacts that have been performed + using that `InProgress`. + + ``` + do { + let inProgress = try mentat.beginTransaction() + let txReport = try inProgress.transact(transaction: "[[:db/add "a" :foo/long 22]]") + let aEntid = txReport.entid(forTempId: "a") + let report = try inProgress.transact(transaction: "[[:db/add "b" :foo/ref \(aEntid)] [:db/add "b" :foo/boolean true]]") + try inProgress.commit() + } catch { + ... + } + ``` + + `InProgress` also provides a number of functions to generating an builder to assert datoms programatically. + The two types of builder are `InProgressBuilder` and `EntityBuilder`. + + `InProgressBuilder` takes the current `InProgress` and provides a programmatic interface to add + and retract values from entities for which there exists an `Entid`. The provided `InProgress` + is used to perform the transacts. + + ``` + let aEntid = txReport.entid(forTempId: "a") + let bEntid = txReport.entid(forTempId: "b") + do { + let inProgress = try mentat.beginTransaction() + let builder = try inProgress.builder() + try builder.add(entid: bEntid, keyword: ":foo/boolean", boolean: true) + try builder.add(entid: aEntid, keyword: ":foo/instant", date: newDate) + let (inProgress, report) = try builder.transact() + try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]") + try inProgress.commit() + } catch { + ... + } + ``` + +`EntityBuilder` takes the current `InProgress` and either an `Entid` or a `tempid` to provide + a programmatic interface to add and retract values from a specific entity. The provided `InProgress` + is used to perform the transacts. + + ``` + do { + let transaction = try mentat.beginTransaction() + let builder = try transaction.builder(forTempId: "b") + try builder.add(keyword: ":foo/boolean", boolean: true) + try builder.add(keyword: ":foo/instant", date: newDate) + let (inProgress, report) = try builder.transact() + let bEntid = report.entid(forTempId: "b") + try inProgress.transact(transaction: "[[:db/add \(bEntid) :foo/long 22]]") + try inProgress.commit() + } catch { + ... + } + ``` + */ +class InProgress: OptionalRustObject { + + /** + Creates an `InProgressBuilder` using this `InProgress`. + + - 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. + + - Returns: an `InProgressBuilder` for this `InProgress` + */ + func builder() throws -> InProgressBuilder { + defer { + self.raw = nil + } + return InProgressBuilder(raw: in_progress_builder(try self.validPointer())) + } + + /** + Creates an `EntityBuilder` using this `InProgress` for the entity with `entid`. + + - Parameter tempId: 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. + + - Returns: an `EntityBuilder` for this `InProgress` + */ + func builder(forEntid entid: Int64) throws -> EntityBuilder { + defer { + self.raw = nil + } + return EntityBuilder(raw: in_progress_entity_builder_from_entid(try self.validPointer(), entid)) + } + + /** + Creates an `EntityBuilder` using this `InProgress` for a new entity with `tempId`. + + - Parameter tempId: The temporary identifier 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. + + - Returns: an `EntityBuilder` for this `InProgress` + */ + func builder(forTempId tempId: String) throws -> EntityBuilder { + defer { + self.raw = nil + } + return EntityBuilder(raw: in_progress_entity_builder_from_temp_id(try self.validPointer(), tempId)) + } + + /** + Transacts the `transaction` + + This does not commit the transaction. In order to do so, `commit` can be called. + + - Parameter transaction: The EDN string to be transacted. + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the transaction failed. + - Throws: `ResultError.empty` if no `TxReport` is returned from the transact. + + - Returns: The `TxReport` generated by the transact. + */ + func transact(transaction: String) throws -> TxReport { + let result = in_progress_transact(try self.validPointer(), transaction).pointee + return TxReport(raw: try result.unwrap()) + } + + /** + Commits all the transacts that have been performed on this `InProgress`, either directly + or through a Builder. + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the commit failed. + */ + func commit() throws { + defer { + self.raw = nil + } + try in_progress_commit(try self.validPointer()).pointee.tryUnwrap() + } + + /** + Rolls back all the transacts that have been performed on this `InProgress`, either directly + or through a Builder. + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the rollback failed. + */ + func rollback() throws { + defer { + self.raw = nil + } + try in_progress_rollback(try self.validPointer()).pointee.tryUnwrap() + } + + override func cleanup(pointer: OpaquePointer) { + in_progress_destroy(pointer) + } +} diff --git a/sdks/swift/Mentat/Mentat/Transact/InProgressBuilder.swift b/sdks/swift/Mentat/Mentat/Transact/InProgressBuilder.swift new file mode 100644 index 00000000..36cc1a4e --- /dev/null +++ b/sdks/swift/Mentat/Mentat/Transact/InProgressBuilder.swift @@ -0,0 +1,372 @@ +/* Copyright 2018 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. */ + +import Foundation +import MentatStore + +/** + This class wraps a raw pointer that points to a Rust `InProgressBuilder` object. + + `InProgressBuilder` provides a programmatic interface to performing assertions for entities. + It provides functions for adding and retracting values for attributes for an entity within + an in progress transaction. + + The `transact` function will transact the assertions that have been added to the `InProgressBuilder` + and pass back the `TxReport` that was generated by this transact and the `InProgress` that was + used to perform the transact. This enables you to perform further transacts on the same `InProgress` + before committing. + + ``` + let aEntid = txReport.entid(forTempId: "a") + let bEntid = txReport.entid(forTempId: "b") + do { + let builder = try mentat.entityBuilder() + try builder.add(entid: bEntid, keyword: ":foo/boolean", boolean: true) + try builder.add(entid: aEntid, keyword: ":foo/instant", date: newDate) + let (inProgress, report) = try builder.transact() + try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]") + try inProgress.commit() + ... + } catch { + ... + } + ``` + + The `commit` function will transact and commit the assertions that have been added to the `EntityBuilder`. + It will consume the `InProgress` used to perform the transact. It returns the `TxReport` generated by + the transact. After calling `commit`, a new transaction must be started by calling `Mentat.beginTransaction()` + in order to perform further actions. + + ``` + let aEntid = txReport.entid(forTempId: "a") + let bEntid = txReport.entid(forTempId: "b") + do { + let builder = try mentat.entityBuilder(forEntid: aEntid) + try builder.add(entid: bEntid, keyword: ":foo/boolean", boolean: true) + try builder.add(entid: aEntid, keyword: ":foo/instant", date: newDate) + let report = try builder.commit() + ... + } catch { + ... + } + ``` + */ +class InProgressBuilder: OptionalRustObject { + + /** + Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/long`. + */ + func add(entid: Entid, keyword: String, long value: Int64) throws { + try in_progress_builder_add_long(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/ref`. + */ + func add(entid: Entid, keyword: String, reference value: Entid) throws { + try in_progress_builder_add_ref(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/keyword`. + */ + func add(entid: Entid, keyword: String, keyword value: String) throws { + try in_progress_builder_add_keyword(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/boolean`. + */ + func add(entid: Entid, keyword: String, boolean value: Bool) throws { + try in_progress_builder_add_boolean(try self.validPointer(), entid, keyword, value ? 1 : 0).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/double`. + */ + func add(entid: Entid, keyword: String, double value: Double) throws { + try in_progress_builder_add_double(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/instant`. + */ + func add(entid: Entid, keyword: String, date value: Date) throws { + try in_progress_builder_add_timestamp(try self.validPointer(), entid, keyword, value.toMicroseconds()).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/string`. + */ + func add(entid: Entid, keyword: String, string value: String) throws { + try in_progress_builder_add_string(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap() + } + + /** + Asserts the value of attribute `keyword` to be the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be asserted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/uuid`. + */ + func add(entid: Entid, keyword: String, uuid value: UUID) throws { + var rawUuid = value.uuid + let _ = try withUnsafePointer(to: &rawUuid) { uuidPtr in + try in_progress_builder_add_uuid(try self.validPointer(), entid, keyword, uuidPtr).pointee.tryUnwrap() + } + } + + /** + Retracts the value of attribute `keyword` from the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/long`. + */ + func retract(entid: Entid, keyword: String, long value: Int64) throws { + try in_progress_builder_retract_long(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/ref`. + */ + func retract(entid: Entid, keyword: String, reference value: Entid) throws { + try in_progress_builder_retract_ref(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/keyword`. + */ + func retract(entid: Entid, keyword: String, keyword value: String) throws { + try in_progress_builder_retract_keyword(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/boolean`. + */ + func retract(entid: Entid, keyword: String, boolean value: Bool) throws { + try in_progress_builder_retract_boolean(try self.validPointer(), entid, keyword, value ? 1 : 0).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/double`. + */ + func retract(entid: Entid, keyword: String, double value: Double) throws { + try in_progress_builder_retract_double(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/instant`. + */ + func retract(entid: Entid, keyword: String, date value: Date) throws { + try in_progress_builder_retract_timestamp(try self.validPointer(), entid, keyword, value.toMicroseconds()).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/string`. + */ + func retract(entid: Entid, keyword: String, string value: String) throws { + try in_progress_builder_retract_string(try self.validPointer(), entid, keyword, value).pointee.tryUnwrap() + } + + /** + Retracts the value of attribute `keyword` from the provided `value` for entity `entid`. + + - Parameter entid: The `Entid` of the entity to be touched. + - Parameter keyword: The name of the attribute in the format `:namespace/name`. + - Parameter value: The value to be retracted + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if the attribute is not present in the schema or the attribute value type + is not `:db.type/uuid`. + */ + func retract(entid: Entid, keyword: String, uuid value: UUID) throws { + var rawUuid = value.uuid + let _ = try withUnsafePointer(to: &rawUuid) { uuidPtr in + try in_progress_builder_retract_uuid(try self.validPointer(), entid, keyword, uuidPtr).pointee.tryUnwrap() + } + } + + /** + Transacts the added assertions. This consumes the pointer associated with this `InProgressBuilder` + such that no further assertions can be added after the `transact` has completed. To perform + further assertions, use the `InProgress` returned from this function. + + This does not commit the transaction. In order to do so, `commit` can be called on the `InProgress` returned + from this function. + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if an error occured during the execution of the transact. + + - Returns: The current `InProgress` and the `TxReport` generated by the transact. + */ + func transact() throws -> (InProgress, TxReport?) { + defer { + self.raw = nil + } + let result = in_progress_builder_transact(try self.validPointer()).pointee + let inProgress = InProgress(raw: result.inProgress) + guard let report = try result.result.pointee.tryUnwrap() else { + return (inProgress, nil) + } + return (inProgress, TxReport(raw: report)) + } + + /** + Transacts the added assertions and commits. This consumes the pointer associated with this `InProgressBuilder` + and the associated `InProgress` such that no further assertions can be added after the `commit` has completed. + To perform further assertions, a new `InProgress` or `InProgressBuilder` should be created. + + - Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the builder + has already been transacted or committed. + - Throws: `ResultError.error` if an error occured during the execution of the transact. + + - Returns: The `TxReport` generated by the transact. + */ + func commit() throws -> TxReport { + defer { + self.raw = nil + } + return TxReport(raw: try in_progress_builder_commit(try self.validPointer()).pointee.unwrap()) + } + + override func cleanup(pointer: OpaquePointer) { + in_progress_builder_destroy(pointer) + } +} diff --git a/sdks/swift/Mentat/Mentat/Transact/TxReport.swift b/sdks/swift/Mentat/Mentat/Transact/TxReport.swift index 8658158d..8f7ee4c1 100644 --- a/sdks/swift/Mentat/Mentat/Transact/TxReport.swift +++ b/sdks/swift/Mentat/Mentat/Transact/TxReport.swift @@ -13,7 +13,7 @@ import Foundation import MentatStore /** - This class wraps a raw pointer than points to a Rust `TxReport` object. + This class wraps a raw pointer that points to a Rust `TxReport` object. The `TxReport` contains information about a successful Mentat transaction. diff --git a/sdks/swift/Mentat/Mentat/store.h b/sdks/swift/Mentat/Mentat/store.h index 7f86e009..5476adfe 100644 --- a/sdks/swift/Mentat/Mentat/store.h +++ b/sdks/swift/Mentat/Mentat/store.h @@ -60,6 +60,16 @@ struct Option { }; typedef struct Option Option; +/* + A mapping of the InProgressTransactResult repr(C) Rust object. + The memory for this is managed by Swift. + */ +struct InProgressTransactResult { + struct InProgress*_Nonnull inProgress; + struct Result*_Nonnull result; +}; +typedef struct InProgressTransactResult InProgressTransactResult; + /* A Mapping for the ValueType Rust object. */ @@ -100,13 +110,67 @@ void typed_value_list_destroy(struct QueryResultRow* _Nullable obj); void typed_value_list_iter_destroy(struct QueryRowIterator* _Nullable obj); void typed_value_result_set_destroy(struct QueryResultRows* _Nullable obj); void typed_value_result_set_iter_destroy(struct QueryRowsIterator* _Nullable obj); +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); // 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); +struct Result*_Nonnull store_begin_transaction(struct Store*_Nonnull store); +// in progress +struct Result*_Nonnull in_progress_transact(struct InProgress*_Nonnull in_progress, const char* _Nonnull transaction); +struct Result*_Nonnull in_progress_commit(struct InProgress*_Nonnull in_progress); +struct Result*_Nonnull in_progress_rollback(struct InProgress*_Nonnull in_progress); + +// in_progress entity building +struct Result*_Nonnull store_in_progress_builder(struct Store*_Nonnull store); +struct InProgressBuilder*_Nonnull in_progress_builder(struct InProgress*_Nonnull in_progress); +struct EntityBuilder*_Nonnull in_progress_entity_builder_from_temp_id(struct InProgress*_Nonnull in_progress, const char*_Nonnull temp_id); +struct EntityBuilder*_Nonnull in_progress_entity_builder_from_entid(struct InProgress*_Nonnull in_progress, const int64_t entid); +struct Result*_Nonnull in_progress_builder_add_string(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const char*_Nonnull value); +struct Result*_Nonnull in_progress_builder_add_long(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull in_progress_builder_add_ref(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull in_progress_builder_add_keyword(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const char*_Nonnull value); +struct Result*_Nonnull in_progress_builder_add_timestamp(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull in_progress_builder_add_boolean(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int32_t value); +struct Result*_Nonnull in_progress_builder_add_double(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const double value); +struct Result*_Nonnull in_progress_builder_add_uuid(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const uuid_t* _Nonnull value); +struct Result*_Nonnull in_progress_builder_retract_string(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const char*_Nonnull value); +struct Result*_Nonnull in_progress_builder_retract_long(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull in_progress_builder_retract_ref(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull in_progress_builder_retract_keyword(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const char*_Nonnull value); +struct Result*_Nonnull in_progress_builder_retract_timestamp(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull in_progress_builder_retract_boolean(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const int32_t value); +struct Result*_Nonnull in_progress_builder_retract_double(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const double value); +struct Result*_Nonnull in_progress_builder_retract_uuid(struct InProgressBuilder*_Nonnull builder, const int64_t entid, const char*_Nonnull kw, const uuid_t* _Nonnull value); +struct InProgressTransactResult*_Nonnull in_progress_builder_transact(struct InProgressBuilder*_Nonnull builder); +struct Result*_Nonnull in_progress_builder_commit(struct InProgressBuilder*_Nonnull builder); + +// entity building +struct Result*_Nonnull store_entity_builder_from_temp_id(struct Store*_Nonnull store, const char*_Nonnull temp_id); +struct Result*_Nonnull store_entity_builder_from_entid(struct Store*_Nonnull store, const int64_t entid); +struct Result*_Nonnull entity_builder_add_string(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const char*_Nonnull value); +struct Result*_Nonnull entity_builder_add_long(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull entity_builder_add_ref(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull entity_builder_add_keyword(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const char*_Nonnull value); +struct Result*_Nonnull entity_builder_add_boolean(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int32_t value); +struct Result*_Nonnull entity_builder_add_double(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const double value); +struct Result*_Nonnull entity_builder_add_timestamp(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull entity_builder_add_uuid(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const uuid_t* _Nonnull value); +struct Result*_Nonnull entity_builder_retract_string(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const char*_Nonnull value); +struct Result*_Nonnull entity_builder_retract_long(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull entity_builder_retract_ref(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull entity_builder_retract_keyword(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const char*_Nonnull value); +struct Result*_Nonnull entity_builder_retract_boolean(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int32_t value); +struct Result*_Nonnull entity_builder_retract_double(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const double value); +struct Result*_Nonnull entity_builder_retract_timestamp(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const int64_t value); +struct Result*_Nonnull entity_builder_retract_uuid(struct EntityBuilder*_Nonnull builder, const char*_Nonnull kw, const uuid_t* _Nonnull value); +struct InProgressTransactResult*_Nonnull entity_builder_transact(struct InProgressBuilder*_Nonnull builder); +struct Result*_Nonnull entity_builder_commit(struct EntityBuilder*_Nonnull builder); // Sync struct Result*_Nonnull store_sync(struct Store*_Nonnull store, const char* _Nonnull user_uuid, const char* _Nonnull server_uri); diff --git a/sdks/swift/Mentat/MentatTests/MentatTests.swift b/sdks/swift/Mentat/MentatTests/MentatTests.swift index 3587e559..026ffff7 100644 --- a/sdks/swift/Mentat/MentatTests/MentatTests.swift +++ b/sdks/swift/Mentat/MentatTests/MentatTests.swift @@ -17,12 +17,12 @@ class MentatTests: XCTestCase { var citiesSchema: String? var seattleData: String? var store: Mentat? - + override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } - + override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() @@ -120,7 +120,8 @@ class MentatTests: XCTestCase { [:db/add "u" :db/cardinality :db.cardinality/one] ] """ - let report = try mentat.transact(transaction: schema) + let transaction = try mentat.beginTransaction(); + let report = try transaction.transact(transaction: schema) let stringEntid = report.entid(forTempId: "s")! let data = """ @@ -142,7 +143,8 @@ class MentatTests: XCTestCase { [:db/add "b" :foo/uuid #uuid "4cb3f828-752d-497a-90c9-b1fd516d5644"] ] """ - let dataReport = try mentat.transact(transaction: data) + let dataReport = try transaction.transact(transaction: data) + try transaction.commit(); return (report, dataReport) } catch { assertionFailure(error.localizedDescription) @@ -317,7 +319,7 @@ class MentatTests: XCTestCase { guard let rows = relResult else { return assertionFailure("No results received") } - + var i = 0 rows.forEach({ (row) in let (name, category) = expectedResults[i] @@ -760,6 +762,418 @@ class MentatTests: XCTestCase { } } + func test3InProgressTransact() { + let mentat = Mentat() + let (_, report) = self.populateWithTypesSchema(mentat: mentat) + XCTAssertNotNil(report) + } + + func testInProgressRollback() { + let mentat = Mentat() + let (_, report) = self.populateWithTypesSchema(mentat: mentat) + XCTAssertNotNil(report) + let aEntid = report!.entid(forTempId: "a")! + + let preLongValue = try! mentat.value(forAttribute: ":foo/long", ofEntity: aEntid) + XCTAssertEqual(25, preLongValue?.asLong()) + + let inProgress = try! mentat.beginTransaction() + XCTAssertNoThrow(try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]")) + XCTAssertNoThrow(try inProgress.rollback()) + + let postLongValue = try! mentat.value(forAttribute: ":foo/long", ofEntity: aEntid) + XCTAssertEqual(25, postLongValue?.asLong()) + + } + + func testInProgressEntityBuilder() { + let mentat = Mentat() + let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) + let bEntid = dataReport!.entid(forTempId: "b")! + let longEntid = schemaReport!.entid(forTempId: "l")! + let stringEntid = schemaReport!.entid(forTempId: "s")! + // test that the values are as expected + let query = """ + [:find [?b ?i ?u ?l ?d ?s ?k ?r] + :in ?e + :where [?e :foo/boolean ?b] + [?e :foo/instant ?i] + [?e :foo/uuid ?u] + [?e :foo/long ?l] + [?e :foo/double ?d] + [?e :foo/string ?s] + [?e :foo/keyword ?k] + [?e :foo/ref ?r]] + """ + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in + XCTAssertNotNil(result) + XCTAssertEqual(false, result?.asBool(index: 0)) + + let previousDate = formatter.date(from: "2018-01-01T11:00:00+00:00") + XCTAssertEqual(previousDate, result?.asDate(index: 1)) + + let previousUuid = UUID(uuidString: "4cb3f828-752d-497a-90c9-b1fd516d5644")! + XCTAssertEqual(previousUuid, result?.asUUID(index: 2)) + + XCTAssertEqual(50, result?.asLong(index: 3)) + XCTAssertEqual(22.46, result?.asDouble(index: 4)) + XCTAssertEqual("Silence is worse; all truths that are kept silent become poisonous.", result?.asString(index: 5)) + XCTAssertEqual(":foo/string", result?.asKeyword(index: 6)) + XCTAssertEqual(stringEntid, result?.asEntid(index: 7)) + }) + + let builder = try! mentat.entityBuilder() + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/boolean", boolean: true)) + let newDate = Date() + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/instant", date: newDate)) + let newUUID = UUID() + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/uuid", uuid: newUUID)) + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/long", long: 75)) + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/double", double: 81.3)) + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/string", string: "Become who you are!")) + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/keyword", keyword: ":foo/long")) + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/ref", reference: longEntid)) + XCTAssertNoThrow(try builder.commit()) + + // test that the values have changed + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in + XCTAssertNotNil(result) + XCTAssertEqual(true, result?.asBool(index: 0)) + XCTAssertEqual(formatter.string(from: newDate), formatter.string(from: result!.asDate(index: 1))) + XCTAssertEqual(newUUID, result?.asUUID(index: 2)) + XCTAssertEqual(75, result?.asLong(index: 3)) + XCTAssertEqual(81.3, result?.asDouble(index: 4)) + XCTAssertEqual("Become who you are!", result?.asString(index: 5)) + XCTAssertEqual(":foo/long", result?.asKeyword(index: 6)) + XCTAssertEqual(longEntid, result?.asEntid(index: 7)) + }) + } + + func testEntityBuilderForEntid() { + let mentat = Mentat() + let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) + let bEntid = dataReport!.entid(forTempId: "b")! + let longEntid = schemaReport!.entid(forTempId: "l")! + let stringEntid = schemaReport!.entid(forTempId: "s")! + // test that the values are as expected + let query = """ + [:find [?b ?i ?u ?l ?d ?s ?k ?r] + :in ?e + :where [?e :foo/boolean ?b] + [?e :foo/instant ?i] + [?e :foo/uuid ?u] + [?e :foo/long ?l] + [?e :foo/double ?d] + [?e :foo/string ?s] + [?e :foo/keyword ?k] + [?e :foo/ref ?r]] + """ + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in + XCTAssertNotNil(result) + XCTAssertEqual(false, result?.asBool(index: 0)) + + let previousDate = formatter.date(from: "2018-01-01T11:00:00+00:00") + XCTAssertEqual(previousDate, result?.asDate(index: 1)) + + let previousUuid = UUID(uuidString: "4cb3f828-752d-497a-90c9-b1fd516d5644")! + XCTAssertEqual(previousUuid, result?.asUUID(index: 2)) + + XCTAssertEqual(50, result?.asLong(index: 3)) + XCTAssertEqual(22.46, result?.asDouble(index: 4)) + XCTAssertEqual("Silence is worse; all truths that are kept silent become poisonous.", result?.asString(index: 5)) + XCTAssertEqual(":foo/string", result?.asKeyword(index: 6)) + XCTAssertEqual(stringEntid, result?.asEntid(index: 7)) + }) + + let builder = try! mentat.entityBuilder(forEntid: bEntid) + XCTAssertNoThrow(try builder.add(keyword: ":foo/boolean", boolean: true)) + let newDate = Date() + XCTAssertNoThrow(try builder.add(keyword: ":foo/instant", date: newDate)) + let newUUID = UUID() + XCTAssertNoThrow(try builder.add(keyword: ":foo/uuid", uuid: newUUID)) + XCTAssertNoThrow(try builder.add(keyword: ":foo/long", long: 75)) + XCTAssertNoThrow(try builder.add(keyword: ":foo/double", double: 81.3)) + XCTAssertNoThrow(try builder.add(keyword: ":foo/string", string: "Become who you are!")) + XCTAssertNoThrow(try builder.add(keyword: ":foo/keyword", keyword: ":foo/long")) + XCTAssertNoThrow(try builder.add(keyword: ":foo/ref", reference: longEntid)) + XCTAssertNoThrow(try builder.commit()) + + // test that the values have changed + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in + XCTAssertNotNil(result) + XCTAssertEqual(true, result?.asBool(index: 0)) + XCTAssertEqual(formatter.string(from: newDate), formatter.string(from: result!.asDate(index: 1))) + XCTAssertEqual(newUUID, result?.asUUID(index: 2)) + XCTAssertEqual(75, result?.asLong(index: 3)) + XCTAssertEqual(81.3, result?.asDouble(index: 4)) + XCTAssertEqual("Become who you are!", result?.asString(index: 5)) + XCTAssertEqual(":foo/long", result?.asKeyword(index: 6)) + XCTAssertEqual(longEntid, result?.asEntid(index: 7)) + }) + } + + func testEntityBuilderForTempid() { + let mentat = Mentat() + let (schemaReport, _) = self.populateWithTypesSchema(mentat: mentat) + let longEntid = schemaReport!.entid(forTempId: "l")! + // test that the values are as expected + let query = """ + [:find [?b ?i ?u ?l ?d ?s ?k ?r] + :in ?e + :where [?e :foo/boolean ?b] + [?e :foo/instant ?i] + [?e :foo/uuid ?u] + [?e :foo/long ?l] + [?e :foo/double ?d] + [?e :foo/string ?s] + [?e :foo/keyword ?k] + [?e :foo/ref ?r]] + """ + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + + let builder = try! mentat.entityBuilder(forTempId: "c") + XCTAssertNoThrow(try builder.add(keyword: ":foo/boolean", boolean: true)) + let newDate = Date() + XCTAssertNoThrow(try builder.add(keyword: ":foo/instant", date: newDate)) + let newUUID = UUID() + XCTAssertNoThrow(try builder.add(keyword: ":foo/uuid", uuid: newUUID)) + XCTAssertNoThrow(try builder.add(keyword: ":foo/long", long: 75)) + XCTAssertNoThrow(try builder.add(keyword: ":foo/double", double: 81.3)) + XCTAssertNoThrow(try builder.add(keyword: ":foo/string", string: "Become who you are!")) + XCTAssertNoThrow(try builder.add(keyword: ":foo/keyword", keyword: ":foo/long")) + XCTAssertNoThrow(try builder.add(keyword: ":foo/ref", reference: longEntid)) + let report = try! builder.commit() + let cEntid = report.entid(forTempId: "c")! + // test that the values have changed + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: cEntid).runTuple { (result) in + XCTAssertNotNil(result) + XCTAssertEqual(true, result?.asBool(index: 0)) + XCTAssertEqual(formatter.string(from: newDate), formatter.string(from: result!.asDate(index: 1))) + XCTAssertEqual(newUUID, result?.asUUID(index: 2)) + XCTAssertEqual(75, result?.asLong(index: 3)) + XCTAssertEqual(81.3, result?.asDouble(index: 4)) + XCTAssertEqual("Become who you are!", result?.asString(index: 5)) + XCTAssertEqual(":foo/long", result?.asKeyword(index: 6)) + XCTAssertEqual(longEntid, result?.asEntid(index: 7)) + }) + } + + func testInProgressBuilderTransact() { + let mentat = Mentat() + let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) + let aEntid = dataReport!.entid(forTempId: "a")! + let bEntid = dataReport!.entid(forTempId: "b")! + let longEntid = schemaReport!.entid(forTempId: "l")! + // test that the values are as expected + let query = """ + [:find [?b ?i ?u ?l ?d ?s ?k ?r] + :in ?e + :where [?e :foo/boolean ?b] + [?e :foo/instant ?i] + [?e :foo/uuid ?u] + [?e :foo/long ?l] + [?e :foo/double ?d] + [?e :foo/string ?s] + [?e :foo/keyword ?k] + [?e :foo/ref ?r]] + """ + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + + let builder = try! mentat.entityBuilder() + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/boolean", boolean: true)) + let newDate = Date() + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/instant", date: newDate)) + let newUUID = UUID() + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/uuid", uuid: newUUID)) + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/long", long: 75)) + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/double", double: 81.3)) + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/string", string: "Become who you are!")) + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/keyword", keyword: ":foo/long")) + XCTAssertNoThrow(try builder.add(entid: bEntid, keyword: ":foo/ref", reference: longEntid)) + let (inProgress, report) = try! builder.transact() + XCTAssertNotNil(inProgress) + XCTAssertNotNil(report) + XCTAssertNoThrow(try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]")) + XCTAssertNoThrow(try inProgress.commit()) + + // test that the values have changed + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in + XCTAssertNotNil(result) + XCTAssertEqual(true, result?.asBool(index: 0)) + XCTAssertEqual(formatter.string(from: newDate), formatter.string(from: result!.asDate(index: 1))) + XCTAssertEqual(newUUID, result?.asUUID(index: 2)) + XCTAssertEqual(75, result?.asLong(index: 3)) + XCTAssertEqual(81.3, result?.asDouble(index: 4)) + XCTAssertEqual("Become who you are!", result?.asString(index: 5)) + XCTAssertEqual(":foo/long", result?.asKeyword(index: 6)) + XCTAssertEqual(longEntid, result?.asEntid(index: 7)) + }) + + let longValue = try! mentat.value(forAttribute: ":foo/long", ofEntity: aEntid) + XCTAssertEqual(22, longValue?.asLong()) + } + + func testEntityBuilderTransact() { + let mentat = Mentat() + let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) + let aEntid = dataReport!.entid(forTempId: "a")! + let bEntid = dataReport!.entid(forTempId: "b")! + let longEntid = schemaReport!.entid(forTempId: "l")! + // test that the values are as expected + let query = """ + [:find [?b ?i ?u ?l ?d ?s ?k ?r] + :in ?e + :where [?e :foo/boolean ?b] + [?e :foo/instant ?i] + [?e :foo/uuid ?u] + [?e :foo/long ?l] + [?e :foo/double ?d] + [?e :foo/string ?s] + [?e :foo/keyword ?k] + [?e :foo/ref ?r]] + """ + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + + let builder = try! mentat.entityBuilder(forEntid: bEntid) + XCTAssertNoThrow(try builder.add(keyword: ":foo/boolean", boolean: true)) + let newDate = Date() + XCTAssertNoThrow(try builder.add(keyword: ":foo/instant", date: newDate)) + let newUUID = UUID() + XCTAssertNoThrow(try builder.add(keyword: ":foo/uuid", uuid: newUUID)) + XCTAssertNoThrow(try builder.add(keyword: ":foo/long", long: 75)) + XCTAssertNoThrow(try builder.add(keyword: ":foo/double", double: 81.3)) + XCTAssertNoThrow(try builder.add(keyword: ":foo/string", string: "Become who you are!")) + XCTAssertNoThrow(try builder.add(keyword: ":foo/keyword", keyword: ":foo/long")) + XCTAssertNoThrow(try builder.add(keyword: ":foo/ref", reference: longEntid)) + let (inProgress, report) = try! builder.transact() + XCTAssertNotNil(inProgress) + XCTAssertNotNil(report) + XCTAssertNoThrow(try inProgress.transact(transaction: "[[:db/add \(aEntid) :foo/long 22]]")) + XCTAssertNoThrow(try inProgress.commit()) + + // test that the values have changed + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in + XCTAssertNotNil(result) + XCTAssertEqual(true, result?.asBool(index: 0)) + XCTAssertEqual(formatter.string(from: newDate), formatter.string(from: result!.asDate(index: 1))) + XCTAssertEqual(newUUID, result?.asUUID(index: 2)) + XCTAssertEqual(75, result?.asLong(index: 3)) + XCTAssertEqual(81.3, result?.asDouble(index: 4)) + XCTAssertEqual("Become who you are!", result?.asString(index: 5)) + XCTAssertEqual(":foo/long", result?.asKeyword(index: 6)) + XCTAssertEqual(longEntid, result?.asEntid(index: 7)) + }) + + let longValue = try! mentat.value(forAttribute: ":foo/long", ofEntity: aEntid) + XCTAssertEqual(22, longValue?.asLong()) + } + + func testEntityBuilderRetract() { + let mentat = Mentat() + let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) + let bEntid = dataReport!.entid(forTempId: "b")! + let stringEntid = schemaReport!.entid(forTempId: "s")! + // test that the values are as expected + let query = """ + [:find [?b ?i ?u ?l ?d ?s ?k ?r] + :in ?e + :where [?e :foo/boolean ?b] + [?e :foo/instant ?i] + [?e :foo/uuid ?u] + [?e :foo/long ?l] + [?e :foo/double ?d] + [?e :foo/string ?s] + [?e :foo/keyword ?k] + [?e :foo/ref ?r]] + """ + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + let previousDate = formatter.date(from: "2018-01-01T11:00:00+00:00")! + let previousUuid = UUID(uuidString: "4cb3f828-752d-497a-90c9-b1fd516d5644")! + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in + XCTAssertNotNil(result) + XCTAssertEqual(false, result?.asBool(index: 0)) + XCTAssertEqual(previousDate, result?.asDate(index: 1)) + XCTAssertEqual(previousUuid, result?.asUUID(index: 2)) + XCTAssertEqual(50, result?.asLong(index: 3)) + XCTAssertEqual(22.46, result?.asDouble(index: 4)) + XCTAssertEqual("Silence is worse; all truths that are kept silent become poisonous.", result?.asString(index: 5)) + XCTAssertEqual(":foo/string", result?.asKeyword(index: 6)) + XCTAssertEqual(stringEntid, result?.asEntid(index: 7)) + }) + + let builder = try! mentat.entityBuilder(forEntid: bEntid) + XCTAssertNoThrow(try builder.retract(keyword: ":foo/boolean", boolean: false)) + XCTAssertNoThrow(try builder.retract(keyword: ":foo/instant", date: previousDate)) + XCTAssertNoThrow(try builder.retract(keyword: ":foo/uuid", uuid: previousUuid)) + XCTAssertNoThrow(try builder.retract(keyword: ":foo/long", long: 50)) + XCTAssertNoThrow(try builder.retract(keyword: ":foo/double", double: 22.46)) + XCTAssertNoThrow(try builder.retract(keyword: ":foo/string", string: "Silence is worse; all truths that are kept silent become poisonous.")) + XCTAssertNoThrow(try builder.retract(keyword: ":foo/keyword", keyword: ":foo/string")) + XCTAssertNoThrow(try builder.retract(keyword: ":foo/ref", reference: stringEntid)) + XCTAssertNoThrow(try builder.commit()) + + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in + XCTAssertNil(result) + }) + } + + func testInProgressEntityBuilderRetract() { + let mentat = Mentat() + let (schemaReport, dataReport) = self.populateWithTypesSchema(mentat: mentat) + let bEntid = dataReport!.entid(forTempId: "b")! + let stringEntid = schemaReport!.entid(forTempId: "s")! + // test that the values are as expected + let query = """ + [:find [?b ?i ?u ?l ?d ?s ?k ?r] + :in ?e + :where [?e :foo/boolean ?b] + [?e :foo/instant ?i] + [?e :foo/uuid ?u] + [?e :foo/long ?l] + [?e :foo/double ?d] + [?e :foo/string ?s] + [?e :foo/keyword ?k] + [?e :foo/ref ?r]] + """ + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + let previousDate = formatter.date(from: "2018-01-01T11:00:00+00:00")! + let previousUuid = UUID(uuidString: "4cb3f828-752d-497a-90c9-b1fd516d5644")! + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in + XCTAssertNotNil(result) + XCTAssertEqual(false, result?.asBool(index: 0)) + XCTAssertEqual(previousDate, result?.asDate(index: 1)) + XCTAssertEqual(previousUuid, result?.asUUID(index: 2)) + XCTAssertEqual(50, result?.asLong(index: 3)) + XCTAssertEqual(22.46, result?.asDouble(index: 4)) + XCTAssertEqual("Silence is worse; all truths that are kept silent become poisonous.", result?.asString(index: 5)) + XCTAssertEqual(":foo/string", result?.asKeyword(index: 6)) + XCTAssertEqual(stringEntid, result?.asEntid(index: 7)) + }) + + let builder = try! mentat.entityBuilder() + XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/boolean", boolean: false)) + XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/instant", date: previousDate)) + XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/uuid", uuid: previousUuid)) + XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/long", long: 50)) + XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/double", double: 22.46)) + XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/string", string: "Silence is worse; all truths that are kept silent become poisonous.")) + XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/keyword", keyword: ":foo/string")) + XCTAssertNoThrow(try builder.retract(entid: bEntid, keyword: ":foo/ref", reference: stringEntid)) + XCTAssertNoThrow(try builder.commit()) + + XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?e", toReference: bEntid).runTuple { (result) in + XCTAssertNil(result) + }) + } // TODO: Add tests for transaction observation }