Wrap caching FFI functions in Android Java library.
`CacheDirection` enum is used only on the Android side to provide a usable interface. FFI calls are more explicit. Tests ensure that a cached query is faster than the uncached one.
This commit is contained in:
parent
8add073001
commit
35467c1b24
6 changed files with 159 additions and 16 deletions
|
@ -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.
|
/// Adds an attribute to the cache.
|
||||||
/// `store_cache_attribute_forward` caches values for an attribute keyed by entity
|
/// `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)
|
/// (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]
|
#[no_mangle]
|
||||||
pub extern "C" fn store_cache_attribute_forward(store: *mut Store, attribute: *const c_char) -> *mut ExternResult {
|
pub extern "C" fn store_cache_attribute_forward(store: *mut Store, attribute: *const c_char) -> *mut ExternResult {
|
||||||
let store = unsafe { &mut *store };
|
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.
|
/// Adds an attribute to the cache.
|
||||||
/// `store_cache_attribute_reverse` caches entities for an attribute keyed by value.
|
/// `store_cache_attribute_reverse` caches entities for an attribute keyed by value.
|
||||||
/// (i.e. find entities that have a particular value for an attribute).
|
/// (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]
|
#[no_mangle]
|
||||||
pub extern "C" fn store_cache_attribute_reverse(store: *mut Store, attribute: *const c_char) -> *mut ExternResult {
|
pub extern "C" fn store_cache_attribute_reverse(store: *mut Store, attribute: *const c_char) -> *mut ExternResult {
|
||||||
let store = unsafe { &mut *store };
|
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.
|
/// `Reverse` caches entities for an attribute keyed by value.
|
||||||
/// (i.e. find entities that have a particular value for an attribute).
|
/// (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]
|
#[no_mangle]
|
||||||
pub extern "C" fn store_cache_attribute_bi_directional(store: *mut Store, attribute: *const c_char) -> *mut ExternResult {
|
pub extern "C" fn store_cache_attribute_bi_directional(store: *mut Store, attribute: *const c_char) -> *mut ExternResult {
|
||||||
let store = unsafe { &mut *store };
|
let store = unsafe { &mut *store };
|
||||||
|
|
|
@ -14,6 +14,7 @@ import android.content.Context;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -1149,6 +1150,7 @@ public class FFIIntegrationTest {
|
||||||
synchronized (expectation1) {
|
synchronized (expectation1) {
|
||||||
expectation1.wait(1000);
|
expectation1.wait(1000);
|
||||||
}
|
}
|
||||||
|
assertTrue(expectation1.isFulfilled);
|
||||||
|
|
||||||
EntityBuilder builder = mentat.entityBuilder(bEntid);
|
EntityBuilder builder = mentat.entityBuilder(bEntid);
|
||||||
builder.retract(":foo/boolean", false);
|
builder.retract(":foo/boolean", false);
|
||||||
|
@ -1173,6 +1175,7 @@ public class FFIIntegrationTest {
|
||||||
synchronized (expectation2) {
|
synchronized (expectation2) {
|
||||||
expectation2.wait(1000);
|
expectation2.wait(1000);
|
||||||
}
|
}
|
||||||
|
assertTrue(expectation2.isFulfilled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1217,6 +1220,7 @@ public class FFIIntegrationTest {
|
||||||
synchronized (expectation1) {
|
synchronized (expectation1) {
|
||||||
expectation1.wait(1000);
|
expectation1.wait(1000);
|
||||||
}
|
}
|
||||||
|
assertTrue(expectation1.isFulfilled);
|
||||||
|
|
||||||
InProgressBuilder builder = mentat.entityBuilder();
|
InProgressBuilder builder = mentat.entityBuilder();
|
||||||
builder.retract(bEntid, ":foo/boolean", false);
|
builder.retract(bEntid, ":foo/boolean", false);
|
||||||
|
@ -1241,5 +1245,77 @@ public class FFIIntegrationTest {
|
||||||
synchronized (expectation2) {
|
synchronized (expectation2) {
|
||||||
expectation2.wait(1000);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -41,6 +41,11 @@ public interface JNA extends Library {
|
||||||
void in_progress_builder_destroy(Pointer obj);
|
void in_progress_builder_destroy(Pointer obj);
|
||||||
void entity_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
|
// transact
|
||||||
RustResult store_transact(Pointer store, String transaction);
|
RustResult store_transact(Pointer store, String transaction);
|
||||||
Pointer tx_report_entity_for_temp_id(Pointer report, String tempid);
|
Pointer tx_report_entity_for_temp_id(Pointer report, String tempid);
|
||||||
|
|
|
@ -48,6 +48,35 @@ public class Mentat extends RustObject {
|
||||||
*/
|
*/
|
||||||
public Mentat(Pointer rawPointer) { this.rawPointer = rawPointer; }
|
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.
|
* Simple transact of an EDN string.
|
||||||
* TODO: Throw an exception if the transact fails
|
* 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
|
* 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 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.
|
* @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.
|
* @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() {
|
public InProgress beginTransaction() {
|
||||||
RustResult result = JNA.INSTANCE.store_begin_transaction(this.rawPointer);
|
RustResult result = JNA.INSTANCE.store_begin_transaction(this.rawPointer);
|
||||||
if (result.isSuccess()) {
|
if (result.isSuccess()) {
|
||||||
|
@ -152,6 +188,14 @@ public class Mentat extends RustObject {
|
||||||
return null;
|
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() {
|
public InProgressBuilder entityBuilder() {
|
||||||
RustResult result = JNA.INSTANCE.store_in_progress_builder(this.rawPointer);
|
RustResult result = JNA.INSTANCE.store_in_progress_builder(this.rawPointer);
|
||||||
if (result.isSuccess()) {
|
if (result.isSuccess()) {
|
||||||
|
@ -165,6 +209,15 @@ public class Mentat extends RustObject {
|
||||||
return null;
|
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) {
|
public EntityBuilder entityBuilder(long entid) {
|
||||||
RustResult result = JNA.INSTANCE.store_entity_builder_from_entid(this.rawPointer, entid);
|
RustResult result = JNA.INSTANCE.store_entity_builder_from_entid(this.rawPointer, entid);
|
||||||
if (result.isSuccess()) {
|
if (result.isSuccess()) {
|
||||||
|
@ -178,6 +231,15 @@ public class Mentat extends RustObject {
|
||||||
return null;
|
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) {
|
public EntityBuilder entityBuilder(String tempId) {
|
||||||
RustResult result = JNA.INSTANCE.store_entity_builder_from_temp_id(this.rawPointer, tempId);
|
RustResult result = JNA.INSTANCE.store_entity_builder_from_temp_id(this.rawPointer, tempId);
|
||||||
if (result.isSuccess()) {
|
if (result.isSuccess()) {
|
||||||
|
|
Binary file not shown.
Loading…
Reference in a new issue