Add wrapper classes for Rust FFI

This commit is contained in:
Emily Toop 2018-04-24 11:50:24 +01:00
parent d193c9c79e
commit 5a1102bf14
12 changed files with 1285 additions and 12 deletions

View file

@ -7,9 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
7B744837208DF20D006CFFB0 /* EntityBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B744836208DF20D006CFFB0 /* EntityBuilder.swift */; };
7B744839208DF2E1006CFFB0 /* InProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B744838208DF2E1006CFFB0 /* InProgress.swift */; };
7B74483B208DF2F9006CFFB0 /* InProgressBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B74483A208DF2F9006CFFB0 /* InProgressBuilder.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 */; };
@ -42,9 +39,6 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
7B744836208DF20D006CFFB0 /* EntityBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityBuilder.swift; sourceTree = "<group>"; };
7B744838208DF2E1006CFFB0 /* InProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InProgress.swift; sourceTree = "<group>"; };
7B74483A208DF2F9006CFFB0 /* InProgressBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InProgressBuilder.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; };
@ -203,9 +197,6 @@
isa = PBXGroup;
children = (
7BEB7D2B207D03DA000369AD /* TxReport.swift */,
7B744836208DF20D006CFFB0 /* EntityBuilder.swift */,
7B744838208DF2E1006CFFB0 /* InProgress.swift */,
7B74483A208DF2F9006CFFB0 /* InProgressBuilder.swift */,
);
path = Transact;
sourceTree = "<group>";
@ -324,15 +315,12 @@
7BDB96B32077C38E009D0651 /* RustObject.swift in Sources */,
7BDB96C62077D347009D0651 /* Date+Int64.swift in Sources */,
7BEB7D2C207D03DA000369AD /* TxReport.swift in Sources */,
7B744839208DF2E1006CFFB0 /* InProgress.swift in Sources */,
7BDB96B42077C38E009D0651 /* OptionalRustObject.swift in Sources */,
7B74483B208DF2F9006CFFB0 /* InProgressBuilder.swift in Sources */,
7BDB96B22077C38E009D0651 /* RelResult.swift in Sources */,
7BDB96AF2077C38E009D0651 /* Query.swift in Sources */,
7BDB96CC207B7684009D0651 /* Errors.swift in Sources */,
7BDB96B02077C38E009D0651 /* Mentat.swift in Sources */,
7BDB96B72077C38E009D0651 /* TypedValue.swift in Sources */,
7B744837208DF20D006CFFB0 /* EntityBuilder.swift in Sources */,
7BDB96B52077C38E009D0651 /* TupleResult.swift in Sources */,
7B74483D208DF667006CFFB0 /* Result+Unwrap.swift in Sources */,
);

View file

