diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 1795db5d..cbe00a75 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1030,11 +1030,6 @@ pub unsafe extern "C" fn tx_report_entity_for_temp_id(tx_report: *mut TxReport, /// Adds an attribute to the cache. /// `store_cache_attribute_forward` caches values for an attribute keyed by entity /// (i.e. find values and entities that have this attribute, or find values of attribute for an entity) -/// -/// # Safety -/// -/// Callers must ensure that the pointer to the `Store` is not dangling and that -/// the C string provided to `attribute` is valid. #[no_mangle] pub extern "C" fn store_cache_attribute_forward(store: *mut Store, attribute: *const c_char) -> *mut ExternResult { let store = unsafe { &mut *store }; @@ -1045,11 +1040,6 @@ pub extern "C" fn store_cache_attribute_forward(store: *mut Store, attribute: *c /// Adds an attribute to the cache. /// `store_cache_attribute_reverse` caches entities for an attribute keyed by value. /// (i.e. find entities that have a particular value for an attribute). -/// -/// # Safety -/// -/// Callers must ensure that the pointer to the `Store` is not dangling and that -/// the C string provided to `attribute` is valid. #[no_mangle] pub extern "C" fn store_cache_attribute_reverse(store: *mut Store, attribute: *const c_char) -> *mut ExternResult { let store = unsafe { &mut *store }; @@ -1065,11 +1055,6 @@ pub extern "C" fn store_cache_attribute_reverse(store: *mut Store, attribute: *c /// /// `Reverse` caches entities for an attribute keyed by value. /// (i.e. find entities that have a particular value for an attribute). -/// -/// # Safety -/// -/// Callers must ensure that the pointer to the `Store` is not dangling and that -/// the C string provided to `attribute` is valid. #[no_mangle] pub extern "C" fn store_cache_attribute_bi_directional(store: *mut Store, attribute: *const c_char) -> *mut ExternResult { let store = unsafe { &mut *store }; diff --git a/sdks/android/Mentat/library/src/androidTest/java/com/mozilla/mentat/FFIIntegrationTest.java b/sdks/android/Mentat/library/src/androidTest/java/com/mozilla/mentat/FFIIntegrationTest.java index 7561455a..cd86b027 100644 --- a/sdks/android/Mentat/library/src/androidTest/java/com/mozilla/mentat/FFIIntegrationTest.java +++ b/sdks/android/Mentat/library/src/androidTest/java/com/mozilla/mentat/FFIIntegrationTest.java @@ -14,6 +14,7 @@ import android.content.Context; import android.content.res.AssetManager; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import android.util.Log; import org.junit.Test; import org.junit.runner.RunWith; @@ -1149,6 +1150,7 @@ public class FFIIntegrationTest { synchronized (expectation1) { expectation1.wait(1000); } + assertTrue(expectation1.isFulfilled); EntityBuilder builder = mentat.entityBuilder(bEntid); builder.retract(":foo/boolean", false); @@ -1173,6 +1175,7 @@ public class FFIIntegrationTest { synchronized (expectation2) { expectation2.wait(1000); } + assertTrue(expectation2.isFulfilled); } @Test @@ -1217,6 +1220,7 @@ public class FFIIntegrationTest { synchronized (expectation1) { expectation1.wait(1000); } + assertTrue(expectation1.isFulfilled); InProgressBuilder builder = mentat.entityBuilder(); builder.retract(bEntid, ":foo/boolean", false); @@ -1241,5 +1245,77 @@ public class FFIIntegrationTest { synchronized (expectation2) { expectation2.wait(1000); } + assertTrue(expectation2.isFulfilled); + } + + @Test + public void testCaching() throws InterruptedException { + String query = "[:find ?district :where\n" + + " [?neighborhood :neighborhood/name \"Beacon Hill\"]\n" + + " [?neighborhood :neighborhood/district ?d]\n" + + " [?d :district/name ?district]]"; + + Mentat mentat = openAndInitializeCitiesStore(); + + final Expectation expectation1 = new Expectation(); + final QueryTimer uncachedTimer = new QueryTimer(); + uncachedTimer.start(); + mentat.query(query).run(new RelResultHandler() { + @Override + public void handleRows(RelResult rows) { + uncachedTimer.end(); + assertNotNull(rows); + expectation1.fulfill(); + } + }); + + synchronized (expectation1) { + expectation1.wait(1000); + } + assertTrue(expectation1.isFulfilled); + + mentat.cache(":neighborhood/name", CacheDirection.REVERSE); + mentat.cache(":neighborhood/district", CacheDirection.FORWARD); + + final Expectation expectation2 = new Expectation(); + final QueryTimer cachedTimer = new QueryTimer(); + cachedTimer.start(); + mentat.query(query).run(new RelResultHandler() { + @Override + public void handleRows(RelResult rows) { + cachedTimer.end(); + assertNotNull(rows); + expectation2.fulfill(); + } + }); + + synchronized (expectation2) { + expectation2.wait(1000); + } + assertTrue(expectation2.isFulfilled); + + long timingDifference = uncachedTimer.duration() - cachedTimer.duration(); + Log.d("testCaching", "Cached query is "+ timingDifference +" nanoseconds faster than the uncached query"); + + assert cachedTimer.duration() < uncachedTimer.duration(); + + } + +} + +class QueryTimer { + private long startTime = 0; + private long endTime = 0; + + public void start() { + this.startTime = System.nanoTime(); + } + + public void end() { + this.endTime = System.nanoTime(); + } + + public long duration() { + return this.endTime - this.startTime; } } diff --git a/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CacheDirection.java b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CacheDirection.java new file mode 100644 index 00000000..56f1ff9e --- /dev/null +++ b/sdks/android/Mentat/library/src/main/java/com/mozilla/mentat/CacheDirection.java @@ -0,0 +1,15 @@ +/* -*- 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; + +public enum CacheDirection { + FORWARD, REVERSE, BOTH +} 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 index 4b14935e..2b6731bf 100644 --- 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 @@ -41,6 +41,11 @@ public interface JNA extends Library { void in_progress_builder_destroy(Pointer obj); void entity_builder_destroy(Pointer obj); + // caching + RustResult store_cache_attribute_forward(Pointer store, String attribute); + RustResult store_cache_attribute_reverse(Pointer store, String attribute); + RustResult store_cache_attribute_bi_directional(Pointer store, String attribute); + // transact RustResult store_transact(Pointer store, String transaction); Pointer tx_report_entity_for_temp_id(Pointer report, String tempid); 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 index c885a728..95dd0076 100644 --- 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 @@ -48,6 +48,35 @@ public class Mentat extends RustObject { */ public Mentat(Pointer rawPointer) { this.rawPointer = rawPointer; } + /** + * Add an attribute to the cache. The {@link CacheDirection} determines how that attribute can be + * looked up. + * + * TODO: Throw an exception if cache action fails + * + * @param attribute The attribute to cache + * @param direction The direction the attribute should be keyed. + * FORWARD caches values for an attribute keyed by entity + * (i.e. find values and entities that have this attribute, or find values of attribute for an entity) + * REVERSE caches entities for an attribute keyed by value. + * (i.e. find entities that have a particular value for an attribute). + * BOTH adds an attribute such that it is cached in both directions. + */ + public void cache(String attribute, CacheDirection direction) { + RustResult result = null; + switch (direction) { + case FORWARD: + result = JNA.INSTANCE.store_cache_attribute_forward(this.rawPointer, attribute); + case REVERSE: + result = JNA.INSTANCE.store_cache_attribute_reverse(this.rawPointer, attribute); + case BOTH: + result = JNA.INSTANCE.store_cache_attribute_bi_directional(this.rawPointer, attribute); + } + if (result.isFailure()) { + Log.e("Mentat", result.err); + } + } + /** * Simple transact of an EDN string. * TODO: Throw an exception if the transact fails @@ -88,7 +117,7 @@ public class Mentat extends RustObject { /** * Retrieve a single value of an attribute for an Entity - * TODO: Throw an exception if there is no the result contains an error. + * TODO: Throw an exception if 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. @@ -139,6 +168,13 @@ public class Mentat extends RustObject { } + /** + * Start a new transaction + * + * TODO: Throw an exception if the result contains an error. + * + * @return The {@link InProgress} used to manage the transaction + */ public InProgress beginTransaction() { RustResult result = JNA.INSTANCE.store_begin_transaction(this.rawPointer); if (result.isSuccess()) { @@ -152,6 +188,14 @@ public class Mentat extends RustObject { return null; } + /** + * Creates a new transaction ({@link InProgress}) and returns an {@link InProgressBuilder} for + * that transaction. + * + * TODO: Throw an exception if the result contains an error. + * + * @return an {@link InProgressBuilder} for a new transaction. + */ public InProgressBuilder entityBuilder() { RustResult result = JNA.INSTANCE.store_in_progress_builder(this.rawPointer); if (result.isSuccess()) { @@ -165,6 +209,15 @@ public class Mentat extends RustObject { return null; } + /** + * Creates a new transaction ({@link InProgress}) and returns an {@link EntityBuilder} for the + * entity with `entid` for that transaction. + * + * TODO: Throw an exception if the result contains an error. + * + * @param entid The `Entid` for this entity. + * @return an {@link EntityBuilder} for a new transaction. + */ public EntityBuilder entityBuilder(long entid) { RustResult result = JNA.INSTANCE.store_entity_builder_from_entid(this.rawPointer, entid); if (result.isSuccess()) { @@ -178,6 +231,15 @@ public class Mentat extends RustObject { return null; } + /** + * Creates a new transaction ({@link InProgress}) and returns an {@link EntityBuilder} for a new + * entity with `tempId` for that transaction. + * + * TODO: Throw an exception if the result contains an error. + * + * @param tempId The temporary identifier for this entity. + * @return an {@link EntityBuilder} for a new transaction. + */ public EntityBuilder entityBuilder(String tempId) { RustResult result = JNA.INSTANCE.store_entity_builder_from_temp_id(this.rawPointer, tempId); if (result.isSuccess()) { diff --git a/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so b/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so index 6edd9a18..fbeae458 100755 Binary files a/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so and b/sdks/android/Mentat/library/src/main/jniLibs/x86/libmentat_ffi.so differ