diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/AttributeList.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/AttributeList.java new file mode 100644 index 00000000..24eebae3 --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/AttributeList.java @@ -0,0 +1,51 @@ +/* -*- 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.Structure; +import com.sun.jna.ptr.IntByReference; + +import java.io.Closeable; +import java.util.Arrays; +import java.util.List; + +/** + * Represents a C struct of a list of Strings containing attributes in the format + * `:namespace/name`. + */ +public class AttributeList extends Structure implements Closeable { + public static class ByReference extends AttributeList implements Structure.ByReference { + } + + public static class ByValue extends AttributeList implements Structure.ByValue { + } + + public IntByReference attributes; + 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; + + @Override + protected List getFieldOrder() { + return Arrays.asList("attributes", "numberOfItems", "len"); + } + + @Override + public void close() { + Log.i("AttributeList", "close"); + + if (this.getPointer() != null) { + JNA.INSTANCE.destroy(this.getPointer()); + } + } +} diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ColResultIterator.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ColResultIterator.java new file mode 100644 index 00000000..8fba4d7a --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/ColResultIterator.java @@ -0,0 +1,58 @@ +/* -*- 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.IOException; +import java.util.Iterator; + +/** + * Iterator for a {@link CollResult} + */ +public class ColResultIterator extends RustObject implements Iterator { + + Pointer nextPointer; + + ColResultIterator(Pointer iterator) { + this.rawPointer = iterator; + } + + private Pointer getNextPointer() { + return JNA.INSTANCE.values_iter_next(this.rawPointer); + } + + @Override + public boolean hasNext() { + this.nextPointer = getNextPointer(); + return this.nextPointer != null; + } + + @Override + public TypedValue next() { + Pointer next = this.nextPointer == null ? getNextPointer() : this.nextPointer; + if (next == null) { + return null; + } + + return new TypedValue(next); + } + + @Override + public void close() { + Log.i("TupleResult", "close"); + if (this.rawPointer != null) { + JNA.INSTANCE.typed_value_list_iter_destroy(this.rawPointer); + } + } +} diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CollResult.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CollResult.java new file mode 100644 index 00000000..5cd3af9b --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CollResult.java @@ -0,0 +1,63 @@ +/* -*- 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.util.Date; +import java.util.UUID; + +/** + * Wraps a `Coll` result from a Mentat query. + * A `Coll` result is a list of rows of single values of type {@link TypedValue}. + * Values for individual rows can be fetched as {@link TypedValue} or converted into a requested type. + *

+ * 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 { + + public CollResult(Pointer pointer) { + super(pointer); + } + + @Override + public void close() { + Log.i("CollResult", "close"); + + if (this.rawPointer != null) { + JNA.INSTANCE.destroy(this.rawPointer); + } + } + + @Override + public ColResultIterator iterator() { + Pointer iterPointer = JNA.INSTANCE.values_iter(this.rawPointer); + this.rawPointer = null; + if (iterPointer == null) { + return null; + } + return new ColResultIterator(iterPointer); + } +} diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CollResultHandler.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CollResultHandler.java new file mode 100644 index 00000000..9f6842e9 --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CollResultHandler.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 CollResult}. + */ +public interface CollResultHandler { + void handleList(CollResult list); +} diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/JNA.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/JNA.java new file mode 100644 index 00000000..31059976 --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/JNA.java @@ -0,0 +1,101 @@ +/* -*- 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.Library; +import com.sun.jna.Native; +import com.sun.jna.NativeLibrary; +import com.sun.jna.Pointer; + +/** + * JNA interface for FFI to Mentat's Rust library + * Each function definition here link directly to a function in Mentat's FFI crate. + * Signatures must match for the linking to work correctly. + */ +public interface JNA extends Library { + String JNA_LIBRARY_NAME = "mentat_ffi"; + NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(JNA_LIBRARY_NAME); + + JNA INSTANCE = (JNA) Native.loadLibrary(JNA_LIBRARY_NAME, JNA.class); + + Pointer store_open(String dbPath); + + void destroy(Pointer obj); + void query_builder_destroy(Pointer obj); + void store_destroy(Pointer obj); + void typed_value_destroy(Pointer obj); + void typed_value_list_destroy(Pointer obj); + void typed_value_list_iter_destroy(Pointer obj); + void typed_value_result_set_destroy(Pointer obj); + void typed_value_result_set_iter_destroy(Pointer obj); + void tx_report_destroy(Pointer obj); + + // transact + RustResult store_transact(Pointer store, String transaction); + Pointer tx_report_entity_for_temp_id(Pointer report, String tempid); + long tx_report_get_entid(Pointer report); + long tx_report_get_tx_instant(Pointer report); + + // sync + RustResult store_sync(Pointer store, String userUuid, String serverUri); + + // observers + void store_register_observer(Pointer store, String key, Pointer attributes, int len, TxObserverCallback callback); + void store_unregister_observer(Pointer store, String key); + long store_entid_for_attribute(Pointer store, String attr); + + // Query Building + Pointer store_query(Pointer store, String query); + RustResult store_value_for_attribute(Pointer store, long entid, String attribute); + void query_builder_bind_long(Pointer query, String var, long value); + void query_builder_bind_ref(Pointer query, String var, long value); + void query_builder_bind_ref_kw(Pointer query, String var, String value); + void query_builder_bind_kw(Pointer query, String var, String value); + void query_builder_bind_boolean(Pointer query, String var, int value); + void query_builder_bind_double(Pointer query, String var, double value); + void query_builder_bind_timestamp(Pointer query, String var, long value); + void query_builder_bind_string(Pointer query, String var, String value); + void query_builder_bind_uuid(Pointer query, String var, Pointer value); + + // Query Execution + RustResult query_builder_execute(Pointer query); + RustResult query_builder_execute_scalar(Pointer query); + RustResult query_builder_execute_coll(Pointer query); + RustResult query_builder_execute_tuple(Pointer query); + + // Query Result Processing + long typed_value_as_long(Pointer value); + long typed_value_as_entid(Pointer value); + String typed_value_as_kw(Pointer value); + String typed_value_as_string(Pointer value); + Pointer typed_value_as_uuid(Pointer value); + int typed_value_as_boolean(Pointer value); + double typed_value_as_double(Pointer value); + long typed_value_as_timestamp(Pointer value); + Pointer typed_value_value_type(Pointer value); + + Pointer row_at_index(Pointer rows, int index); + Pointer rows_iter(Pointer rows); + Pointer rows_iter_next(Pointer iter); + + Pointer values_iter(Pointer rows); + Pointer values_iter_next(Pointer iter); + + Pointer value_at_index(Pointer rows, int index); + long value_at_index_as_long(Pointer rows, int index); + long value_at_index_as_entid(Pointer rows, int index); + String value_at_index_as_kw(Pointer rows, int index); + String value_at_index_as_string(Pointer rows, int index); + Pointer value_at_index_as_uuid(Pointer rows, int index); + long value_at_index_as_boolean(Pointer rows, int index); + double value_at_index_as_double(Pointer rows, int index); + long value_at_index_as_timestamp(Pointer rows, int index); +} diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Mentat.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Mentat.java new file mode 100644 index 00000000..038be11c --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/Mentat.java @@ -0,0 +1,152 @@ +/* -*- 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; + +/** + * 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. + */ +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: + *

+ *

+ *

+ * Each bound variable must have a corresponding value in the query string used to create this query. + *

+ *

{@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. + *

+ * To fetch individual rows from a `RelResult` use `row(Int32)`. + *

+ *
{@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 { + + 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.rows_iter(this.rawPointer); + this.rawPointer = null; + if (iterPointer == null) { + return null; + } + return new RelResultIterator(iterPointer); + } + + @Override + public void close() { + Log.i("RelResult", "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..10e62c7c --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/RelResultIterator.java @@ -0,0 +1,57 @@ +/* -*- 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.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.rows_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() { + Log.i("TupleResult", "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..15adc918 --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TupleResult.java @@ -0,0 +1,170 @@ +/* -*- 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.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_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 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() { + Log.i("TxChange", "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..abb98e30 --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxChangeList.java @@ -0,0 +1,59 @@ +/* -*- 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.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() { + Log.i("TxChangeList", "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..ff6a8646 --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/TxReport.java @@ -0,0 +1,92 @@ +/* -*- 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.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() { + 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); + } + } +}