diff --git a/.gitignore b/.gitignore
index 07102c19..8abb0fce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,28 @@ pom.xml.asc
/fixtures/*.db-shm
/fixtures/*.db-wal
/query-parser/out/
+## Build generated
+/sdks/swift/Mentat/build/
+DerivedData
+build.xcarchive
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+/sdks/swift/Mentat/*.xcodeproj/project.xcworkspace/xcuserdata
+
+## Other
+*.xccheckout
+*.moved-aside
+*.xcuserstate
+*.xcscmblueprint
+
+## Obj-C/Swift specific
+*.hmap
+*.ipa
\ No newline at end of file
diff --git a/android_build_all.sh b/android_build_all.sh
new file mode 100755
index 00000000..61430482
--- /dev/null
+++ b/android_build_all.sh
@@ -0,0 +1,4 @@
+# This will eventually become a complete build script, not just for Android
+cargo build -p mentat_ffi --target i686-linux-android --release
+cargo build -p mentat_ffi --target armv7-linux-androideabi --release
+cargo build -p mentat_ffi --target aarch64-linux-android --release
diff --git a/build/version.rs b/build/version.rs
index 66b38976..f67ff18c 100644
--- a/build/version.rs
+++ b/build/version.rs
@@ -19,7 +19,7 @@ use rustc_version::{
/// MIN_VERSION should be changed when there's a new minimum version of rustc required
/// to build the project.
-static MIN_VERSION: &'static str = "1.24.0";
+static MIN_VERSION: &'static str = "1.25.0";
fn main() {
let ver = version().unwrap();
diff --git a/core/src/types.rs b/core/src/types.rs
index ad426a12..ca3b86d4 100644
--- a/core/src/types.rs
+++ b/core/src/types.rs
@@ -755,6 +755,27 @@ impl Binding {
_ => None,
}
}
+
+ pub fn into_c_string(self) -> Option<*mut c_char> {
+ match self {
+ Binding::Scalar(v) => v.into_c_string(),
+ _ => None,
+ }
+ }
+
+ pub fn into_kw_c_string(self) -> Option<*mut c_char> {
+ match self {
+ Binding::Scalar(v) => v.into_kw_c_string(),
+ _ => None,
+ }
+ }
+
+ pub fn into_uuid_c_string(self) -> Option<*mut c_char> {
+ match self {
+ Binding::Scalar(v) => v.into_uuid_c_string(),
+ _ => None,
+ }
+ }
}
#[test]
diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml
index cd872585..7c1e7c10 100644
--- a/ffi/Cargo.toml
+++ b/ffi/Cargo.toml
@@ -1,10 +1,14 @@
[package]
name = "mentat_ffi"
-version = "0.1.0"
+version = "0.0.1"
authors = ["Emily Toop "]
+[lib]
+name = "mentat_ffi"
+crate-type = ["lib", "staticlib", "cdylib"]
+
[dependencies]
libc = "0.2"
[dependencies.mentat]
-path = ".."
+path = "../"
diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs
index b657e871..fc7d734b 100644
--- a/ffi/src/lib.rs
+++ b/ffi/src/lib.rs
@@ -8,6 +8,65 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
+//! This module exposes an Foreign Function Interface (FFI) that allows Mentat to be
+//! called from other languages.
+//!
+//! Functions that are available to other languages in this module are defined as
+//! extern "C" functions which allow them to be layed out correctly for the
+//! platform's C ABI. They all have a `#[no_mangle]` decorator to ensure
+//! Rust's name mangling is turned off, so that it is easier to link to.
+//!
+//! Mentat's FFI contains unsafe code. As it is an interface between foreign code
+//! and native Rust code, Rust cannot guarantee that the types and data that have been passed
+//! to it from another language are present and in the format it is expecting.
+//! This interface is designed to ensure that nothing unsafe passes through this module
+//! and enters Mentat proper
+//!
+//! Structs defined with `#[repr(C)]` are guaranteed to have a layout that is compatible
+//! with the platform's representation in C.
+//!
+//! This API passes pointers in two ways, depending on the lifetime of the value and
+//! what value owns it.
+//! Pointers to values that are guaranteed to live beyond the lifetime of the function,
+//! are passed over the FFI as a raw pointer.
+//!
+//! `value as *const Binding`
+//!
+//! Pointers to values that cannot be guaranteed to live beyond the lifetime of the function
+//! are first `Box`ed so that they live on the heap, and the raw pointer passed this way.
+//!
+//! `Box::into_raw(Box::new(value))`
+//!
+//! The memory for a value that is moved onto the heap before being passed over the FFI
+//! is no longer managed by Rust, but Rust still owns the value. Therefore the pointer
+//! must be returned to Rust in order to be released. To this effect a number of `destructor`
+//! functions are provided for each Rust value type that is passed, as is a catch all destructor
+//! to release memory for `#[repr(C)]` values.
+//! The destructors reclaim the memory via [Box](std::boxed::Box) and then drop the reference, causing the
+//! memory to be released.
+//!
+//! A macro has been provided to make defining destructors easier.
+//!
+//! `define_destructor!(query_builder_destroy, QueryBuilder);`
+//!
+//! Passing a pointer to memory that has already been released will cause Mentat to crash,
+//! so callers have to be careful to ensure they manage their pointers properly.
+//! Failure to call a destructor for a value on the heap will cause a memory leak.
+//!
+//! Generally, the functions exposed in this module have a direct mapping to existing Mentat APIs,
+//! in order to keep application logic to a minumum and provide the greatest flexibility
+//! for callers using the interface. However, in some cases a single convenience function
+//! has been provided in order to make the interface easier to use and reduce the number
+//! of calls that have to be made over the FFI to perform a task. An example of this is
+//! `store_register_observer`, which takes a single native callback function that is then
+//! wrapped inside a Rust closure and added to a [TxObserver](mentat::TxObserver) struct. This is then used to
+//! register the observer with the store.
+//!
+//! [Result](std::result::Result) and [Option](std::option::Option) Rust types have `repr(C)` structs that mirror them. This is to provide a more
+//! native access pattern to callers and to enable easier passing of optional types and error
+//! propogation. These types have implemented [From](std::convert::From) such that conversion from the Rust type
+//! to the C type is as painless as possible.
+
extern crate libc;
extern crate mentat;
@@ -17,6 +76,7 @@ use std::collections::{
use std::os::raw::{
c_char,
c_int,
+ c_longlong,
c_void,
};
use std::slice;
@@ -25,9 +85,8 @@ use std::sync::{
};
use std::vec;
-use libc::time_t;
-
pub use mentat::{
+ Binding,
Entid,
FindSpec,
HasSchema,
@@ -38,15 +97,24 @@ pub use mentat::{
QueryInputs,
QueryOutput,
QueryResults,
+ RelResult,
Store,
Syncable,
TypedValue,
TxObserver,
+ TxReport,
Uuid,
ValueType,
Variable,
};
+pub use mentat::entity_builder::{
+ BuildTerms,
+ EntityBuilder,
+ InProgressBuilder,
+ IntoThing,
+};
+
pub mod android;
pub mod utils;
@@ -56,24 +124,53 @@ pub use utils::strings::{
string_to_c_char,
};
-pub type TypedValueIterator = vec::IntoIter;
-pub type TypedValueListIterator = vec::IntoIter>;
+pub use utils::log;
+pub type BindingIterator = vec::IntoIter;
+pub type BindingListIterator = std::slice::Chunks<'static, mentat::Binding>;
+
+/// A C representation of the change provided by the transaction observers
+/// from a single transact.
+/// Holds a transaction identifier, the changes as a set of affected attributes
+/// and the length of the list of changes.
+///
+/// #Safety
+///
+/// Callers are responsible for managing the memory for the return value.
+/// A destructor `destroy` is provided for releasing the memory for this
+/// pointer type.
#[repr(C)]
#[derive(Debug, Clone)]
-pub struct ExternTxReport {
+pub struct TransactionChange {
pub txid: Entid,
- pub changes: Box<[Entid]>,
pub changes_len: usize,
+ pub changes: Box<[Entid]>,
}
+ /// A C representation of the list of changes provided by the transaction observers.
+ /// Provides the list of changes as the length of the list.
+///
+/// #Safety
+///
+/// Callers are responsible for managing the memory for the return value.
+/// A destructor `destroy` is provided for releasing the memory for this
+/// pointer type.
#[repr(C)]
#[derive(Debug)]
-pub struct ExternTxReportList {
- pub reports: Box<[ExternTxReport]>,
+pub struct TxChangeList {
+ pub reports: Box<[TransactionChange]>,
pub len: usize,
}
+/// A C representation Rust's [Option](std::option::Option).
+/// A value of `Some` results in `value` containing a raw pointer as a `c_void`.
+/// A value of `None` results in `value` containing a null pointer.
+///
+/// #Safety
+///
+/// Callers are responsible for managing the memory for the return value.
+/// A destructor `destroy` is provided for releasing the memory for this
+/// pointer type.
#[repr(C)]
#[derive(Debug)]
pub struct ExternOption {
@@ -88,6 +185,16 @@ impl From
+ *
+ * Note that iteration is consuming and can only be done once.
+ */
+public class RelResult extends RustObject implements Iterable {
+
+ public RelResult(Pointer pointer) {
+ this.rawPointer = pointer;
+ }
+
+ /**
+ * Fetch the row at the requested index.
+ * TODO: Throw an exception if the result set has already been iterated.
+ * @param index the index of the row to be fetched
+ * @return The row at the requested index as a `TupleResult`, if present, or nil if there is no row at that index.
+ */
+ public TupleResult rowAtIndex(int index) {
+ this.validate();
+ Pointer pointer = JNA.INSTANCE.row_at_index(this.rawPointer, index);
+ if (pointer == null) {
+ return null;
+ }
+
+ return new TupleResult(pointer);
+ }
+
+ @Override
+ public RelResultIterator iterator() {
+ this.validate();
+ Pointer iterPointer = JNA.INSTANCE.typed_value_result_set_into_iter(this.rawPointer);
+ this.rawPointer = null;
+ if (iterPointer == null) {
+ return null;
+ }
+ return new RelResultIterator(iterPointer);
+ }
+
+ @Override
+ public void close() {
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.typed_value_result_set_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultHandler.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultHandler.java
new file mode 100644
index 00000000..f9a4e39c
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultHandler.java
@@ -0,0 +1,18 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+/**
+ * Interface defining the structure of a callback from a query returning a {@link RelResult}.
+ */
+public interface RelResultHandler {
+ void handleRows(RelResult rows);
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultIterator.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultIterator.java
new file mode 100644
index 00000000..28577a3f
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultIterator.java
@@ -0,0 +1,54 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+
+import java.util.Iterator;
+/**
+ * Iterator for a {@link RelResult}
+ */
+public class RelResultIterator extends RustObject implements Iterator {
+
+ Pointer nextPointer;
+
+ RelResultIterator(Pointer iterator) {
+ this.rawPointer = iterator;
+ }
+
+ private Pointer getNextPointer() {
+ return JNA.INSTANCE.typed_value_result_set_iter_next(this.rawPointer);
+ }
+
+ @Override
+ public boolean hasNext() {
+ this.nextPointer = getNextPointer();
+ return this.nextPointer != null;
+ }
+
+ @Override
+ public TupleResult next() {
+ Pointer next = this.nextPointer == null ? getNextPointer() : this.nextPointer;
+ if (next == null) {
+ return null;
+ }
+
+ return new TupleResult(next);
+ }
+
+
+ @Override
+ public void close() {
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.typed_value_result_set_iter_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java
new file mode 100644
index 00000000..35f7b8fb
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustObject.java
@@ -0,0 +1,34 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+
+import java.io.Closeable;
+
+/**
+ * Base class that wraps an non-optional {@link Pointer} representing a pointer to a Rust object.
+ * This class implements {@link Closeable} but does not provide an implementation, forcing all
+ * subclasses to implement it. This ensures that all classes that inherit from RustObject
+ * will have their {@link Pointer} destroyed when the Java wrapper is destroyed.
+ */
+abstract class RustObject implements Closeable {
+ Pointer rawPointer;
+
+ /**
+ * Throws a {@link NullPointerException} if the underlying {@link Pointer} is null.
+ */
+ void validate() {
+ if (this.rawPointer == null) {
+ throw new NullPointerException(this.getClass() + " consumed");
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustResult.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustResult.java
new file mode 100644
index 00000000..dc19ddc4
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RustResult.java
@@ -0,0 +1,62 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+import com.sun.jna.Structure;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a C struct containing a {@link Pointer}s and String that map to a Rust Result.
+ * A RustResult will contain either an ok value, OR an err value, or neither - never both.
+ */
+public class RustResult extends Structure implements Closeable {
+ public static class ByReference extends RustResult implements Structure.ByReference {
+ }
+
+ public static class ByValue extends RustResult implements Structure.ByValue {
+ }
+
+ public Pointer ok;
+ public String err;
+
+ /**
+ * Is there an value attached to this result
+ * @return true if a value is present, false otherwise
+ */
+ public boolean isSuccess() {
+ return this.ok != null;
+ }
+
+ /**
+ * Is there an error attached to this result?
+ * @return true is an error is present, false otherwise
+ */
+ public boolean isFailure() {
+ return this.err != null;
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList("ok", "err");
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (this.getPointer() != null) {
+ JNA.INSTANCE.destroy(this.getPointer());
+ }
+ }
+}
\ No newline at end of file
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ScalarResultHandler.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ScalarResultHandler.java
new file mode 100644
index 00000000..ad4b2dd3
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ScalarResultHandler.java
@@ -0,0 +1,18 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+/**
+ * Interface defining the structure of a callback from a query returning a single {@link TypedValue}.
+ */
+public interface ScalarResultHandler {
+ void handleValue(TypedValue value);
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java
new file mode 100644
index 00000000..a9701682
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java
@@ -0,0 +1,168 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * Wraps a `Tuple` result from a Mentat query.
+ * A `Tuple` result is a single row {@link TypedValue}s.
+ * Values for individual fields can be fetched as {@link TypedValue} or converted into a requested type.
+ *
+ * Field values can be fetched as one of the following types:
+ *
+ *
{@link TypedValue}
+ *
long
+ *
Entid (as long)
+ *
Keyword (as String)
+ *
boolean
+ *
double
+ *
{@link Date}
+ *
{@link String}
+ *
{@link UUID}
+ *
+ *
+ * To iterate over the result set use standard iteration flows.
+ */
+public class TupleResult extends RustObject {
+
+ public TupleResult(Pointer pointer) {
+ this.rawPointer = pointer;
+ }
+
+ /**
+ * Return the {@link TypedValue} at the specified index.
+ * If the index is greater than the number of values then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link TypedValue} at that index.
+ */
+ public TypedValue get(Integer index) {
+ this.validate();
+ Pointer pointer = JNA.INSTANCE.value_at_index(this.rawPointer, index);
+ if (pointer == null) {
+ return null;
+ }
+ return new TypedValue(pointer);
+ }
+
+ /**
+ * Return the {@link Long} 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 {@link TypedValue} at this index is not `Long` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link Long} at that index.
+ */
+ public Long asLong(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_long(this.rawPointer, 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 {@link TypedValue} at this index is not `Ref` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The Entid at that index.
+ */
+ public Long asEntid(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_entid(this.rawPointer, index);
+ }
+
+ /**
+ * Return the keyword {@link 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 {@link TypedValue} at this index is not `Keyword` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The keyword at that index.
+ */
+ public String asKeyword(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_kw(this.rawPointer, index);
+ }
+
+ /**
+ * Return the {@link Boolean} 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 {@link TypedValue} at this index is not `Boolean` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link Boolean} at that index.
+ */
+ public Boolean asBool(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_boolean(this.rawPointer, index) == 0 ? false : true;
+ }
+
+ /**
+ * Return the {@link 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 {@link TypedValue} at this index is not `Double` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link Double} at that index.
+ */
+ public Double asDouble(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_double(this.rawPointer, index);
+ }
+
+ /**
+ * Return the {@link 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 {@link TypedValue} at this index is not `Instant` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link Date} at that index.
+ */
+ public Date asDate(Integer index) {
+ this.validate();
+ return new Date(JNA.INSTANCE.value_at_index_into_timestamp(this.rawPointer, index));
+ }
+
+ /**
+ * Return the {@link 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 {@link TypedValue} at this index is not `String` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link String} at that index.
+ */
+ public String asString(Integer index) {
+ this.validate();
+ return JNA.INSTANCE.value_at_index_into_string(this.rawPointer, index);
+ }
+
+ /**
+ * Return the {@link 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 {@link TypedValue} at this index is not `Uuid` then this function will crash.
+ * @param index The index of the value to fetch.
+ * @return The {@link UUID} at that index.
+ */
+ public UUID asUUID(Integer index) {
+ this.validate();
+ Pointer uuidPtr = JNA.INSTANCE.value_at_index_into_uuid(this.rawPointer, index);
+ byte[] bytes = uuidPtr.getByteArray(0, 16);
+ ByteBuffer bb = ByteBuffer.wrap(bytes);
+ long high = bb.getLong();
+ long low = bb.getLong();
+
+ return new UUID(high, low);
+ }
+
+ @Override
+ public void close() {
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.typed_value_list_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResultHandler.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResultHandler.java
new file mode 100644
index 00000000..4eb7f766
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResultHandler.java
@@ -0,0 +1,18 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+/**
+ * Interface defining the structure of a callback from a query returning a {@link TupleResult}.
+ */
+public interface TupleResultHandler {
+ void handleRow(TupleResult row);
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChange.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChange.java
new file mode 100644
index 00000000..d5b6a830
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChange.java
@@ -0,0 +1,64 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+import com.sun.jna.Structure;
+
+import java.io.Closeable;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a C struct representing changes that occured during a transaction.
+ * These changes contain the transaction identifier, a {@link Pointer} to a list of affected attribute
+ * Entids and the number of items that the list contains.
+ */
+public class TxChange extends Structure implements Closeable {
+ public static class ByReference extends TxChange implements Structure.ByReference {
+ }
+
+ public static class ByValue extends TxChange implements Structure.ByValue {
+ }
+
+ public int txid;
+ public Pointer changes;
+ public int numberOfItems;
+ // Used by the Swift counterpart, JNA does this for us automagically.
+ // But we still need it here so that the number of fields and their order is correct
+ public int changes_len;
+
+ /**
+ * Get the affected attributes for this transaction
+ * @return The changes as a list of Entids of affected attributes
+ */
+ public List getChanges() {
+ final long[] array = (long[]) changes.getLongArray(0, numberOfItems);
+ Long[] longArray = new Long[numberOfItems];
+ int idx = 0;
+ for (long change: array) {
+ longArray[idx++] = change;
+ }
+ return Arrays.asList(longArray);
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList("txid", "changes", "changes_len", "numberOfItems");
+ }
+
+ @Override
+ public void close() {
+ if (this.getPointer() != null) {
+ JNA.INSTANCE.destroy(this.getPointer());
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChangeList.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChangeList.java
new file mode 100644
index 00000000..f27d163a
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChangeList.java
@@ -0,0 +1,56 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Structure;
+
+import java.io.Closeable;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a C struct containing a list of {@link TxChange}s that occured.
+ */
+public class TxChangeList extends Structure implements Closeable {
+ public static class ByReference extends TxChangeList implements Structure.ByReference {
+ }
+
+ public static class ByValue extends TxChangeList implements Structure.ByValue {
+ }
+
+ public TxChange.ByReference reports;
+ public int numberOfItems;
+ // Used by the Swift counterpart, JNA does this for us automagically.
+ // // But we still need it here so that the number of fields and their order is correct
+ public int len;
+
+ /**
+ * Get the changes that occured
+ * @return a list of {@link TxChange}s for the notification
+ */
+ public List getReports() {
+ final TxChange[] array = (TxChange[]) reports.toArray(numberOfItems);
+ return Arrays.asList(array);
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList("reports", "numberOfItems", "len");
+ }
+
+ @Override
+ public void close() {
+ final TxChange[] nativeReports = (TxChange[]) reports.toArray(numberOfItems);
+ for (TxChange nativeReport : nativeReports) {
+ nativeReport.close();
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxObserverCallback.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxObserverCallback.java
new file mode 100644
index 00000000..9b474954
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxObserverCallback.java
@@ -0,0 +1,20 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Callback;
+
+/**
+ * Protocol to be implemented by any object that wishes to register for transaction observation
+ */
+public interface TxObserverCallback extends Callback {
+ void transactionObserverCalled(String key, TxChangeList.ByReference reports);
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxReport.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxReport.java
new file mode 100644
index 00000000..abeaf530
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxReport.java
@@ -0,0 +1,89 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+
+import java.util.Date;
+
+/**
+ * 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:)`.
+ *
+ *
{@code
+ * TxReport report = mentat.transact("[[:db/add "a" :foo/boolean true]]");
+ * long aEntid = report.getEntidForTempId("a");
+ *}
+ */
+public class TxReport extends RustObject {
+
+ private Long txId;
+ private Date txInstant;
+
+
+ public TxReport(Pointer pointer) {
+ this.rawPointer = pointer;
+ }
+
+ /**
+ * Get the identifier for the transaction.
+ * @return The identifier for the transaction.
+ */
+ public Long getTxId() {
+ if (this.txId == null) {
+ this.txId = JNA.INSTANCE.tx_report_get_entid(this.rawPointer);
+ }
+
+ return this.txId;
+ }
+
+ /**
+ * Get the time that the transaction occured.
+ * @return The time that the transaction occured.
+ */
+ public Date getTxInstant() {
+ if (this.txInstant == null) {
+ this.txInstant = new Date(JNA.INSTANCE.tx_report_get_tx_instant(this.rawPointer));
+ }
+ return this.txInstant;
+ }
+
+ /**
+ * Access an `Entid` for a temporary identifier that was provided in the transaction.
+ * @param tempId A {@link String} representing the temporary identifier to fetch the `Entid` for.
+ * @return The `Entid` for the temporary identifier, if present, otherwise `null`.
+ */
+ public Long getEntidForTempId(String tempId) {
+ Pointer longPointer = JNA.INSTANCE.tx_report_entity_for_temp_id(this.rawPointer, tempId);
+ if (longPointer == null) {
+ return null;
+ }
+
+ return longPointer.getLong(0);
+ }
+
+ @Override
+ public void close() {
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.tx_report_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java
new file mode 100644
index 00000000..943e9753
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java
@@ -0,0 +1,157 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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. */
+
+package com.mozilla.mentat;
+
+import com.sun.jna.Pointer;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * 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.
+ */
+public class TypedValue extends RustObject {
+
+ private Object value;
+
+ private boolean isConsumed() {
+ return this.rawPointer == null;
+ }
+
+ public TypedValue(Pointer pointer) {
+ this.rawPointer = pointer;
+ }
+
+ /**
+ * This value as a {@link Long}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Long`
+ * @return the value of this {@link TypedValue} as a {@link Long}
+ */
+ public Long asLong() {
+ if (!this.isConsumed()) {
+ this.value = JNA.INSTANCE.typed_value_into_long(this.rawPointer);
+ this.rawPointer = null;
+ }
+ return (Long)value;
+ }
+
+ /**
+ * This value as a Entid. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Ref`
+ * @return the value of this {@link TypedValue} as a Entid
+ */
+ public Long asEntid() {
+ if (!this.isConsumed()) {
+ this.value = JNA.INSTANCE.typed_value_into_entid(this.rawPointer);
+ this.rawPointer = null;
+ }
+ return (Long)value;
+ }
+
+ /**
+ * This value as a keyword {@link String}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Keyword`
+ * @return the value of this {@link TypedValue} as a Keyword
+ */
+ public String asKeyword() {
+ if (!this.isConsumed()) {
+ this.value = JNA.INSTANCE.typed_value_into_kw(this.rawPointer);
+ this.rawPointer = null;
+ }
+ return (String)value;
+ }
+
+ /**
+ * This value as a {@link Boolean}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Boolean`
+ * @return the value of this {@link TypedValue} as a {@link Boolean}
+ */
+ public Boolean asBoolean() {
+ if (!this.isConsumed()) {
+ long value = JNA.INSTANCE.typed_value_into_boolean(this.rawPointer);
+ this.value = value == 0 ? false : true;
+ this.rawPointer = null;
+ }
+ return (Boolean) this.value;
+ }
+
+ /**
+ * This value as a {@link Double}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Double`
+ * @return the value of this {@link TypedValue} as a {@link Double}
+ */
+ public Double asDouble() {
+ if (!this.isConsumed()) {
+ this.value = JNA.INSTANCE.typed_value_into_double(this.rawPointer);
+ this.rawPointer = null;
+ }
+ return (Double)value;
+ }
+
+ /**
+ * This value as a {@link Date}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Instant`
+ * @return the value of this {@link TypedValue} as a {@link Date}
+ */
+ public Date asDate() {
+ if (!this.isConsumed()) {
+ this.value = new Date(JNA.INSTANCE.typed_value_into_timestamp(this.rawPointer) * 1_000);
+ this.rawPointer = null;
+ }
+ return (Date)this.value;
+ }
+
+ /**
+ * This value as a {@link String}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `String`
+ * @return the value of this {@link TypedValue} as a {@link String}
+ */
+ public String asString() {
+ if (!this.isConsumed()) {
+ this.value = JNA.INSTANCE.typed_value_into_string(this.rawPointer);
+ this.rawPointer = null;
+ }
+ return (String)value;
+ }
+
+ /**
+ * This value as a {@link UUID}. This function will panic if the `ValueType` of this
+ * {@link TypedValue} is not a `Uuid`
+ * @return the value of this {@link TypedValue} as a {@link UUID}
+ */
+ public UUID asUUID() {
+ if (!this.isConsumed()) {
+ Pointer uuidPtr = JNA.INSTANCE.typed_value_into_uuid(this.rawPointer);
+ byte[] bytes = uuidPtr.getByteArray(0, 16);
+ ByteBuffer bb = ByteBuffer.wrap(bytes);
+ long high = bb.getLong();
+ long low = bb.getLong();
+ this.value = new UUID(high, low);
+ this.rawPointer = null;
+ }
+ return (UUID)this.value;
+ }
+
+ @Override
+ public void close() {
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.typed_value_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/jniLibs/arm64/libjnidispatch.so b/sdks/android/Mentat/library/src/main/jniLibs/arm64/libjnidispatch.so
new file mode 100644
index 00000000..d2b1a8f2
Binary files /dev/null and b/sdks/android/Mentat/library/src/main/jniLibs/arm64/libjnidispatch.so differ
diff --git a/sdks/android/Mentat/library/src/main/jniLibs/arm64/libmentat_ffi.so b/sdks/android/Mentat/library/src/main/jniLibs/arm64/libmentat_ffi.so
new file mode 100644
index 00000000..6bed1721
Binary files /dev/null and b/sdks/android/Mentat/library/src/main/jniLibs/arm64/libmentat_ffi.so differ
diff --git a/sdks/android/Mentat/library/src/main/jniLibs/armeabi/libjnidispatch.so b/sdks/android/Mentat/library/src/main/jniLibs/armeabi/libjnidispatch.so
new file mode 100644
index 00000000..bc167135
Binary files /dev/null and b/sdks/android/Mentat/library/src/main/jniLibs/armeabi/libjnidispatch.so differ
diff --git a/sdks/android/Mentat/library/src/main/jniLibs/armeabi/libmentat_ffi.so b/sdks/android/Mentat/library/src/main/jniLibs/armeabi/libmentat_ffi.so
new file mode 100644
index 00000000..ac20e94b
Binary files /dev/null and b/sdks/android/Mentat/library/src/main/jniLibs/armeabi/libmentat_ffi.so differ
diff --git a/sdks/android/Mentat/library/src/main/jniLibs/x86/libjnidispatch.so b/sdks/android/Mentat/library/src/main/jniLibs/x86/libjnidispatch.so
new file mode 100644
index 00000000..a87d0865
Binary files /dev/null and b/sdks/android/Mentat/library/src/main/jniLibs/x86/libjnidispatch.so differ
diff --git a/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so b/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so
new file mode 100755
index 00000000..1160a11a
Binary files /dev/null and b/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so differ
diff --git a/sdks/android/Mentat/library/src/main/res/values/strings.xml b/sdks/android/Mentat/library/src/main/res/values/strings.xml
new file mode 100644
index 00000000..30b035ae
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Mentat
+
diff --git a/sdks/android/Mentat/settings.gradle b/sdks/android/Mentat/settings.gradle
new file mode 100644
index 00000000..d8f14a13
--- /dev/null
+++ b/sdks/android/Mentat/settings.gradle
@@ -0,0 +1 @@
+include ':library'
diff --git a/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj b/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..37886d23
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat.xcodeproj/project.pbxproj
@@ -0,0 +1,615 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 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 */; };
+ 7BDB96942077C299009D0651 /* Mentat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BDB968A2077C299009D0651 /* Mentat.framework */; };
+ 7BDB96992077C299009D0651 /* MentatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96982077C299009D0651 /* MentatTests.swift */; };
+ 7BDB969B2077C299009D0651 /* Mentat.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BDB968D2077C299009D0651 /* Mentat.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 7BDB96AF2077C38E009D0651 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96A62077C38D009D0651 /* Query.swift */; };
+ 7BDB96B02077C38E009D0651 /* Mentat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96A72077C38D009D0651 /* Mentat.swift */; };
+ 7BDB96B12077C38E009D0651 /* store.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BDB96A82077C38E009D0651 /* store.h */; };
+ 7BDB96B22077C38E009D0651 /* RelResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96A92077C38E009D0651 /* RelResult.swift */; };
+ 7BDB96B32077C38E009D0651 /* RustObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AA2077C38E009D0651 /* RustObject.swift */; };
+ 7BDB96B42077C38E009D0651 /* OptionalRustObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AB2077C38E009D0651 /* OptionalRustObject.swift */; };
+ 7BDB96B52077C38E009D0651 /* TupleResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AC2077C38E009D0651 /* TupleResult.swift */; };
+ 7BDB96B72077C38E009D0651 /* TypedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96AE2077C38E009D0651 /* TypedValue.swift */; };
+ 7BDB96C22077CD98009D0651 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BDB96C12077CD98009D0651 /* libresolv.tbd */; };
+ 7BDB96C62077D347009D0651 /* Date+Int64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96C52077D346009D0651 /* Date+Int64.swift */; };
+ 7BDB96C9207B735A009D0651 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 7BDB96C8207B735A009D0651 /* fixtures */; };
+ 7BDB96CC207B7684009D0651 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDB96CB207B7684009D0651 /* Errors.swift */; };
+ 7BEB7D2C207D03DA000369AD /* TxReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BEB7D2B207D03DA000369AD /* TxReport.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 7BDB96952077C299009D0651 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 7BDB96812077C299009D0651 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 7BDB96892077C299009D0651;
+ remoteInfo = Mentat;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 7B74483C208DF667006CFFB0 /* Result+Unwrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Result+Unwrap.swift"; path = "Mentat/Extensions/Result+Unwrap.swift"; sourceTree = SOURCE_ROOT; };
+ 7B911E1A2085081D000998CB /* libtoodle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtoodle.a; path = "../../../../sync-storage-prototype/rust/target/universal/release/libtoodle.a"; sourceTree = ""; };
+ 7BAE75A32089022B00895D37 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
+ 7BDB968A2077C299009D0651 /* Mentat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Mentat.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 7BDB968D2077C299009D0651 /* Mentat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Mentat.h; sourceTree = ""; };
+ 7BDB968E2077C299009D0651 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 7BDB96932077C299009D0651 /* MentatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MentatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 7BDB96982077C299009D0651 /* MentatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentatTests.swift; sourceTree = ""; };
+ 7BDB969A2077C299009D0651 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 7BDB96A62077C38D009D0651 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; };
+ 7BDB96A72077C38D009D0651 /* Mentat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mentat.swift; sourceTree = ""; };
+ 7BDB96A82077C38E009D0651 /* store.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = store.h; sourceTree = ""; };
+ 7BDB96A92077C38E009D0651 /* RelResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelResult.swift; sourceTree = ""; };
+ 7BDB96AA2077C38E009D0651 /* RustObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RustObject.swift; sourceTree = ""; };
+ 7BDB96AB2077C38E009D0651 /* OptionalRustObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalRustObject.swift; sourceTree = ""; };
+ 7BDB96AC2077C38E009D0651 /* TupleResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TupleResult.swift; sourceTree = ""; };
+ 7BDB96AE2077C38E009D0651 /* TypedValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypedValue.swift; sourceTree = ""; };
+ 7BDB96BF2077CD7A009D0651 /* libmentat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmentat.a; path = ../../../target/universal/release/libmentat.a; sourceTree = ""; };
+ 7BDB96C12077CD98009D0651 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
+ 7BDB96C32077D090009D0651 /* module.map */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.map; sourceTree = ""; };
+ 7BDB96C52077D346009D0651 /* Date+Int64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Int64.swift"; sourceTree = ""; };
+ 7BDB96C8207B735A009D0651 /* fixtures */ = {isa = PBXFileReference; lastKnownFileType = folder; name = fixtures; path = ../../../../fixtures; sourceTree = ""; };
+ 7BDB96CB207B7684009D0651 /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; };
+ 7BEB7D21207BDDEF000369AD /* libtoodle.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtoodle.a; path = ../../../target/universal/release/libtoodle.a; sourceTree = ""; };
+ 7BEB7D23207BE2AF000369AD /* libmentat_ffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmentat_ffi.a; path = ../../../target/universal/release/libmentat_ffi.a; sourceTree = ""; };
+ 7BEB7D2B207D03DA000369AD /* TxReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxReport.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 7BDB96862077C299009D0651 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7BAE75A42089022B00895D37 /* libsqlite3.tbd in Frameworks */,
+ 7BDB96C22077CD98009D0651 /* libresolv.tbd in Frameworks */,
+ 7BAE75A22089020E00895D37 /* libmentat_ffi.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7BDB96902077C299009D0651 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7BDB96942077C299009D0651 /* Mentat.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 7BDB96802077C299009D0651 = {
+ isa = PBXGroup;
+ children = (
+ 7BDB968C2077C299009D0651 /* Mentat */,
+ 7BDB96972077C299009D0651 /* MentatTests */,
+ 7BDB968B2077C299009D0651 /* Products */,
+ 7BDB96BE2077CD7A009D0651 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 7BDB968B2077C299009D0651 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 7BDB968A2077C299009D0651 /* Mentat.framework */,
+ 7BDB96932077C299009D0651 /* MentatTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 7BDB968C2077C299009D0651 /* Mentat */ = {
+ isa = PBXGroup;
+ children = (
+ 7BDB96CA207B7672009D0651 /* Errors */,
+ 7BDB96C42077D346009D0651 /* Extensions */,
+ 7BDB96BA2077C42B009D0651 /* Core */,
+ 7BDB96A42077C301009D0651 /* Query */,
+ 7BDB96B92077C403009D0651 /* Rust */,
+ 7BDB96A82077C38E009D0651 /* store.h */,
+ 7BDB96A72077C38D009D0651 /* Mentat.swift */,
+ 7BEB7D26207BE5BB000369AD /* Transact */,
+ 7BDB968D2077C299009D0651 /* Mentat.h */,
+ 7BDB968E2077C299009D0651 /* Info.plist */,
+ 7BDB96C32077D090009D0651 /* module.map */,
+ );
+ path = Mentat;
+ sourceTree = "";
+ };
+ 7BDB96972077C299009D0651 /* MentatTests */ = {
+ isa = PBXGroup;
+ children = (
+ 7BDB96C8207B735A009D0651 /* fixtures */,
+ 7BDB96982077C299009D0651 /* MentatTests.swift */,
+ 7BDB969A2077C299009D0651 /* Info.plist */,
+ );
+ path = MentatTests;
+ sourceTree = "";
+ };
+ 7BDB96A42077C301009D0651 /* Query */ = {
+ isa = PBXGroup;
+ children = (
+ 7BDB96A62077C38D009D0651 /* Query.swift */,
+ 7BDB96A92077C38E009D0651 /* RelResult.swift */,
+ 7BDB96AC2077C38E009D0651 /* TupleResult.swift */,
+ );
+ path = Query;
+ sourceTree = "";
+ };
+ 7BDB96B92077C403009D0651 /* Rust */ = {
+ isa = PBXGroup;
+ children = (
+ 7BDB96AB2077C38E009D0651 /* OptionalRustObject.swift */,
+ 7BDB96AA2077C38E009D0651 /* RustObject.swift */,
+ );
+ path = Rust;
+ sourceTree = "";
+ };
+ 7BDB96BA2077C42B009D0651 /* Core */ = {
+ isa = PBXGroup;
+ children = (
+ 7BDB96AE2077C38E009D0651 /* TypedValue.swift */,
+ );
+ path = Core;
+ sourceTree = "";
+ };
+ 7BDB96BE2077CD7A009D0651 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 7BAE75A32089022B00895D37 /* libsqlite3.tbd */,
+ 7B911E1A2085081D000998CB /* libtoodle.a */,
+ 7BEB7D23207BE2AF000369AD /* libmentat_ffi.a */,
+ 7BEB7D21207BDDEF000369AD /* libtoodle.a */,
+ 7BDB96C12077CD98009D0651 /* libresolv.tbd */,
+ 7BDB96BF2077CD7A009D0651 /* libmentat.a */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 7BDB96C42077D346009D0651 /* Extensions */ = {
+ isa = PBXGroup;
+ children = (
+ 7B74483C208DF667006CFFB0 /* Result+Unwrap.swift */,
+ 7BDB96C52077D346009D0651 /* Date+Int64.swift */,
+ );
+ path = Extensions;
+ sourceTree = "";
+ };
+ 7BDB96CA207B7672009D0651 /* Errors */ = {
+ isa = PBXGroup;
+ children = (
+ 7BDB96CB207B7684009D0651 /* Errors.swift */,
+ );
+ path = Errors;
+ sourceTree = "";
+ };
+ 7BEB7D26207BE5BB000369AD /* Transact */ = {
+ isa = PBXGroup;
+ children = (
+ 7BEB7D2B207D03DA000369AD /* TxReport.swift */,
+ );
+ path = Transact;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ 7BDB96872077C299009D0651 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7BDB96B12077C38E009D0651 /* store.h in Headers */,
+ 7BDB969B2077C299009D0651 /* Mentat.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ 7BDB96892077C299009D0651 /* Mentat */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 7BDB969E2077C299009D0651 /* Build configuration list for PBXNativeTarget "Mentat" */;
+ buildPhases = (
+ 7BDB96852077C299009D0651 /* Sources */,
+ 7BDB96862077C299009D0651 /* Frameworks */,
+ 7BDB96872077C299009D0651 /* Headers */,
+ 7BDB96882077C299009D0651 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Mentat;
+ productName = Mentat;
+ productReference = 7BDB968A2077C299009D0651 /* Mentat.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ 7BDB96922077C299009D0651 /* MentatTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 7BDB96A12077C299009D0651 /* Build configuration list for PBXNativeTarget "MentatTests" */;
+ buildPhases = (
+ 7BDB968F2077C299009D0651 /* Sources */,
+ 7BDB96902077C299009D0651 /* Frameworks */,
+ 7BDB96912077C299009D0651 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 7BDB96962077C299009D0651 /* PBXTargetDependency */,
+ );
+ name = MentatTests;
+ productName = MentatTests;
+ productReference = 7BDB96932077C299009D0651 /* MentatTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 7BDB96812077C299009D0651 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 0930;
+ LastUpgradeCheck = 0930;
+ ORGANIZATIONNAME = Mozilla;
+ TargetAttributes = {
+ 7BDB96892077C299009D0651 = {
+ CreatedOnToolsVersion = 9.3;
+ LastSwiftMigration = 0930;
+ };
+ 7BDB96922077C299009D0651 = {
+ CreatedOnToolsVersion = 9.3;
+ };
+ };
+ };
+ buildConfigurationList = 7BDB96842077C299009D0651 /* Build configuration list for PBXProject "Mentat" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 7BDB96802077C299009D0651;
+ productRefGroup = 7BDB968B2077C299009D0651 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 7BDB96892077C299009D0651 /* Mentat */,
+ 7BDB96922077C299009D0651 /* MentatTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 7BDB96882077C299009D0651 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7BDB96912077C299009D0651 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7BDB96C9207B735A009D0651 /* fixtures in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 7BDB96852077C299009D0651 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7BDB96B32077C38E009D0651 /* RustObject.swift in Sources */,
+ 7BDB96C62077D347009D0651 /* Date+Int64.swift in Sources */,
+ 7BEB7D2C207D03DA000369AD /* TxReport.swift in Sources */,
+ 7BDB96B42077C38E009D0651 /* OptionalRustObject.swift in Sources */,
+ 7BDB96B22077C38E009D0651 /* RelResult.swift in Sources */,
+ 7BDB96AF2077C38E009D0651 /* Query.swift in Sources */,
+ 7BDB96CC207B7684009D0651 /* Errors.swift in Sources */,
+ 7BDB96B02077C38E009D0651 /* Mentat.swift in Sources */,
+ 7BDB96B72077C38E009D0651 /* TypedValue.swift in Sources */,
+ 7BDB96B52077C38E009D0651 /* TupleResult.swift in Sources */,
+ 7B74483D208DF667006CFFB0 /* Result+Unwrap.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7BDB968F2077C299009D0651 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7BDB96992077C299009D0651 /* MentatTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 7BDB96962077C299009D0651 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 7BDB96892077C299009D0651 /* Mentat */;
+ targetProxy = 7BDB96952077C299009D0651 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 7BDB969C2077C299009D0651 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.3;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ OTHER_CFLAGS = "-fembed-bitcode";
+ OTHER_LDFLAGS = "";
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Mentat";
+ SWIFT_OBJC_BRIDGING_HEADER = "";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 7BDB969D2077C299009D0651 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.3;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_CFLAGS = "-fembed-bitcode";
+ OTHER_LDFLAGS = "";
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Mentat";
+ SWIFT_OBJC_BRIDGING_HEADER = "";
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 7BDB969F2077C299009D0651 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = 8BHJ767F4Y;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Mentat/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../../../target/universal/release";
+ OTHER_CFLAGS = "-fembed-bitcode";
+ OTHER_LDFLAGS = "";
+ PRODUCT_BUNDLE_IDENTIFIER = com.mozilla.Mentat;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Mentat";
+ SWIFT_OBJC_BRIDGING_HEADER = "";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 7BDB96A02077C299009D0651 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = 8BHJ767F4Y;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_TESTABILITY = NO;
+ INFOPLIST_FILE = Mentat/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../../../target/universal/release";
+ OTHER_CFLAGS = "-fembed-bitcode";
+ OTHER_LDFLAGS = "";
+ PRODUCT_BUNDLE_IDENTIFIER = com.mozilla.Mentat;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Mentat";
+ SWIFT_OBJC_BRIDGING_HEADER = "";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ 7BDB96A22077C299009D0651 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 8BHJ767F4Y;
+ INFOPLIST_FILE = MentatTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ PRODUCT_BUNDLE_IDENTIFIER = com.mozilla.MentatTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 7BDB96A32077C299009D0651 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 8BHJ767F4Y;
+ INFOPLIST_FILE = MentatTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ PRODUCT_BUNDLE_IDENTIFIER = com.mozilla.MentatTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 4.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 7BDB96842077C299009D0651 /* Build configuration list for PBXProject "Mentat" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7BDB969C2077C299009D0651 /* Debug */,
+ 7BDB969D2077C299009D0651 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 7BDB969E2077C299009D0651 /* Build configuration list for PBXNativeTarget "Mentat" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7BDB969F2077C299009D0651 /* Debug */,
+ 7BDB96A02077C299009D0651 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 7BDB96A12077C299009D0651 /* Build configuration list for PBXNativeTarget "MentatTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7BDB96A22077C299009D0651 /* Debug */,
+ 7BDB96A32077C299009D0651 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 7BDB96812077C299009D0651 /* Project object */;
+}
diff --git a/sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..8f0c7f34
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 00000000..0c67376e
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat Debug.xcscheme b/sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat Debug.xcscheme
new file mode 100644
index 00000000..b08914c4
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat Debug.xcscheme
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat.xcscheme b/sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat.xcscheme
new file mode 100644
index 00000000..c03b16b0
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat.xcodeproj/xcshareddata/xcschemes/Mentat.xcscheme
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdks/swift/Mentat/Mentat/Core/TypedValue.swift b/sdks/swift/Mentat/Mentat/Core/TypedValue.swift
new file mode 100644
index 00000000..3db504a2
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Core/TypedValue.swift
@@ -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_into_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_into_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_into_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_into_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_into_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_into_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_into_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_into_uuid(self.raw!).pointee
+ self.value = UUID(uuid: bytes)
+ }
+ return self.value as! UUID?
+ }
+
+ override func cleanup(pointer: OpaquePointer) {
+ typed_value_destroy(pointer)
+ }
+}
diff --git a/sdks/swift/Mentat/Mentat/Errors/Errors.swift b/sdks/swift/Mentat/Mentat/Errors/Errors.swift
new file mode 100644
index 00000000..9091219b
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Errors/Errors.swift
@@ -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
+}
diff --git a/sdks/swift/Mentat/Mentat/Extensions/Date+Int64.swift b/sdks/swift/Mentat/Mentat/Extensions/Date+Int64.swift
new file mode 100644
index 00000000..068c666b
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Extensions/Date+Int64.swift
@@ -0,0 +1,22 @@
+/* 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
+
+extension Date {
+ /**
+ This `Date` as microseconds.
+
+ - Returns: The `timeIntervalSince1970` in microseconds
+ */
+ func toMicroseconds() -> Int64 {
+ return Int64(self.timeIntervalSince1970 * 1_000_000)
+ }
+}
diff --git a/sdks/swift/Mentat/Mentat/Extensions/Result+Unwrap.swift b/sdks/swift/Mentat/Mentat/Extensions/Result+Unwrap.swift
new file mode 100644
index 00000000..3d94903a
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Extensions/Result+Unwrap.swift
@@ -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
+ }
+}
diff --git a/sdks/swift/Mentat/Mentat/Info.plist b/sdks/swift/Mentat/Mentat/Info.plist
new file mode 100644
index 00000000..1007fd9d
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSPrincipalClass
+
+
+
diff --git a/sdks/swift/Mentat/Mentat/Mentat.h b/sdks/swift/Mentat/Mentat/Mentat.h
new file mode 100644
index 00000000..e08f33ca
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Mentat.h
@@ -0,0 +1,18 @@
+/* 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
+
+//! Project version number for Mentat.
+FOUNDATION_EXPORT double MentatVersionNumber;
+
+//! Project version string for Mentat.
+FOUNDATION_EXPORT const unsigned char MentatVersionString[];
+
diff --git a/sdks/swift/Mentat/Mentat/Mentat.swift b/sdks/swift/Mentat/Mentat/Mentat.swift
new file mode 100644
index 00000000..5dd6fe66
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Mentat.swift
@@ -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 implemented 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.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, reports: UnsafePointer) {
+ let key = String(cString: key)
+ guard let observer = Mentat.observers[key] else { return }
+ DispatchQueue.global(qos: .background).async {
+ observer.transactionDidOccur(key: key, reports: [TxChange]())
+ }
+}
diff --git a/sdks/swift/Mentat/Mentat/Query/Query.swift b/sdks/swift/Mentat/Mentat/Query/Query.swift
new file mode 100644
index 00000000..7521b022
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Query/Query.swift
@@ -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 construct 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)
+ }
+}
diff --git a/sdks/swift/Mentat/Mentat/Query/RelResult.swift b/sdks/swift/Mentat/Mentat/Query/RelResult.swift
new file mode 100644
index 00000000..7124c035
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Query/RelResult.swift
@@ -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 = typed_value_result_set_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 = typed_value_result_set_into_iter(try self.validPointer())
+ self.raw = nil
+ return RelResultIterator(iter: rowIter)
+ } catch {
+ return RelResultIterator(iter: nil)
+ }
+ }
+}
diff --git a/sdks/swift/Mentat/Mentat/Query/TupleResult.swift b/sdks/swift/Mentat/Mentat/Query/TupleResult.swift
new file mode 100644
index 00000000..d413581b
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Query/TupleResult.swift
@@ -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_into_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_into_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_into_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_into_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_into_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_into_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_into_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_into_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 = typed_value_list_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 = typed_value_list_into_iter(raw)
+ return ColResultIterator(iter: rowIter)
+ }
+}
diff --git a/sdks/swift/Mentat/Mentat/Rust/OptionalRustObject.swift b/sdks/swift/Mentat/Mentat/Rust/OptionalRustObject.swift
new file mode 100644
index 00000000..7f2fe74d
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Rust/OptionalRustObject.swift
@@ -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.")
+ }
+}
+
diff --git a/sdks/swift/Mentat/Mentat/Rust/RustObject.swift b/sdks/swift/Mentat/Mentat/Rust/RustObject.swift
new file mode 100644
index 00000000..7648fadb
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Rust/RustObject.swift
@@ -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.")
+ }
+}
diff --git a/sdks/swift/Mentat/Mentat/Transact/TxReport.swift b/sdks/swift/Mentat/Mentat/Transact/TxReport.swift
new file mode 100644
index 00000000..8658158d
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/Transact/TxReport.swift
@@ -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.
+
+ - 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)
+ }
+}
diff --git a/sdks/swift/Mentat/Mentat/module.map b/sdks/swift/Mentat/Mentat/module.map
new file mode 100644
index 00000000..5cb44399
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/module.map
@@ -0,0 +1,4 @@
+module MentatStore [system][extern_c] {
+ header "store.h"
+ export *
+}
diff --git a/sdks/swift/Mentat/Mentat/store.h b/sdks/swift/Mentat/Mentat/store.h
new file mode 100644
index 00000000..7f86e009
--- /dev/null
+++ b/sdks/swift/Mentat/Mentat/store.h
@@ -0,0 +1,171 @@
+/* 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. */
+
+#ifndef store_h
+#define store_h
+#include
+#include
+
+/*
+ * This file contains headers for all of the structs and functions that map directly to the functions
+ * defined in mentat/ffi/src/lib.rs.
+ *
+ * The C in this file is specifically formatted to be used with Objective C and Swift and contains
+ * macros and flags that will not be recognised by other C based languages.
+ */
+
+/*
+ A mapping of the TxChange repr(C) Rust object.
+ The memory for this is managed by Swift.
+ */
+struct TxChange {
+ int64_t txid;
+ int64_t*_Nonnull* _Nonnull changes;
+ uint64_t len;
+};
+
+/*
+ A mapping of the TxChangeList repr(C) Rust object.
+ The memory for this is managed by Swift.
+ */
+struct TxChangeList {
+ struct TxChange*_Nonnull* _Nonnull reports;
+ uint64_t len;
+};
+typedef struct TxChangeList TxChangeList;
+
+/*
+ A mapping of the ExternResult repr(C) Rust object.
+ The memory for this is managed by Swift.
+ */
+struct Result {
+ void* _Nullable ok;
+ char* _Nullable err;
+};
+typedef struct Result Result;
+
+/*
+ A mapping of the ExternOption repr(C) Rust object.
+ The memory for this is managed by Swift.
+ */
+struct Option {
+ void* _Nullable value;
+};
+typedef struct Option Option;
+
+/*
+ A Mapping for the ValueType Rust object.
+ */
+typedef NS_ENUM(NSInteger, ValueType) {
+ ValueTypeRef = 1,
+ ValueTypeBoolean,
+ ValueTypeInstant,
+ ValueTypeLong,
+ ValueTypeDouble,
+ ValueTypeString,
+ ValueTypeKeyword,
+ ValueTypeUuid
+};
+
+// Opaque Structs mapping to Rust types that are passed over the FFI boundary
+struct EntityBuilder;
+struct InProgress;
+struct InProgressBuilder;
+struct Query;
+struct QueryResultRow;
+struct QueryResultRows;
+struct QueryRowsIterator;
+struct QueryRowIterator;
+struct Store;
+struct TxReport;
+struct TypedValue;
+
+// Store
+struct Store*_Nonnull store_open(const char*_Nonnull uri);
+
+// Destructors.
+void destroy(void* _Nullable obj);
+void query_builder_destroy(struct Query* _Nullable obj);
+void store_destroy(struct Store* _Nonnull obj);
+void tx_report_destroy(struct TxReport* _Nonnull obj);
+void typed_value_destroy(struct TypedValue* _Nullable obj);
+void typed_value_list_destroy(struct QueryResultRow* _Nullable obj);
+void typed_value_list_iter_destroy(struct QueryRowIterator* _Nullable obj);
+void typed_value_result_set_destroy(struct QueryResultRows* _Nullable obj);
+void typed_value_result_set_iter_destroy(struct QueryRowsIterator* _Nullable obj);
+
+// transact
+struct Result*_Nonnull store_transact(struct Store*_Nonnull store, const char* _Nonnull transaction);
+const int64_t* _Nullable tx_report_entity_for_temp_id(const struct TxReport* _Nonnull report, const char* _Nonnull tempid);
+int64_t tx_report_get_entid(const struct TxReport* _Nonnull report);
+int64_t tx_report_get_tx_instant(const struct TxReport* _Nonnull report);
+
+
+// Sync
+struct Result*_Nonnull store_sync(struct Store*_Nonnull store, const char* _Nonnull user_uuid, const char* _Nonnull server_uri);
+
+// Observers
+void store_register_observer(struct Store*_Nonnull store, const char* _Nonnull key, const int64_t* _Nonnull attributes, const int64_t len, void (*_Nonnull callback_fn)(const char* _Nonnull key, const struct TxChangeList* _Nonnull reports));
+void store_unregister_observer(struct Store*_Nonnull store, const char* _Nonnull key);
+int64_t store_entid_for_attribute(struct Store*_Nonnull store, const char*_Nonnull attr);
+int64_t changelist_entry_at(const struct TxChange* _Nonnull report, size_t index);
+
+// Query
+struct Query*_Nonnull store_query(struct Store*_Nonnull store, const char* _Nonnull query);
+struct Result*_Nonnull store_value_for_attribute(struct Store*_Nonnull store, const int64_t entid, const char* _Nonnull attribute);
+
+// Query Variable Binding
+void query_builder_bind_long(struct Query*_Nonnull query, const char* _Nonnull var, const int64_t value);
+void query_builder_bind_ref(struct Query*_Nonnull query, const char* _Nonnull var, const int64_t value);
+void query_builder_bind_ref_kw(struct Query*_Nonnull query, const char* _Nonnull var, const char* _Nonnull value);
+void query_builder_bind_kw(struct Query*_Nonnull query, const char* _Nonnull var, const char* _Nonnull value);
+void query_builder_bind_boolean(struct Query*_Nonnull query, const char* _Nonnull var, const int32_t value);
+void query_builder_bind_double(struct Query*_Nonnull query, const char* _Nonnull var, const double value);
+void query_builder_bind_timestamp(struct Query*_Nonnull query, const char* _Nonnull var, const int64_t value);
+void query_builder_bind_string(struct Query*_Nonnull query, const char* _Nonnull var, const char* _Nonnull value);
+void query_builder_bind_uuid(struct Query*_Nonnull query, const char* _Nonnull var, const uuid_t* _Nonnull value);
+
+// Query execution
+struct Result*_Nonnull query_builder_execute(struct Query*_Nonnull query);
+struct Result*_Nonnull query_builder_execute_scalar(struct Query*_Nonnull query);
+struct Result*_Nonnull query_builder_execute_coll(struct Query*_Nonnull query);
+struct Result*_Nonnull query_builder_execute_tuple(struct Query*_Nonnull query);
+
+// Query Result Processing
+int64_t typed_value_into_long(struct TypedValue*_Nonnull value);
+int64_t typed_value_into_entid(struct TypedValue*_Nonnull value);
+const char* _Nonnull typed_value_into_kw(struct TypedValue*_Nonnull value);
+int32_t typed_value_into_boolean(struct TypedValue*_Nonnull value);
+double typed_value_into_double(struct TypedValue*_Nonnull value);
+int64_t typed_value_into_timestamp(struct TypedValue*_Nonnull value);
+const char* _Nonnull typed_value_into_string(struct TypedValue*_Nonnull value);
+const uuid_t* _Nonnull typed_value_into_uuid(struct TypedValue*_Nonnull value);
+enum ValueType typed_value_value_type(struct TypedValue*_Nonnull value);
+
+struct QueryResultRow* _Nullable row_at_index(struct QueryResultRows* _Nonnull rows, const int32_t index);
+struct QueryRowsIterator* _Nonnull typed_value_result_set_into_iter(struct QueryResultRows* _Nonnull rows);
+struct QueryResultRow* _Nullable typed_value_result_set_iter_next(struct QueryRowsIterator* _Nonnull iter);
+struct QueryRowIterator* _Nonnull typed_value_list_into_iter(struct QueryResultRow* _Nonnull row);
+struct TypedValue* _Nullable typed_value_list_iter_next(struct QueryRowIterator* _Nonnull iter);
+
+struct TypedValue* _Nonnull value_at_index(struct QueryResultRow* _Nonnull row, const int32_t index);
+int64_t value_at_index_into_long(struct QueryResultRow* _Nonnull row, const int32_t index);
+int64_t value_at_index_into_entid(struct QueryResultRow* _Nonnull row, const int32_t index);
+const char* _Nonnull value_at_index_into_kw(struct QueryResultRow* _Nonnull row, const int32_t index);
+int32_t value_at_index_into_boolean(struct QueryResultRow* _Nonnull row, const int32_t index);
+double value_at_index_into_double(struct QueryResultRow* _Nonnull row, const int32_t index);
+int64_t value_at_index_into_timestamp(struct QueryResultRow* _Nonnull row, const int32_t index);
+const char* _Nonnull value_at_index_into_string(struct QueryResultRow* _Nonnull row, const int32_t index);
+const uuid_t* _Nonnull value_at_index_into_uuid(struct QueryResultRow* _Nonnull row, const int32_t index);
+
+// Transaction change lists
+const struct TxChange* _Nullable tx_change_list_entry_at(const struct TxChangeList* _Nonnull list, size_t index);
+
+#endif /* store_h */
diff --git a/sdks/swift/Mentat/MentatTests/Info.plist b/sdks/swift/Mentat/MentatTests/Info.plist
new file mode 100644
index 00000000..6c40a6cd
--- /dev/null
+++ b/sdks/swift/Mentat/MentatTests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/sdks/swift/Mentat/MentatTests/MentatTests.swift b/sdks/swift/Mentat/MentatTests/MentatTests.swift
new file mode 100644
index 00000000..3587e559
--- /dev/null
+++ b/sdks/swift/Mentat/MentatTests/MentatTests.swift
@@ -0,0 +1,765 @@
+/* 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 XCTest
+
+@testable import Mentat
+
+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()
+ }
+
+ // test that a store can be opened in memory
+ func testOpenInMemoryStore() {
+ XCTAssertNotNil(Mentat().raw)
+ }
+
+ // test that a store can be opened in a specific location
+ func testOpenStoreInLocation() {
+ let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
+ let documentsURL = paths[0]
+ let storeURI = documentsURL.appendingPathComponent("test.db", isDirectory: false).absoluteString
+ XCTAssertNotNil(Mentat(storeURI: storeURI).raw)
+ }
+
+ func readFile(forResource resource: String, withExtension ext: String, subdirectory: String ) throws -> String {
+ let bundle = Bundle(for: type(of: self))
+ let schemaUrl = bundle.url(forResource: resource, withExtension: ext, subdirectory: subdirectory)!
+ let contents = try String(contentsOf: schemaUrl)
+ return contents
+ }
+
+ func readCitiesSchema() throws -> String {
+ guard let schema = self.citiesSchema else {
+ self.citiesSchema = try self.readFile(forResource: "cities", withExtension: "schema", subdirectory: "fixtures")
+ return self.citiesSchema!
+ }
+
+ return schema
+ }
+
+ func readSeattleData() throws -> String {
+ guard let data = self.seattleData else {
+ self.seattleData = try self.readFile(forResource: "all_seattle", withExtension: "edn", subdirectory: "fixtures")
+ return self.seattleData!
+ }
+
+ return data
+ }
+
+ func transactCitiesSchema(mentat: Mentat) throws -> TxReport {
+ let vocab = try readCitiesSchema()
+ let report = try mentat.transact(transaction: vocab)
+ return report
+ }
+
+ func transactSeattleData(mentat: Mentat) throws -> TxReport {
+ let data = try readSeattleData()
+ let report = try mentat.transact(transaction: data)
+ return report
+ }
+
+ func openAndInitializeCitiesStore() -> Mentat {
+ guard let mentat = self.store else {
+ let mentat = Mentat()
+ let _ = try! self.transactCitiesSchema(mentat: mentat)
+ let _ = try! self.transactSeattleData(mentat: mentat)
+ self.store = mentat
+ return mentat
+ }
+
+ return mentat
+ }
+
+ func populateWithTypesSchema(mentat: Mentat) -> (TxReport?, TxReport?) {
+ do {
+ let schema = """
+ [
+ [:db/add "b" :db/ident :foo/boolean]
+ [:db/add "b" :db/valueType :db.type/boolean]
+ [:db/add "b" :db/cardinality :db.cardinality/one]
+ [:db/add "l" :db/ident :foo/long]
+ [:db/add "l" :db/valueType :db.type/long]
+ [:db/add "l" :db/cardinality :db.cardinality/one]
+ [:db/add "r" :db/ident :foo/ref]
+ [:db/add "r" :db/valueType :db.type/ref]
+ [:db/add "r" :db/cardinality :db.cardinality/one]
+ [:db/add "i" :db/ident :foo/instant]
+ [:db/add "i" :db/valueType :db.type/instant]
+ [:db/add "i" :db/cardinality :db.cardinality/one]
+ [:db/add "d" :db/ident :foo/double]
+ [:db/add "d" :db/valueType :db.type/double]
+ [:db/add "d" :db/cardinality :db.cardinality/one]
+ [:db/add "s" :db/ident :foo/string]
+ [:db/add "s" :db/valueType :db.type/string]
+ [:db/add "s" :db/cardinality :db.cardinality/one]
+ [:db/add "k" :db/ident :foo/keyword]
+ [:db/add "k" :db/valueType :db.type/keyword]
+ [:db/add "k" :db/cardinality :db.cardinality/one]
+ [:db/add "u" :db/ident :foo/uuid]
+ [:db/add "u" :db/valueType :db.type/uuid]
+ [:db/add "u" :db/cardinality :db.cardinality/one]
+ ]
+ """
+ let report = try mentat.transact(transaction: schema)
+ let stringEntid = report.entid(forTempId: "s")!
+
+ let data = """
+ [
+ [:db/add "a" :foo/boolean true]
+ [:db/add "a" :foo/long 25]
+ [:db/add "a" :foo/instant #inst "2017-01-01T11:00:00.000Z"]
+ [:db/add "a" :foo/double 11.23]
+ [:db/add "a" :foo/string "The higher we soar the smaller we appear to those who cannot fly."]
+ [:db/add "a" :foo/keyword :foo/string]
+ [:db/add "a" :foo/uuid #uuid "550e8400-e29b-41d4-a716-446655440000"]
+ [:db/add "b" :foo/boolean false]
+ [:db/add "b" :foo/ref \(stringEntid)]
+ [:db/add "b" :foo/keyword :foo/string]
+ [:db/add "b" :foo/long 50]
+ [:db/add "b" :foo/instant #inst "2018-01-01T11:00:00.000Z"]
+ [:db/add "b" :foo/double 22.46]
+ [:db/add "b" :foo/string "Silence is worse; all truths that are kept silent become poisonous."]
+ [:db/add "b" :foo/uuid #uuid "4cb3f828-752d-497a-90c9-b1fd516d5644"]
+ ]
+ """
+ let dataReport = try mentat.transact(transaction: data)
+ return (report, dataReport)
+ } catch {
+ assertionFailure(error.localizedDescription)
+ }
+ return (nil, nil)
+ }
+
+ func test1TransactVocabulary() {
+ do {
+ let mentat = Mentat()
+ let vocab = try readCitiesSchema()
+ let report = try mentat.transact(transaction: vocab)
+ XCTAssertNotNil(report)
+ assert(report.txId > 0)
+ } catch {
+ assertionFailure(error.localizedDescription)
+ }
+ }
+
+ func test2TransactEntities() {
+ do {
+ let mentat = Mentat()
+ let vocab = try readCitiesSchema()
+ let _ = try mentat.transact(transaction: vocab)
+ let data = try readSeattleData()
+ let report = try mentat.transact(transaction: data)
+ XCTAssertNotNil(report)
+ assert(report.txId > 0)
+ let entid = report.entid(forTempId: "a17592186045438")
+ assert(entid == 65566)
+ } catch {
+ assertionFailure(error.localizedDescription)
+ }
+ }
+
+ func testQueryScalar() {
+ let mentat = openAndInitializeCitiesStore()
+ let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query).bind(varName: "?name", toString: "Wallingford").runScalar(callback: { scalarResult in
+ guard let result = scalarResult?.asString() else {
+ return assertionFailure("No String value received")
+ }
+ assert(result == "KOMO Communities - Wallingford")
+ expect.fulfill()
+ }))
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testQueryColl() {
+ let mentat = openAndInitializeCitiesStore()
+ let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in
+ guard let rows = collResult else {
+ return assertionFailure("No results received")
+ }
+ // we are expecting 3 results
+ for i in 0..<3 {
+ let _ = rows.asDate(index: i)
+ assert(true)
+ }
+ expect.fulfill()
+ }))
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testQueryCollResultIterator() {
+ let mentat = openAndInitializeCitiesStore()
+ let query = "[:find [?when ...] :where [_ :db/txInstant ?when] :order (asc ?when)]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query).runColl(callback: { collResult in
+ guard let rows = collResult else {
+ return assertionFailure("No results received")
+ }
+
+ rows.forEach({ (value) in
+ assert(value.valueType.rawValue == 2)
+ })
+ expect.fulfill()
+ }))
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testQueryTuple() {
+ let mentat = openAndInitializeCitiesStore()
+ let query = """
+ [:find [?name ?cat]
+ :where
+ [?c :community/name ?name]
+ [?c :community/type :community.type/website]
+ [(fulltext $ :community/category "food") [[?c ?cat]]]]
+ """
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query).runTuple(callback: { tupleResult in
+ guard let tuple = tupleResult else {
+ return assertionFailure("expecting a result")
+ }
+ let name = tuple.asString(index: 0)
+ let category = tuple.asString(index: 1)
+ assert(name == "Community Harvest of Southwest Seattle")
+ assert(category == "sustainable food")
+ expect.fulfill()
+ }))
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testQueryRel() {
+ let mentat = openAndInitializeCitiesStore()
+ let query = """
+ [:find ?name ?cat
+ :where
+ [?c :community/name ?name]
+ [?c :community/type :community.type/website]
+ [(fulltext $ :community/category "food") [[?c ?cat]]]]
+ """
+ let expect = expectation(description: "Query is executed")
+ let expectedResults = [("InBallard", "food"),
+ ("Seattle Chinatown Guide", "food"),
+ ("Community Harvest of Southwest Seattle", "sustainable food"),
+ ("University District Food Bank", "food bank")]
+ XCTAssertNoThrow(try mentat.query(query: query).run(callback: { relResult in
+ guard let rows = relResult else {
+ return assertionFailure("No results received")
+ }
+
+ for (i, row) in rows.enumerated() {
+ let (name, category) = expectedResults[i]
+ assert( row.asString(index: 0) == name)
+ assert(row.asString(index: 1) == category)
+ }
+ expect.fulfill()
+ }))
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testQueryRelResultIterator() {
+ let mentat = openAndInitializeCitiesStore()
+ let query = """
+ [:find ?name ?cat
+ :where
+ [?c :community/name ?name]
+ [?c :community/type :community.type/website]
+ [(fulltext $ :community/category "food") [[?c ?cat]]]]
+ """
+ let expect = expectation(description: "Query is executed")
+ let expectedResults = [("InBallard", "food"),
+ ("Seattle Chinatown Guide", "food"),
+ ("Community Harvest of Southwest Seattle", "sustainable food"),
+ ("University District Food Bank", "food bank")]
+ XCTAssertNoThrow(try mentat.query(query: query).run(callback: { relResult in
+ guard let rows = relResult else {
+ return assertionFailure("No results received")
+ }
+
+ var i = 0
+ rows.forEach({ (row) in
+ let (name, category) = expectedResults[i]
+ i += 1
+ assert(row.asString(index: 0) == name)
+ assert(row.asString(index: 1) == category)
+ })
+ assert(i == 4)
+ expect.fulfill()
+ }))
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testBindLong() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")
+ let query = "[:find ?e . :in ?long :where [?e :foo/long ?long]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?long", toLong: 25)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asEntid() == aEntid)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testBindRef() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let stringEntid = mentat.entidForAttribute(attribute: ":foo/string")
+ let bEntid = report!.entid(forTempId: "b")
+ let query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?ref", toReference: stringEntid)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asEntid() == bEntid)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testBindKwRef() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let bEntid = report!.entid(forTempId: "b")
+ let query = "[:find ?e . :in ?ref :where [?e :foo/ref ?ref]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?ref", toReference: ":foo/string")
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asEntid() == bEntid)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testBindKw() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")
+ let query = "[:find ?e . :in ?kw :where [?e :foo/keyword ?kw]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?kw", toKeyword: ":foo/string")
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asEntid() == aEntid)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testBindDate() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")
+ let query = "[:find [?e ?d] :in ?now :where [?e :foo/instant ?d] [(< ?d ?now)]]"
+ let expect = expectation(description: "Query is executed")
+
+ let formatter = DateFormatter()
+ formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
+ let boundDate = formatter.date(from: "2018-04-16T16:39:18+00:00")!
+
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?now", toDate: boundDate)
+ .runTuple { row in
+ XCTAssertNotNil(row)
+ assert(row?.asEntid(index: 0) == aEntid)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+
+ func testBindString() {
+ let mentat = openAndInitializeCitiesStore()
+ let query = "[:find ?n . :in ?name :where [(fulltext $ :community/name ?name) [[?e ?n]]]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?name", toString: "Wallingford")
+ .runScalar(callback: { scalarResult in
+ guard let result = scalarResult?.asString() else {
+ return assertionFailure("No String value received")
+ }
+ assert(result == "KOMO Communities - Wallingford")
+ expect.fulfill()
+ }))
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testBindUuid() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")
+ let query = "[:find ?e . :in ?uuid :where [?e :foo/uuid ?uuid]]"
+ let uuid = UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")!
+ let expect = expectation(description: "Query is rund")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?uuid", toUuid: uuid)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asEntid() == aEntid)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testBindBoolean() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")
+ let query = "[:find ?e . :in ?bool :where [?e :foo/boolean ?bool]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?bool", toBoolean: true)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asEntid() == aEntid)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testBindDouble() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")
+ let query = "[:find ?e . :in ?double :where [?e :foo/double ?double]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?double", toDouble: 11.23)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asEntid() == aEntid)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testTypedValueAsLong() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")!
+ let query = "[:find ?v . :in ?e :where [?e :foo/long ?v]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?e", toReference: aEntid)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asLong() == 25)
+ assert(value?.asLong() == 25)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testTypedValueAsRef() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")!
+ let query = "[:find ?e . :where [?e :foo/long 25]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asEntid() == aEntid)
+ assert(value?.asEntid() == aEntid)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testTypedValueAsKw() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")!
+ let query = "[:find ?v . :in ?e :where [?e :foo/keyword ?v]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?e", toReference: aEntid)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asKeyword() == ":foo/string")
+ assert(value?.asKeyword() == ":foo/string")
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testTypedValueAsBoolean() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")!
+ let query = "[:find ?v . :in ?e :where [?e :foo/boolean ?v]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?e", toReference: aEntid)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asBool() == true)
+ assert(value?.asBool() == true)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testTypedValueAsDouble() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")!
+ let query = "[:find ?v . :in ?e :where [?e :foo/double ?v]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?e", toReference: aEntid)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asDouble() == 11.23)
+ assert(value?.asDouble() == 11.23)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testTypedValueAsDate() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")!
+ let query = "[:find ?v . :in ?e :where [?e :foo/instant ?v]]"
+ let expect = expectation(description: "Query is executed")
+
+ let formatter = DateFormatter()
+ formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
+ let expectedDate = formatter.date(from: "2017-01-01T11:00:00+00:00")
+
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?e", toReference: aEntid)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asDate() == expectedDate)
+ assert(value?.asDate() == expectedDate)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testTypedValueAsString() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")!
+ let query = "[:find ?v . :in ?e :where [?e :foo/string ?v]]"
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?e", toReference: aEntid)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asString() == "The higher we soar the smaller we appear to those who cannot fly.")
+ assert(value?.asString() == "The higher we soar the smaller we appear to those who cannot fly.")
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testTypedValueAsUuid() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")!
+ let query = "[:find ?v . :in ?e :where [?e :foo/uuid ?v]]"
+ let expectedUuid = UUID(uuidString: "550e8400-e29b-41d4-a716-446655440000")!
+ let expect = expectation(description: "Query is executed")
+ XCTAssertNoThrow(try mentat.query(query: query)
+ .bind(varName: "?e", toReference: aEntid)
+ .runScalar { value in
+ XCTAssertNotNil(value)
+ assert(value?.asUUID() == expectedUuid)
+ assert(value?.asUUID() == expectedUuid)
+ expect.fulfill()
+ })
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testValueForAttributeOfEntity() {
+ let mentat = Mentat()
+ let (_, report) = self.populateWithTypesSchema(mentat: mentat)
+ let aEntid = report!.entid(forTempId: "a")!
+ var value: TypedValue? = nil;
+ XCTAssertNoThrow(value = try mentat.value(forAttribute: ":foo/long", ofEntity: aEntid))
+ XCTAssertNotNil(value)
+ assert(value?.asLong() == 25)
+ }
+
+ func testEntidForAttribute() {
+ let mentat = Mentat()
+ let _ = self.populateWithTypesSchema(mentat: mentat)
+ let entid = mentat.entidForAttribute(attribute: ":foo/long")
+ assert(entid == 65540)
+ }
+
+ func testMultipleQueries() {
+ let mentat = Mentat()
+ let _ = self.populateWithTypesSchema(mentat: mentat)
+ let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]")
+
+ let q1Expect = expectation(description: "Query 1 is executed")
+ XCTAssertNoThrow(try q1.run { results in
+ XCTAssertNotNil(results)
+ q1Expect.fulfill()
+ })
+
+ let q2 = mentat.query(query: "[:find ?x :where [_ _ ?x]]")
+ let q2Expect = expectation(description: "Query 2 is executed")
+ XCTAssertNoThrow(try q2.run { results in
+ XCTAssertNotNil(results)
+ q2Expect.fulfill()
+ })
+
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+ func testNestedQueries() {
+ let mentat = Mentat()
+ let _ = self.populateWithTypesSchema(mentat: mentat)
+ let q1 = mentat.query(query: "[:find ?x :where [?x _ _]]")
+ let q2 = mentat.query(query: "[:find ?x :where [_ _ ?x]]")
+
+ let expect = expectation(description: "Query 1 is executed")
+ XCTAssertNoThrow(try q1.run { results in
+ XCTAssertNotNil(results)
+ try? q2.run { results in
+ XCTAssertNotNil(results)
+ expect.fulfill()
+ }
+ })
+
+ waitForExpectations(timeout: 1) { error in
+ if let error = error {
+ assertionFailure("waitForExpectationsWithTimeout errored: \(error)")
+ }
+ }
+ }
+
+
+ // TODO: Add tests for transaction observation
+}
diff --git a/src/conn.rs b/src/conn.rs
index 813afe74..585c9943 100644
--- a/src/conn.rs
+++ b/src/conn.rs
@@ -94,7 +94,6 @@ use mentat_tolstoy::Syncer;
use uuid::Uuid;
use entity_builder::{
- BuildTerms,
InProgressBuilder,
TermBuilder,
};
@@ -647,10 +646,6 @@ impl Store {
pub fn unregister_observer(&mut self, key: &String) {
self.conn.unregister_observer(key);
}
-
- pub fn assert_datom(&mut self, entid: T, attribute: Keyword, value: TypedValue) -> Result<()> where T: Into {
- self.conn.assert_datom(&mut self.sqlite, entid, attribute, value)
- }
}
impl Queryable for Store {
@@ -973,18 +968,6 @@ impl Conn {
pub fn unregister_observer(&mut self, key: &String) {
self.tx_observer_service.lock().unwrap().deregister(key);
}
-
- // TODO: expose the entity builder over FFI and remove the need for this function entirely
- // It's really only here in order to keep the FFI layer as thin as possible.
- // Once the entity builder is exposed, we can perform all of these functions over FFI from the client.
- pub fn assert_datom(&mut self, sqlite: &mut rusqlite::Connection, entid: T, attribute: Keyword, value: TypedValue) -> Result<()> where T: Into {
- let in_progress = self.begin_transaction(sqlite)?;
- let mut builder = in_progress.builder().describe(entid.into());
- builder.add_kw(&attribute, value)?;
- builder.commit()
- .map_err(|e| e.into())
- .and(Ok(()))
- }
}
#[cfg(test)]
diff --git a/src/lib.rs b/src/lib.rs
index 029ad1e9..d73b9529 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -55,6 +55,7 @@ pub use mentat_query::{
pub use mentat_db::{
CORE_SCHEMA_VERSION,
DB_SCHEMA_CORE,
+ AttributeSet,
TxObserver,
TxReport,
new_connection,
diff --git a/src/query_builder.rs b/src/query_builder.rs
index 2d998eb7..5abeca60 100644
--- a/src/query_builder.rs
+++ b/src/query_builder.rs
@@ -14,10 +14,12 @@ use std::collections::{
};
use mentat_core::{
+ DateTime,
Entid,
Keyword,
Binding,
TypedValue,
+ Utc,
ValueType,
};
@@ -37,15 +39,15 @@ use errors::{
};
pub struct QueryBuilder<'a> {
- sql: String,
+ query: String,
values: BTreeMap,
types: BTreeMap,
store: &'a mut Store,
}
impl<'a> QueryBuilder<'a> {
- pub fn new(store: &'a mut Store, sql: T) -> QueryBuilder where T: Into {
- QueryBuilder { sql: sql.into(), values: BTreeMap::new(), types: BTreeMap::new(), store }
+ pub fn new(store: &'a mut Store, query: T) -> QueryBuilder where T: Into {
+ QueryBuilder { query: query.into(), values: BTreeMap::new(), types: BTreeMap::new(), store }
}
pub fn bind_value(&mut self, var: &str, value: T) -> &mut Self where T: Into {
@@ -71,6 +73,12 @@ impl<'a> QueryBuilder<'a> {
pub fn bind_instant(&mut self, var: &str, value: i64) -> &mut Self {
self.values.insert(Variable::from_valid_name(var), TypedValue::instant(value));
+
+ self
+ }
+
+ pub fn bind_date_time(&mut self, var: &str, value: DateTime) -> &mut Self {
+ self.values.insert(Variable::from_valid_name(var), TypedValue::Instant(value));
self
}
@@ -84,7 +92,7 @@ impl<'a> QueryBuilder<'a> {
let types = ::std::mem::replace(&mut self.types, Default::default());
let query_inputs = QueryInputs::new(types, values)?;
let read = self.store.begin_read()?;
- read.q_once(&self.sql, query_inputs)
+ read.q_once(&self.query, query_inputs)
}
pub fn execute_scalar(&mut self) -> Result