@ -0,0 +1,181 @@
/* 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
/**
A wrapper around Mentat's `TypedValue` Rust object. This class wraps a raw pointer to a Rust `TypedValue`
struct and provides accessors to the values according to expected result type.
As the FFI functions for fetching values are consuming, this class keeps a copy of the result internally after
fetching so that the value can be referenced several times.
Also, due to the consuming nature of the FFI layer, this class also manages it's raw pointer, nilling it after calling the
FFI conversion function so that the underlying base class can manage cleanup.
*/
class TypedValue: OptionalRustObject {
private var value: Any?
/**
The `ValueType` for this `TypedValue`.
- Returns: The `ValueType` for this `TypedValue`.
*/
var valueType: ValueType {
return typed_value_value_type(self.raw!)
}
private func isConsumed() -> Bool {
return self.raw == nil
}
/**
This value as a `Int64`. This function will panic if the `ValueType` of this `TypedValue`
is not a `Long`
- Returns: the value of this `TypedValue` as a `Int64`
*/
func asLong() -> Int64 {
defer {
self.raw = nil
}
if !self.isConsumed() {
self.value = typed_value_as_long(self.raw!)
}
return self.value as! Int64
}
/**
This value as an `Entid`. This function will panic if the `ValueType` of this `TypedValue`
is not a `Ref`
- Returns: the value of this `TypedValue` as an `Entid`
*/
func asEntid() -> Entid {
defer {
self.raw = nil
}
if !self.isConsumed() {
self.value = typed_value_as_entid(self.raw!)
}
return self.value as! Entid
}
/**
This value as a keyword `String`. This function will panic if the `ValueType` of this `TypedValue`
is not a `Keyword`
- Returns: the value of this `TypedValue` as a keyword `String`
*/
func asKeyword() -> String {
defer {
self.raw = nil
}
if !self.isConsumed() {
self.value = String(cString: typed_value_as_kw(self.raw!))
}
return self.value as! String
}
/**
This value as a `Bool`. This function will panic if the `ValueType` of this `TypedValue`
is not a `Boolean`
- Returns: the value of this `TypedValue` as a `Bool`
*/
func asBool() -> Bool {
defer {
self.raw = nil
}
if !self.isConsumed() {
let v = typed_value_as_boolean(self.raw!)
self.value = v > 0
}
return self.value as! Bool
}
/**
This value as a `Double`. This function will panic if the `ValueType` of this `TypedValue`
is not a `Double`
- Returns: the value of this `TypedValue` as a `Double`
*/
func asDouble() -> Double {
defer {
self.raw = nil
}
if !self.isConsumed() {
self.value = typed_value_as_double(self.raw!)
}
return self.value as! Double
}
/**
This value as a `Date`. This function will panic if the `ValueType` of this `TypedValue`
is not a `Instant`
- Returns: the value of this `TypedValue` as a `Date`
*/
func asDate() -> Date {
defer {
self.raw = nil
}
if !self.isConsumed() {
let timestamp = typed_value_as_timestamp(self.raw!)
self.value = Date(timeIntervalSince1970: TimeInterval(timestamp))
}
return self.value as! Date
}
/**
This value as a `String`. This function will panic if the `ValueType` of this `TypedValue`
is not a `String`
- Returns: the value of this `TypedValue` as a `String`
*/
func asString() -> String {
defer {
self.raw = nil
}
if !self.isConsumed() {
self.value = String(cString: typed_value_as_string(self.raw!))
}
return self.value as! String
}
/**
This value as a `UUID`. This function will panic if the `ValueType` of this `TypedValue`
is not a `Uuid`
- Returns: the value of this `TypedValue` as a `UUID?`. If the `UUID` is not valid then this function returns nil.
*/
func asUUID() -> UUID? {
defer {
self.raw = nil
}
if !self.isConsumed() {
let bytes = typed_value_as_uuid(self.raw!).pointee
self.value = UUID(uuid: bytes)
}
return self.value as! UUID?
}
override func cleanup(pointer: OpaquePointer) {
typed_value_destroy(pointer)
}
}

View file

@ -0,0 +1,30 @@
//
/* 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
public enum QueryError: Error {
case invalidKeyword(message: String)
case executionFailed(message: String)
}
public struct MentatError: Error {
let message: String
}
public enum PointerError: Error {
case pointerConsumed
}
public enum ResultError: Error {
case error(message: String)
case empty
}

View file

@ -0,0 +1,16 @@
///* This Source Code Form is subject to the terms of the Mozilla Public
// * License, v. 2.0. If a copy of the MPL was not distributed with this
// * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
extension Date {
/**
This `Date` as microseconds.
- Returns: The `timeIntervalSince1970` in microseconds
*/
func toMicroseconds() -> Int64 {
return Int64(self.timeIntervalSince1970 * 1_000_000)
}
}

View file

@ -0,0 +1,50 @@
/* 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
extension Result {
/**
Force unwraps a result.
Expects there to be a value attached and throws an error is there is not.
- Throws: `ResultError.error` if the result contains an error
- Throws: `ResultError.empty` if the result contains no error but also no result.
- Returns: The pointer to the successful result value.
*/
@discardableResult public func unwrap() throws -> UnsafeMutableRawPointer {
guard let success = self.ok else {
if let error = self.err {
throw ResultError.error(message: String(cString: error))
}
throw ResultError.empty
}
return success
}
/**
Unwraps an optional result, yielding either a successful value or a nil.
- Throws: `ResultError.error` if the result contains an error
- Returns: The pointer to the successful result value, or nil if no value is present.
*/
@discardableResult public func tryUnwrap() throws -> UnsafeMutableRawPointer? {
guard let success = self.ok else {
if let error = self.err {
throw ResultError.error(message: String(cString: error))
}
return nil
}
return success
}
}

