From 08387d4754fd744a31202782e24fafc9f66d6de7 Mon Sep 17 00:00:00 2001
From: Emily Toop
+ * Row values can be fetched as one of the following types:
+ *
+ * To iterate over the result set use standard iteration flows.
+ */
+public class CollResult extends TupleResult implements Iterable
+ *
+ *
+ *
+ * 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.
+ */
+public class Mentat extends RustObject {
+
+ static {
+ System.loadLibrary("mentat_ffi");
+ }
+
+ /**
+ * Open a connection to a Store in a given location.
+ * If the store does not already exist, one will be created.
+ * @param dbPath The URI as a String of the store to open.
+ */
+ public Mentat(String dbPath) {
+ this.rawPointer = JNA.INSTANCE.store_open(dbPath);
+ }
+
+ /**
+ * Open a connection to an in-memory Store.
+ */
+ public Mentat() {
+ this.rawPointer = JNA.INSTANCE.store_open("");
+ }
+
+ /**
+ * Create a new Mentat with the provided pointer to a Mentat Store
+ * @param rawPointer A pointer to a Mentat Store.
+ */
+ public Mentat(Pointer rawPointer) { this.rawPointer = rawPointer; }
+
+ /**
+ * Simple transact of an EDN string.
+ * TODO: Throw an exception if the transact fails
+ * @param transaction The string, as EDN, to be transacted.
+ * @return The {@link TxReport} of the completed transaction
+ */
+ public TxReport transact(String transaction) {
+ RustResult result = JNA.INSTANCE.store_transact(this.rawPointer, transaction);
+ if (result.isFailure()) {
+ Log.i("Mentat", result.err);
+ return null;
+ }
+
+ if (result.isSuccess()) {
+ return new TxReport(result.ok);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the the `Entid` of the attribute
+ * @param attribute The string represeting the attribute whose `Entid` we are after. The string is represented as `:namespace/name`.
+ * @return The `Entid` associated with the attribute.
+ */
+ public long entIdForAttribute(String attribute) {
+ return JNA.INSTANCE.store_entid_for_attribute(this.rawPointer, attribute);
+ }
+
+ /**
+ * Start a query.
+ * @param query The string represeting the the query to be executed.
+ * @return The {@link Query} representing the query that can be executed.
+ */
+ public Query query(String query) {
+ return new Query(JNA.INSTANCE.store_query(this.rawPointer, query));
+ }
+
+ /**
+ * Retrieve a single value of an attribute for an Entity
+ * TODO: Throw an exception if there is no the result contains an error.
+ * @param attribute The string the attribute whose value is to be returned. The string is represented as `:namespace/name`.
+ * @param entid The `Entid` of the entity we want the value from.
+ * @return The {@link TypedValue} containing the value of the attribute for the entity.
+ */
+ public TypedValue valueForAttributeOfEntity(String attribute, long entid) {
+ RustResult result = JNA.INSTANCE.store_value_for_attribute(this.rawPointer, entid, attribute);
+
+ if (result.isSuccess()) {
+ return new TypedValue(result.ok);
+ }
+
+ if (result.isFailure()) {
+ Log.i("Mentat", result.err);
+ }
+
+ return null;
+ }
+
+ /**
+ * Register an callback and a set of attributes to observer for transaction observation.
+ * The callback function is called when a transaction occurs in the `Store` that this `Mentat`
+ * is connected to that affects the attributes that an observer has registered for.
+ * @param key `String` representing an identifier for the observer.
+ * @param attributes An array of Strings representing the attributes that the observer wishes
+ * to be notified about if they are referenced in a transaction.
+ * @param callback the function to call when an observer notice is fired.
+ */
+ public void registerObserver(String key, String[] attributes, TxObserverCallback callback) {
+ // turn string array into int array
+ long[] attrEntids = new long[attributes.length];
+ for(int i = 0; i < attributes.length; i++) {
+ attrEntids[i] = JNA.INSTANCE.store_entid_for_attribute(this.rawPointer, attributes[i]);
+ }
+ Log.i("Mentat", "Registering observer {" + key + "} for attributes:");
+ for (int i = 0; i < attrEntids.length; i++) {
+ Log.i("Mentat", "entid: " + attrEntids[i]);
+ }
+ final Pointer entidsNativeArray = new Memory(8 * attrEntids.length);
+ entidsNativeArray.write(0, attrEntids, 0, attrEntids.length);
+ JNA.INSTANCE.store_register_observer(rawPointer, key, entidsNativeArray, attrEntids.length, callback);
+ }
+
+ /**
+ * Unregister the observer that was registered with the provided key such that it will no longer be called
+ * if a transaction occurs that affects the attributes that the observer was registered to observe.
+ *
+ * The observer will need to re-register if it wants to start observing again.
+ * @param key String representing an identifier for the observer.
+ */
+ public void unregisterObserver(String key) {
+ JNA.INSTANCE.store_unregister_observer(rawPointer, key);
+ }
+
+ @Override
+ public void close() {
+ Log.i("Mentat", "close");
+ if (this.rawPointer != null) {
+ JNA.INSTANCE.store_destroy(this.rawPointer);
+ }
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Query.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Query.java
new file mode 100644
index 00000000..e565926f
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Query.java
@@ -0,0 +1,332 @@
+/* -*- 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 android.util.Log;
+
+import com.sun.jna.Memory;
+import com.sun.jna.Pointer;
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * 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:
+ *
+ *
+ * {@code
+ * String query = "[:find ?name ?cat\n" +
+ * " :in ?type\n" +
+ * " :where\n" +
+ * " [?c :community/name ?name]\n" +
+ * " [?c :community/type ?type]\n" +
+ * " [?c :community/category ?cat]]";
+ * mentat.query(query).bindKeywordReference("?type", ":community.type/website").run(new RelResultHandler() {
+ * @Override
+ * public void handleRows(RelResult rows) {
+ * ...
+ * }
+ * });
+ *}
+ *
+ * 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:
+ *
+ * {@code
+ * String query = "[: find ?a ?b ?c\n" +
+ * " : where ... ]";
+ * mentat.query(query).run(new RelResultHandler() {
+ * @Override
+ * public void handleRows(RelResult rows) {
+ * ...
+ * }
+ * });
+ *}
+ *
+ * - `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:
+ *
+ * {@code
+ * String query = "[: find ?a .\n" +
+ * " : where ... ]";
+ * mentat.query(query).runScalar(new ScalarResultHandler() {
+ * @Override
+ * public void handleValue(TypedValue value) {
+ * ...
+ * }
+ * });
+ *}
+ *
+ * - `Coll` - This returns a list of single values as a result. Queries that wish to have `Coll` results should format their query strings:
+ * {@code
+ * String query = "[: find [?a ...]\n" +
+ * " : where ... ]";
+ * mentat.query(query).runColl(new ScalarResultHandler() {
+ * @Override
+ * public void handleList(CollResult list) {
+ * ...
+ * }
+ * });
+ *}
+ *
+ * - `Tuple` - This returns a single row of values. Queries that wish to have `Tuple` results should format their query strings:
+ * {@code
+ * String query = "[: find [?a ?b ?c]\n" +
+ * " : where ... ]";
+ * mentat.query(query).runTuple(new TupleResultHandler() {
+ * @Override
+ * public void handleRow(TupleResult row) {
+ * ...
+ * }
+ * });
+ *}
+ */
+public class Query extends RustObject {
+
+ public Query(Pointer pointer) {
+ this.rawPointer = pointer;
+ }
+
+ /**
+ * Binds a long value to the provided variable name.
+ * TODO: Throw an exception if the query raw pointer has been consumed.
+ * @param varName The name of the variable in the format `?name`.
+ * @param value The value to be bound
+ * @return This {@link Query} such that further function can be called.
+ */
+ Query bindLong(String varName, long value) {
+ this.validate();
+ JNA.INSTANCE.query_builder_bind_long(this.rawPointer, varName, value);
+ return this;
+ }
+
+ /**
+ * Binds a Entid value to the provided variable name.
+ * TODO: Throw an exception if the query raw pointer has been consumed.
+ * @param varName The name of the variable in the format `?name`.
+ * @param value The value to be bound
+ * @return This {@link Query} such that further function can be called.
+ */
+ Query bindEntidReference(String varName, long value) {
+ this.validate();
+ JNA.INSTANCE.query_builder_bind_ref(this.rawPointer, varName, value);
+ return this;
+ }
+
+ /**
+ * Binds a String keyword value to the provided variable name.
+ * TODO: Throw an exception if the query raw pointer has been consumed.
+ * @param varName The name of the variable in the format `?name`.
+ * @param value The value to be bound
+ * @return This {@link Query} such that further function can be called.
+ */
+ Query bindKeywordReference(String varName, String value) {
+ this.validate();
+ JNA.INSTANCE.query_builder_bind_ref_kw(this.rawPointer, varName, value);
+ return this;
+ }
+
+ /**
+ * Binds a keyword value to the provided variable name.
+ * TODO: Throw an exception if the query raw pointer has been consumed.
+ * @param varName The name of the variable in the format `?name`.
+ * @param value The value to be bound
+ * @return This {@link Query} such that further function can be called.
+ */
+ Query bindKeyword(String varName, String value) {
+ this.validate();
+ JNA.INSTANCE.query_builder_bind_kw(this.rawPointer, varName, value);
+ return this;
+ }
+
+ /**
+ * Binds a boolean value to the provided variable name.
+ * TODO: Throw an exception if the query raw pointer has been consumed.
+ * @param varName The name of the variable in the format `?name`.
+ * @param value The value to be bound
+ * @return This {@link Query} such that further function can be called.
+ */
+ Query bindBoolean(String varName, boolean value) {
+ this.validate();
+ JNA.INSTANCE.query_builder_bind_boolean(this.rawPointer, varName, value ? 1 : 0);
+ return this;
+ }
+
+ /**
+ * Binds a double value to the provided variable name.
+ * TODO: Throw an exception if the query raw pointer has been consumed.
+ * @param varName The name of the variable in the format `?name`.
+ * @param value The value to be bound
+ * @return This {@link Query} such that further function can be called.
+ */
+ Query bindDouble(String varName, double value) {
+ this.validate();
+ JNA.INSTANCE.query_builder_bind_double(this.rawPointer, varName, value);
+ return this;
+ }
+
+ /**
+ * Binds a {@link Date} value to the provided variable name.
+ * TODO: Throw an exception if the query raw pointer has been consumed.
+ * @param varName The name of the variable in the format `?name`.
+ * @param value The value to be bound
+ * @return This {@link Query} such that further function can be called.
+ */
+ Query bindDate(String varName, Date value) {
+ this.validate();
+ long timestamp = value.getTime() * 1000;
+ JNA.INSTANCE.query_builder_bind_timestamp(this.rawPointer, varName, timestamp);
+ return this;
+ }
+
+ /**
+ * Binds a {@link String} value to the provided variable name.
+ * TODO: Throw an exception if the query raw pointer has been consumed.
+ * @param varName The name of the variable in the format `?name`.
+ * @param value The value to be bound
+ * @return This {@link Query} such that further function can be called.
+ */
+ Query bindString(String varName, String value) {
+ this.validate();
+ JNA.INSTANCE.query_builder_bind_string(this.rawPointer, varName, value);
+ return this;
+ }
+
+ /**
+ * Binds a {@link UUID} value to the provided variable name.
+ * TODO: Throw an exception if the query raw pointer has been consumed.
+ * @param varName The name of the variable in the format `?name`.
+ * @param value The value to be bound
+ * @return This {@link Query} such that further function can be called.
+ */
+ Query bindUUID(String varName, UUID value) {
+ this.validate();
+ ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
+ bb.putLong(value.getMostSignificantBits());
+ bb.putLong(value.getLeastSignificantBits());
+ byte[] bytes = bb.array();
+ final Pointer bytesNativeArray = new Memory(bytes.length);
+ bytesNativeArray.write(0, bytes, 0, bytes.length);
+ JNA.INSTANCE.query_builder_bind_uuid(this.rawPointer, varName, bytesNativeArray);
+ return this;
+ }
+
+ /**
+ * Execute the query with the values bound associated with this {@link Query} and call the provided
+ * callback function with the results as a list of rows of {@link TypedValue}s.
+ * TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
+ * @param handler the handler to call with the results of this query
+ */
+ void run(final RelResultHandler handler) {
+ this.validate();
+ RustResult result = JNA.INSTANCE.query_builder_execute(rawPointer);
+ rawPointer = null;
+
+ if (result.isFailure()) {
+ Log.e("Query", result.err);
+ return;
+ }
+ handler.handleRows(new RelResult(result.ok));
+ }
+
+ /**
+ * Execute the query with the values bound associated with this {@link Query} and call the provided
+ * callback function with the results with the result as a single {@link TypedValue}.
+ * TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
+ * @param handler the handler to call with the results of this query
+ */
+ void runScalar(final ScalarResultHandler handler) {
+ this.validate();
+ RustResult result = JNA.INSTANCE.query_builder_execute_scalar(rawPointer);
+ rawPointer = null;
+
+ if (result.isFailure()) {
+ Log.e("Query", result.err);
+ return;
+ }
+
+ if (result.isSuccess()) {
+ handler.handleValue(new TypedValue(result.ok));
+ } else {
+ handler.handleValue(null);
+ }
+ }
+
+ /**
+ * Execute the query with the values bound associated with this {@link Query} and call the provided
+ * callback function with the results with the result as a list of single {@link TypedValue}s.
+ * TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
+ * @param handler the handler to call with the results of this query
+ */
+ void runColl(final CollResultHandler handler) {
+ this.validate();
+ RustResult result = JNA.INSTANCE.query_builder_execute_coll(rawPointer);
+ rawPointer = null;
+
+ if (result.isFailure()) {
+ Log.e("Query", result.err);
+ return;
+ }
+ handler.handleList(new CollResult(result.ok));
+ }
+
+ /**
+ * Execute the query with the values bound associated with this {@link Query} and call the provided
+ * callback function with the results with the result as a list of single {@link TypedValue}s.
+ * TODO: Throw an exception if the query raw pointer has been consumed or the query fails to execute
+ * @param handler the handler to call with the results of this query
+ */
+ void runTuple(final TupleResultHandler handler) {
+ this.validate();
+ RustResult result = JNA.INSTANCE.query_builder_execute_tuple(rawPointer);
+ rawPointer = null;
+
+ if (result.isFailure()) {
+ Log.e("Query", result.err);
+ return;
+ }
+
+ if (result.isSuccess()) {
+ handler.handleRow(new TupleResult(result.ok));
+ } else {
+ handler.handleRow(null);
+ }
+ }
+
+ @Override
+ public void close() {
+ Log.i("Query", "close");
+
+ if (this.rawPointer == null) {
+ return;
+ }
+ JNA.INSTANCE.query_builder_destroy(this.rawPointer);
+ }
+}
diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResult.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResult.java
new file mode 100644
index 00000000..9b125677
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResult.java
@@ -0,0 +1,90 @@
+/* -*- 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 android.util.Log;
+
+import com.sun.jna.Pointer;
+
+/**
+ * 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.
+ *
{@code + * mentat.query(query).run(new RelResultHandler() { + * @Override + * public void handleRows(RelResult rows) { + * TupleResult row1 = rows.rowAtIndex(0); + * TupleResult row2 = rows.rowAtIndex(1); + * ... + * } + * }); + *}+ * + * To iterate over the result set use standard iteration flows. + *
{@code + * mentat.query(query).run(new RelResultHandler() { + * @Override + * public void handleRows(RelResult rows) { + * for (TupleResult row: rows) { + * ... + * } + * } + * }); + *}+ * + * Note that iteration is consuming and can only be done once. + */ +public class RelResult extends RustObject implements Iterable
+ * Field values can be fetched as one of the following types: + *
+ * 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_as_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_as_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_as_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_as_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_as_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_as_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_as_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_as_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() {
+ Log.i("TupleResult", "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..35b46b14
--- /dev/null
+++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChange.java
@@ -0,0 +1,67 @@
+/* -*- 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 android.util.Log;
+
+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
{@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() { + Log.i("TxReport", "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..06ba1679 --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TypedValue.java @@ -0,0 +1,161 @@ +/* -*- 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 android.util.Log; +import com.sun.jna.Pointer; + +import java.io.BufferedReader; +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_as_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_as_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_as_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_as_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_as_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_as_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_as_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_as_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() { + Log.i("TypedValue", "close"); + + if (this.rawPointer != null) { + JNA.INSTANCE.typed_value_destroy(this.rawPointer); + } + } +}