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
312d3bf4e0
commit
133cad35b6
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.
|
||||
/// `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 };
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Binary file not shown.
Loading…
Reference in a new issue