View file

@ -0,0 +1,170 @@
/* 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
typealias Entid = Int64
/**
Protocol to be implemenented by any object that wishes to register for transaction observation
*/
protocol Observing {
func transactionDidOccur(key: String, reports: [TxChange])
}
/**
Protocol to be implemented by any object that provides an interface to Mentat's transaction observers.
*/
protocol Observable {
func register(key: String, observer: Observing, attributes: [String])
func unregister(key: String)
}
/**
The primary class for accessing Mentat's API.
This class provides all of the basic API that can be found in Mentat's Store struct.
The raw pointer it holds is a pointer to a Store.
*/
class Mentat: RustObject {
fileprivate static var observers = [String: Observing]()
/**
Create a new Mentat with the provided pointer to a Mentat Store
- Parameter raw: A pointer to a Mentat Store.
*/
required override init(raw: OpaquePointer) {
super.init(raw: raw)
}
/**
Open a connection to a Store in a given location.
If the store does not already exist, one will be created.
- Parameter storeURI: The URI as a String of the store to open.
If no store URI is provided, an in-memory store will be opened.
*/
convenience init(storeURI: String = "") {
self.init(raw: store_open(storeURI))
}
/**
Simple transact of an EDN string.
- Parameter transaction: The string, as EDN, to be transacted
- Throws: `MentatError` if the an error occured during the transaction, or the TxReport is nil.
- Returns: The `TxReport` of the completed transaction
*/
func transact(transaction: String) throws -> TxReport {
let result = store_transact(self.raw, transaction).pointee
return TxReport(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`.
- Returns: The `Entid` associated with the attribute.
*/
func entidForAttribute(attribute: String) -> Entid {
return Entid(store_entid_for_attribute(self.raw, attribute))
}
/**
Start a query.
- Parameter query: The string represeting the the query to be executed.
- Returns: The `Query` representing the query that can be executed.
*/
func query(query: String) -> Query {
return Query(raw: store_query(self.raw, query))
}
/**
Retrieve a single value of an attribute for an Entity
- Parameter attribute: The string the attribute whose value is to be returned.
The string is represented as `:namespace/name`.
- Parameter entid: The `Entid` of the entity we want the value from.
- Returns: The `TypedValue` containing the value of the attribute for the entity.
*/
func value(forAttribute attribute: String, ofEntity entid: Entid) throws -> TypedValue? {
let result = store_value_for_attribute(self.raw, entid, attribute).pointee
return TypedValue(raw: try result.unwrap())
}
// Destroys the pointer by passing it back into Rust to be cleaned up
override func cleanup(pointer: OpaquePointer) {
store_destroy(pointer)
}
}
/**
Set up `Mentat` to provide an interface to Mentat's transaction observation
*/
extension Mentat: Observable {
/**
Register an `Observing` and a set of attributes to observer for transaction observation.
The `transactionDidOccur(String: [TxChange]:)` function is called when a transaction
occurs in the `Store` that this `Mentat` is connected to that affects the attributes that an
`Observing` has registered for.
- Parameter key: `String` representing an identifier for the `Observing`.
- Parameter observer: The `Observing` to be notified when a transaction occurs.
- Parameter attributes: An `Array` of `Strings` representing the attributes that the `Observing`
wishes to be notified about if they are referenced in a transaction.
*/
func register(key: String, observer: Observing, attributes: [String]) {
let attrEntIds = attributes.map({ (kw) -> Entid in
let entid = Entid(self.entidForAttribute(attribute: kw));
return entid
})
let ptr = UnsafeMutablePointer<Entid>.allocate(capacity: attrEntIds.count)
let entidPointer = UnsafeMutableBufferPointer(start: ptr, count: attrEntIds.count)
var _ = entidPointer.initialize(from: attrEntIds)
guard let firstElement = entidPointer.baseAddress else {
return
}
Mentat.observers[key] = observer
store_register_observer(self.raw, key, firstElement, Entid(attributes.count), transactionObserverCallback)
}
/**
Unregister the `Observing` that was registered with the provided key such that it will no longer be called
if a transaction occurs that affects the attributes that `Observing` was registered to observe.
The `Observing` will need to re-register if it wants to start observing again.
- Parameter key: `String` representing an identifier for the `Observing`.
*/
func unregister(key: String) {
Mentat.observers.removeValue(forKey: key)
store_unregister_observer(self.raw, key)
}
}
/**
This function needs to be static as callbacks passed into Rust from Swift cannot contain state. Therefore the observers are static, as is
the function that we pass into Rust to receive the callback.
*/
private func transactionObserverCallback(key: UnsafePointer<CChar>, reports: UnsafePointer<TxChangeList>) {
let key = String(cString: key)
guard let observer = Mentat.observers[key] else { return }
DispatchQueue.global(qos: .background).async {
observer.transactionDidOccur(key: key, reports: [TxChange]())
}
}

View file

@ -0,0 +1,337 @@
/* 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 allows you to contruct a query, bind values to variables and run those queries against a mentat DB.
This class cannot be created directly, but must be created through `Mentat.query(String:)`.
The types of values you can bind are
- `Int64`
- `Entid`
- `Keyword`
- `Bool`
- `Double`
- `Date`
- `String`
- `UUID`.
Each bound variable must have a corresponding value in the query string used to create this query.
```
let query = """
[:find ?name ?cat
:in ?type
:where
[?c :community/name ?name]
[?c :community/type ?type]
[?c :community/category ?cat]]
"""
mentat.query(query: query)
.bind(varName: "?type", toKeyword: ":community.type/website")
.run { result in
...
}
```
Queries can be run and the results returned in a number of different formats. Individual result values are returned as `TypedValues` and
the format differences relate to the number and structure of those values. The result format is related to the format provided in the query string.
- `Rel` - This is the default `run` function and returns a list of rows of values. Queries that wish to have `Rel` results should format their query strings:
```
let query = """
[: find ?a ?b ?c
: where ... ]
"""
mentat.query(query: query)
.run { result in
...
}
```
- `Scalar` - This returns a single value as a result. This can be optional, as the value may not be present. Queries that wish to have `Scalar` results should format their query strings:
```
let query = """
[: find ?a .
: where ... ]
"""
mentat.query(query: query)
.runScalar { result in
...
}
```
- `Coll` - This returns a list of single values as a result. Queries that wish to have `Coll` results should format their query strings:
```
let query = """
[: find [?a ...]
: where ... ]
"""
mentat.query(query: query)
.runColl { result in
...
}
```
- `Tuple` - This returns a single row of values. Queries that wish to have `Tuple` results should format their query strings:
```
let query = """
[: find [?a ?b ?c]
: where ... ]
"""
mentat.query(query: query)
.runTuple { result in
...
}
```
*/
class Query: OptionalRustObject {
/**
Binds a `Int64` value to the provided variable name.
- Parameter varName: The name of the variable in the format `?name`.
- Parameter value: The value to be bound
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
- Returns: This `Query` such that further function can be called.
*/
func bind(varName: String, toLong value: Int64) throws -> Query {
query_builder_bind_long(try! self.validPointer(), varName, value)
return self
}
/**
Binds a `Entid` value to the provided variable name.
- Parameter varName: The name of the variable in the format `?name`.
- Parameter value: The value to be bound
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
- Returns: This `Query` such that further function can be called.
*/
func bind(varName: String, toReference value: Entid) throws -> Query {
query_builder_bind_ref(try! self.validPointer(), varName, value)
return self
}
/**
Binds a `String` value representing a keyword for an attribute to the provided variable name.
Keywords take the format `:namespace/name`.
- Parameter varName: The name of the variable in the format `?name`.
- Parameter value: The value to be bound
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
- Returns: This `Query` such that further function can be called.
*/
func bind(varName: String, toReference value: String) throws -> Query {
query_builder_bind_ref_kw(try! self.validPointer(), varName, value)
return self
}
/**
Binds a keyword `String` value to the provided variable name.
Keywords take the format `:namespace/name`.
- Parameter varName: The name of the variable in the format `?name`.
- Parameter value: The value to be bound
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
- Returns: This `Query` such that further function can be called.
*/
func bind(varName: String, toKeyword value: String) throws -> Query {
query_builder_bind_kw(try! self.validPointer(), varName, value)
return self
}
/**
Binds a `Bool` value to the provided variable name.
- Parameter varName: The name of the variable in the format `?name`.
- Parameter value: The value to be bound
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
- Returns: This `Query` such that further function can be called.
*/
func bind(varName: String, toBoolean value: Bool) throws -> Query {
query_builder_bind_boolean(try! self.validPointer(), varName, value ? 1 : 0)
return self
}
/**
Binds a `Double` value to the provided variable name.
- Parameter varName: The name of the variable in the format `?name`.
- Parameter value: The value to be bound
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
- Returns: This `Query` such that further function can be called.
*/
func bind(varName: String, toDouble value: Double) throws -> Query {
query_builder_bind_double(try! self.validPointer(), varName, value)
return self
}
/**
Binds a `Date` value to the provided variable name.
- Parameter varName: The name of the variable in the format `?name`.
- Parameter value: The value to be bound
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
- Returns: This `Query` such that further function can be called.
*/
func bind(varName: String, toDate value: Date) throws -> Query {
query_builder_bind_timestamp(try! self.validPointer(), varName, value.toMicroseconds())
return self
}
/**
Binds a `String` value to the provided variable name.
- Parameter varName: The name of the variable in the format `?name`.
- Parameter value: The value to be bound
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
- Returns: This `Query` such that further function can be called.
*/
func bind(varName: String, toString value: String) throws -> Query {
query_builder_bind_string(try! self.validPointer(), varName, value)
return self
}
/**
Binds a `UUID` value to the provided variable name.
- Parameter varName: The name of the variable in the format `?name`.
- Parameter value: The value to be bound
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has already been executed.
- Returns: This `Query` such that further function can be called.
*/
func bind(varName: String, toUuid value: UUID) throws -> Query {
var rawUuid = value.uuid
withUnsafePointer(to: &rawUuid) { uuidPtr in
query_builder_bind_uuid(try! self.validPointer(), varName, uuidPtr)
}
return self
}
/**
Execute the query with the values bound associated with this `Query` and call the provided callback function with the results as a list of rows of `TypedValues`.
- Parameter callback: the function to call with the results of this query
- Throws: `QueryError.executionFailed` if the query fails to execute. This could be because the provided query did not parse, or that
variable we incorrectly bound, or that the query provided was not `Rel`.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has previously been executed.
*/
func run(callback: @escaping (RelResult?) -> Void) throws {
let result = query_builder_execute(try! self.validPointer())
self.raw = nil
if let err = result.pointee.err {
let message = String(cString: err)
throw QueryError.executionFailed(message: message)
}
guard let results = result.pointee.ok else {
callback(nil)
return
}
callback(RelResult(raw: results))
}
/**
Execute the query with the values bound associated with this `Query` and call the provided callback function with the result as a single `TypedValue`.
- Parameter callback: the function to call with the results of this query
- Throws: `QueryError.executionFailed` if the query fails to execute. This could be because the provided query did not parse, that
variable we incorrectly bound, or that the query provided was not `Scalar`.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has previously been executed.
*/
func runScalar(callback: @escaping (TypedValue?) -> Void) throws {
let result = query_builder_execute_scalar(try! self.validPointer())
self.raw = nil
if let err = result.pointee.err {
let message = String(cString: err)
throw QueryError.executionFailed(message: message)
}
guard let results = result.pointee.ok else {
callback(nil)
return
}
callback(TypedValue(raw: OpaquePointer(results)))
}
/**
Execute the query with the values bound associated with this `Query` and call the provided callback function with the result as a list of single `TypedValues`.
- Parameter callback: the function to call with the results of this query
- Throws: `QueryError.executionFailed` if the query fails to execute. This could be because the provided query did not parse, that
variable we incorrectly bound, or that the query provided was not `Coll`.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has previously been executed.
*/
func runColl(callback: @escaping (ColResult?) -> Void) throws {
let result = query_builder_execute_coll(try! self.validPointer())
self.raw = nil
if let err = result.pointee.err {
let message = String(cString: err)
throw QueryError.executionFailed(message: message)
}
guard let results = result.pointee.ok else {
callback(nil)
return
}
callback(ColResult(raw: results))
}
/**
Execute the query with the values bound associated with this `Query` and call the provided callback function with the result as a list of single `TypedValues`.
- Parameter callback: the function to call with the results of this query
- Throws: `QueryError.executionFailed` if the query fails to execute. This could be because the provided query did not parse, that
variable we incorrectly bound, or that the query provided was not `Tuple`.
- Throws: `PointerError.pointerConsumed` if the underlying raw pointer has already consumed, which will occur if the query has previously been executed.
*/
func runTuple(callback: @escaping (TupleResult?) -> Void) throws {
let result = query_builder_execute_tuple(try! self.validPointer())
self.raw = nil
if let err = result.pointee.err {
let message = String(cString: err)
throw QueryError.executionFailed(message: message)
}
guard let results = result.pointee.ok else {
callback(nil)
return
}
callback(TupleResult(raw: OpaquePointer(results)))
}
override func cleanup(pointer: OpaquePointer) {
query_builder_destroy(pointer)
}
}

View file

@ -0,0 +1,106 @@
/* 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
/**
Wraps a `Rel` result from a Mentat query.
A `Rel` result is a list of rows of `TypedValues`.
Individual rows can be fetched or the set can be iterated.
To fetch individual rows from a `RelResult` use `row(Int32)`.
```
query.run { rows in
let row1 = rows.row(0)
let row2 = rows.row(1)
}
```
To iterate over the result set use standard iteration flows.
```
query.run { rows in
rows.forEach { row in
...
}
}
```
Note that iteration is consuming and can only be done once.
*/
class RelResult: OptionalRustObject {
/**
Fetch the row at the requested index.
- Parameter index: the index of the row to be fetched
- Throws: `PointerError.pointerConsumed` if the result set has already been iterated.
- Returns: The row at the requested index as a `TupleResult`, if present, or nil if there is no row at that index.
*/
func row(index: Int32) throws -> TupleResult? {
guard let row = row_at_index(try self.validPointer(), index) else {
return nil
}
return TupleResult(raw: row)
}
override func cleanup(pointer: OpaquePointer) {
destroy(UnsafeMutableRawPointer(pointer))
}
}
/**
Iterator for `RelResult`.
To iterate over the result set use standard iteration flows.
```
query.run { result in
rows.forEach { row in
...
}
}
```
Note that iteration is consuming and can only be done once.
*/
class RelResultIterator: OptionalRustObject, IteratorProtocol {
typealias Element = TupleResult
init(iter: OpaquePointer?) {
super.init(raw: iter)
}
func next() -> Element? {
guard let iter = self.raw,
let rowPtr = rows_iter_next(iter) else {
return nil
}
return TupleResult(raw: rowPtr)
}
override func cleanup(pointer: OpaquePointer) {
typed_value_result_set_iter_destroy(pointer)
}
}
extension RelResult: Sequence {
func makeIterator() -> RelResultIterator {
do {
let rowIter = rows_iter(try self.validPointer())
self.raw = nil
return RelResultIterator(iter: rowIter)
} catch {
return RelResultIterator(iter: nil)
}
}
}

View file

@ -0,0 +1,217 @@
/* 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
/**
Wraps a `Tuple` result from a Mentat query.
A `Tuple` result is a list of `TypedValues`.
Individual values can be fetched as `TypedValues` or converted into a requested type.
Values can be fetched as one of the following types:
- `TypedValue`
- `Int64`
- `Entid`
- `Keyword`
- `Bool`
- `Double`
- `Date`
- `String`
- `UUID`.
*/
class TupleResult: OptionalRustObject {
/**
Return the `TypedValue` at the specified index.
If the index is greater than the number of values then this function will crash.
- Parameter index: The index of the value to fetch.
- Returns: The `TypedValue` at that index.
*/
func get(index: Int) -> TypedValue {
return TypedValue(raw: value_at_index(self.raw!, Int32(index)))
}
/**
Return the `Int64` at the specified index.
If the index is greater than the number of values then this function will crash.
If the value type if the `TypedValue` at this index is not `Long` then this function will crash.
- Parameter index: The index of the value to fetch.
- Returns: The `Int64` at that index.
*/
func asLong(index: Int) -> Int64 {
return value_at_index_as_long(self.raw!, Int32(index))
}
/**
Return the `Entid` at the specified index.
If the index is greater than the number of values then this function will crash.
If the value type if the `TypedValue` at this index is not `Ref` then this function will crash.
- Parameter index: The index of the value to fetch.
- Returns: The `Entid` at that index.
*/
func asEntid(index: Int) -> Entid {
return value_at_index_as_entid(self.raw!, Int32(index))
}
/**
Return the keyword `String` at the specified index.
If the index is greater than the number of values then this function will crash.
If the value type if the `TypedValue` at this index is not `Keyword` then this function will crash.
- Parameter index: The index of the value to fetch.
- Returns: The keyword `String` at that index.
*/
func asKeyword(index: Int) -> String {
return String(cString: value_at_index_as_kw(self.raw!, Int32(index)))
}
/**
Return the `Bool` at the specified index.
If the index is greater than the number of values then this function will crash.
If the value type if the `TypedValue` at this index is not `Boolean` then this function will crash.
- Parameter index: The index of the value to fetch.
- Returns: The `Bool` at that index.
*/
func asBool(index: Int) -> Bool {
return value_at_index_as_boolean(self.raw!, Int32(index)) == 0 ? false : true
}
/**
Return the `Double` at the specified index.
If the index is greater than the number of values then this function will crash.
If the value type if the `TypedValue` at this index is not `Double` then this function will crash.
- Parameter index: The index of the value to fetch.
- Returns: The `Double` at that index.
*/
func asDouble(index: Int) -> Double {
return value_at_index_as_double(self.raw!, Int32(index))
}
/**
Return the `Date` at the specified index.
If the index is greater than the number of values then this function will crash.
If the value type if the `TypedValue` at this index is not `Instant` then this function will crash.
- Parameter index: The index of the value to fetch.
- Returns: The `Date` at that index.
*/
func asDate(index: Int) -> Date {
return Date(timeIntervalSince1970: TimeInterval(value_at_index_as_timestamp(self.raw!, Int32(index))))
}
/**
Return the `String` at the specified index.
If the index is greater than the number of values then this function will crash.
If the value type if the `TypedValue` at this index is not `String` then this function will crash.
- Parameter index: The index of the value to fetch.
- Returns: The `String` at that index.
*/
func asString(index: Int) -> String {
return String(cString: value_at_index_as_string(self.raw!, Int32(index)))
}
/**
Return the `UUID` at the specified index.
If the index is greater than the number of values then this function will crash.
If the value type if the `TypedValue` at this index is not `Uuid` then this function will crash.
- Parameter index: The index of the value to fetch.
- Returns: The `UUID` at that index.
*/
func asUUID(index: Int) -> UUID? {
return UUID(uuid: value_at_index_as_uuid(self.raw!, Int32(index)).pointee)
}
override func cleanup(pointer: OpaquePointer) {
typed_value_list_destroy(pointer)
}
}
/**
Wraps a `Coll` result from a Mentat query.
A `Coll` result is a list of rows of single values of type `TypedValue`.
Values for individual rows can be fetched as `TypedValue` or converted into a requested type.
Row values can be fetched as one of the following types:
- `TypedValue`
- `Int64`
- `Entid`
- `Keyword`
- `Bool`
- `Double`
- `Date`
- `String`
- `UUID`.
*/
class ColResult: TupleResult {
}
/**
Iterator for `ColResult`.
To iterate over the result set use standard iteration flows.
```
query.runColl { rows in
rows.forEach { value in
...
}
}
```
Note that iteration is consuming and can only be done once.
*/
class ColResultIterator: OptionalRustObject, IteratorProtocol {
typealias Element = TypedValue
init(iter: OpaquePointer?) {
super.init(raw: iter)
}
func next() -> Element? {
guard let iter = self.raw,
let rowPtr = values_iter_next(iter) else {
return nil
}
return TypedValue(raw: rowPtr)
}
override func cleanup(pointer: OpaquePointer) {
typed_value_list_iter_destroy(pointer)
}
}
extension ColResult: Sequence {
func makeIterator() -> ColResultIterator {
defer {
self.raw = nil
}
guard let raw = self.raw else {
return ColResultIterator(iter: nil)
}
let rowIter = values_iter(raw)
return ColResultIterator(iter: rowIter)
}
}

View file

@ -0,0 +1,68 @@
/* 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
/**
Base class that wraps an optional `OpaquePointer` representing a pointer to a Rust object.
This class should be used to wrap Rust pointer that point to consuming structs, that is, calling a function
for that Rust pointer, will cause Rust to destroy the pointer, leaving the Swift pointer dangling.
These classes are responsible for ensuring that their raw `OpaquePointer` are `nil`led after calling a consuming
FFI function.
This class provides cleanup functions on deinit, ensuring that all classes
that inherit from it will have their `OpaquePointer` destroyed when the Swift wrapper is destroyed.
If a class does not override `cleanup` then a `fatalError` is thrown.
The optional pointer is managed here such that is the pointer is nil, then the cleanup function is not called
ensuring that we do not double free the pointer on exit.
*/
class OptionalRustObject: Destroyable {
var raw: OpaquePointer?
lazy var uniqueId: ObjectIdentifier = {
ObjectIdentifier(self)
}()
init(raw: UnsafeMutableRawPointer) {
self.raw = OpaquePointer(raw)
}
init(raw: OpaquePointer?) {
self.raw = raw
}
func intoRaw() -> OpaquePointer? {
return self.raw
}
deinit {
guard let raw = self.raw else { return }
self.cleanup(pointer: raw)
}
/**
Provides a non-optional `OpaquePointer` if one exists for this class.
- Throws: `Pointer.pointerConsumed` if the raw pointer wrapped by this class is nil
- Returns: the raw `OpaquePointer` wrapped by this class.
*/
func validPointer() throws -> OpaquePointer {
guard let r = self.raw else {
throw PointerError.pointerConsumed
}
return r
}
func cleanup(pointer: OpaquePointer) {
fatalError("\(cleanup) is not implemented.")
}
}

View file

@ -0,0 +1,49 @@
/* 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
protocol Destroyable {
func cleanup(pointer: OpaquePointer)
}
/**
Base class that wraps an non-optional `OpaquePointer` representing a pointer to a Rust object.
This class provides cleanup functions on deinit, ensuring that all classes
that inherit from it will have their `OpaquePointer` destroyed when the Swift wrapper is destroyed.
If a class does not override `cleanup` then a `fatalError` is thrown.
*/
public class RustObject: Destroyable {
var raw: OpaquePointer
init(raw: OpaquePointer) {
self.raw = raw
}
init(raw: UnsafeMutableRawPointer) {
self.raw = OpaquePointer(raw)
}
init?(raw: OpaquePointer?) {
guard let r = raw else {
return nil
}
self.raw = r
}
deinit {
self.cleanup(pointer: self.raw)
}
func cleanup(pointer: OpaquePointer) {
fatalError("\(cleanup) is not implemented.")
}
}

View file

@ -0,0 +1,61 @@
/* 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 than points to a Rust `TxReport` object.
The `TxReport` contains information about a successful Mentat transaction.
This information includes:
- `txId` - the identifier for the transaction.
- `txInstant` - the time that the transaction occured.
- a map of temporary identifiers provided in the transaction and the `Entid`s that they were mapped to,
Access an `Entid` for a temporary identifier that was provided in the transaction can be done through `entid(String:)`.
```
let report = mentat.transact("[[:db/add "a" :foo/boolean true]]")
let aEntid = report.entid(forTempId: "a")
```
*/
class TxReport: RustObject {
// The identifier for the transaction.
public var txId: Entid {
return tx_report_get_entid(self.raw)
}
// The time that the transaction occured.
public var txInstant: Date {
return Date(timeIntervalSince1970: TimeInterval(tx_report_get_tx_instant(self.raw)))
}
/**
Access an `Entid` for a temporary identifier that was provided in the transaction can be done through `entid(String:)`.
- Parameter tempId: A `String` representing the temporary identifier to fetch the `Entid` for.
- Returns: The `Entid` for the temporary identifier, if present, otherwise `nil`.
*/
public func entid(forTempId tempId: String) -> Entid? {
guard let entidPtr = tx_report_entity_for_temp_id(self.raw, tempId) else {
return nil
}
return entidPtr.pointee
}
override func cleanup(pointer: OpaquePointer) {
tx_report_destroy(pointer)
}
}