Implement InProgress transactions and InProgress and Entity builders on iOS

This commit is contained in:
Emily Toop 2018-04-26 17:28:14 +01:00
parent ed5427253b
commit 38c1a93712
10 changed files with 1462 additions and 15 deletions

View file

@ -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 = "<group>"; };
7B64E44B209094510063909F /* EntityBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityBuilder.swift; sourceTree = "<group>"; };
7B64E44C209094520063909F /* InProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InProgress.swift; sourceTree = "<group>"; };
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 = "<group>"; };
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;
};

View file

@ -37,14 +37,6 @@
BlueprintName = "MentatTests"
ReferencedContainer = "container:Mentat.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "MentatTests/testBindBoolean()">
</Test>
<Test
Identifier = "MentatTests/testBindDate()">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
<MacroExpansion>

View file

@ -1,4 +1,3 @@
//
/* Copyright 2018 Mozilla
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use

View file

@ -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`.

View file

@ -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<InProgressBuilder>` 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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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.

View file

@ -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);

View file

@ -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
}