Summary: This is an abstraction for working with custom Comparators implemented in native C++ code from Java. Native code must directly extend `rocksdb::Comparator`. When the native code comparator is compiled into the RocksDB codebase, you can then create a Java Class, and JNI stub to wrap it. Useful if the C++/JNI barrier overhead is too much for your applications comparator performance. An example is provided in `java/rocksjni/native_comparator_wrapper_test.cc` and `java/src/main/java/org/rocksdb/NativeComparatorWrapperTest.java`. Closes https://github.com/facebook/rocksdb/pull/3334 Differential Revision: D7172605 Pulled By: miasantreble fbshipit-source-id: e24b7eb267a3bcb6afa214e0379a1d5e8a2ceabemain
parent
e476d0e252
commit
c5302a8a58
@ -0,0 +1,50 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
|
||||||
|
#include <jni.h> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "rocksdb/comparator.h" |
||||||
|
#include "rocksdb/slice.h" |
||||||
|
|
||||||
|
#include "include/org_rocksdb_NativeComparatorWrapperTest_NativeStringComparatorWrapper.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class NativeComparatorWrapperTestStringComparator |
||||||
|
: public Comparator { |
||||||
|
|
||||||
|
const char* Name() const { |
||||||
|
return "NativeComparatorWrapperTestStringComparator"; |
||||||
|
} |
||||||
|
|
||||||
|
int Compare( |
||||||
|
const Slice& a, const Slice& b) const { |
||||||
|
return a.ToString().compare(b.ToString()); |
||||||
|
} |
||||||
|
|
||||||
|
void FindShortestSeparator( |
||||||
|
std::string* start, const Slice& limit) const { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
void FindShortSuccessor( |
||||||
|
std::string* key) const { |
||||||
|
return; |
||||||
|
} |
||||||
|
}; |
||||||
|
} // end of rocksdb namespace
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: org_rocksdb_NativeComparatorWrapperTest_NativeStringComparatorWrapper |
||||||
|
* Method: newStringComparator |
||||||
|
* Signature: ()J |
||||||
|
*/ |
||||||
|
jlong Java_org_rocksdb_NativeComparatorWrapperTest_00024NativeStringComparatorWrapper_newStringComparator( |
||||||
|
JNIEnv* env , jobject jobj) { |
||||||
|
auto* comparator = |
||||||
|
new rocksdb::NativeComparatorWrapperTestStringComparator(); |
||||||
|
return reinterpret_cast<jlong>(comparator); |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
|
||||||
|
package org.rocksdb; |
||||||
|
|
||||||
|
enum ComparatorType { |
||||||
|
JAVA_COMPARATOR((byte)0x0), |
||||||
|
JAVA_DIRECT_COMPARATOR((byte)0x1), |
||||||
|
JAVA_NATIVE_COMPARATOR_WRAPPER((byte)0x2); |
||||||
|
|
||||||
|
private final byte value; |
||||||
|
|
||||||
|
ComparatorType(final byte value) { |
||||||
|
this.value = value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* <p>Returns the byte value of the enumerations value.</p> |
||||||
|
* |
||||||
|
* @return byte representation |
||||||
|
*/ |
||||||
|
byte getValue() { |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* <p>Get the ComparatorType enumeration value by |
||||||
|
* passing the byte identifier to this method.</p> |
||||||
|
* |
||||||
|
* @param byteIdentifier of ComparatorType. |
||||||
|
* |
||||||
|
* @return ComparatorType instance. |
||||||
|
* |
||||||
|
* @throws IllegalArgumentException if the comparator type for the byteIdentifier |
||||||
|
* cannot be found |
||||||
|
*/ |
||||||
|
static ComparatorType getComparatorType(final byte byteIdentifier) { |
||||||
|
for (final ComparatorType comparatorType : ComparatorType.values()) { |
||||||
|
if (comparatorType.getValue() == byteIdentifier) { |
||||||
|
return comparatorType; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
throw new IllegalArgumentException( |
||||||
|
"Illegal value provided for ComparatorType."); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
|
||||||
|
package org.rocksdb; |
||||||
|
|
||||||
|
/** |
||||||
|
* A simple abstraction to allow a Java class to wrap a custom comparator |
||||||
|
* implemented in C++. |
||||||
|
* |
||||||
|
* The native comparator must directly extend rocksdb::Comparator. |
||||||
|
*/ |
||||||
|
public abstract class NativeComparatorWrapper |
||||||
|
extends AbstractComparator<Slice> { |
||||||
|
|
||||||
|
@Override |
||||||
|
final ComparatorType getComparatorType() { |
||||||
|
return ComparatorType.JAVA_NATIVE_COMPARATOR_WRAPPER; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public final String name() { |
||||||
|
throw new IllegalStateException("This should not be called. " + |
||||||
|
"Implementation is in Native code"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public final int compare(final Slice s1, final Slice s2) { |
||||||
|
throw new IllegalStateException("This should not be called. " + |
||||||
|
"Implementation is in Native code"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public final String findShortestSeparator(final String start, final Slice limit) { |
||||||
|
throw new IllegalStateException("This should not be called. " + |
||||||
|
"Implementation is in Native code"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public final String findShortSuccessor(final String key) { |
||||||
|
throw new IllegalStateException("This should not be called. " + |
||||||
|
"Implementation is in Native code"); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* We override {@link RocksCallbackObject#disposeInternal()} |
||||||
|
* as disposing of a native rocksd::Comparator extension requires |
||||||
|
* a slightly different approach as it is not really a RocksCallbackObject |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected void disposeInternal() { |
||||||
|
disposeInternal(nativeHandle_); |
||||||
|
} |
||||||
|
|
||||||
|
private native void disposeInternal(final long handle); |
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under both the GPLv2 (found in the
|
||||||
|
// COPYING file in the root directory) and Apache 2.0 License
|
||||||
|
// (found in the LICENSE.Apache file in the root directory).
|
||||||
|
|
||||||
|
package org.rocksdb; |
||||||
|
|
||||||
|
import org.junit.Rule; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.rules.TemporaryFolder; |
||||||
|
|
||||||
|
import java.util.*; |
||||||
|
import java.util.Comparator; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
|
||||||
|
public class NativeComparatorWrapperTest { |
||||||
|
|
||||||
|
@Rule |
||||||
|
public TemporaryFolder dbFolder = new TemporaryFolder(); |
||||||
|
|
||||||
|
private static final Random random = new Random(); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rountrip() throws RocksDBException { |
||||||
|
final String dbPath = dbFolder.getRoot().getAbsolutePath(); |
||||||
|
final int ITERATIONS = 1_000; |
||||||
|
|
||||||
|
final String[] storedKeys = new String[ITERATIONS]; |
||||||
|
try (final NativeStringComparatorWrapper comparator = new NativeStringComparatorWrapper(); |
||||||
|
final Options opt = new Options() |
||||||
|
.setCreateIfMissing(true) |
||||||
|
.setComparator(comparator)) { |
||||||
|
|
||||||
|
// store random integer keys
|
||||||
|
try (final RocksDB db = RocksDB.open(opt, dbPath)) { |
||||||
|
for (int i = 0; i < ITERATIONS; i++) { |
||||||
|
final String strKey = randomString(); |
||||||
|
final byte key[] = strKey.getBytes(); |
||||||
|
// does key already exist (avoid duplicates)
|
||||||
|
if (i > 0 && db.get(key) != null) { |
||||||
|
i--; // generate a different key
|
||||||
|
} else { |
||||||
|
db.put(key, "value".getBytes()); |
||||||
|
storedKeys[i] = strKey; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// sort the stored keys into ascending alpha-numeric order
|
||||||
|
Arrays.sort(storedKeys, new Comparator<String>() { |
||||||
|
@Override |
||||||
|
public int compare(final String o1, final String o2) { |
||||||
|
return o1.compareTo(o2); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// re-open db and read from start to end
|
||||||
|
// string keys should be in ascending
|
||||||
|
// order
|
||||||
|
try (final RocksDB db = RocksDB.open(opt, dbPath); |
||||||
|
final RocksIterator it = db.newIterator()) { |
||||||
|
int count = 0; |
||||||
|
for (it.seekToFirst(); it.isValid(); it.next()) { |
||||||
|
final String strKey = new String(it.key()); |
||||||
|
assertEquals(storedKeys[count++], strKey); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private String randomString() { |
||||||
|
final char[] chars = new char[12]; |
||||||
|
for(int i = 0; i < 12; i++) { |
||||||
|
final int letterCode = random.nextInt(24); |
||||||
|
final char letter = (char) (((int) 'a') + letterCode); |
||||||
|
chars[i] = letter; |
||||||
|
} |
||||||
|
return String.copyValueOf(chars); |
||||||
|
} |
||||||
|
|
||||||
|
public static class NativeStringComparatorWrapper |
||||||
|
extends NativeComparatorWrapper { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected long initializeNative(final long... nativeParameterHandles) { |
||||||
|
return newStringComparator(); |
||||||
|
} |
||||||
|
|
||||||
|
private native long newStringComparator(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue