JNI support for ReadOptions::iterate_upper_bound

Summary:
Plumbed ReadOptions::iterate_upper_bound through JNI.

Made the following design choices:
* Used Slice instead of AbstractSlice due to the anticipated usecase (key / key prefix). Can change this if anyone disagrees.
* Used Slice instead of raw byte[] which seemed cleaner but necessitated the package-private handle-based Slice constructor. Followed WriteBatch as an example.
* We need a copy constructor for ReadOptions, as we create one base ReadOptions for a particular usecase and clone -> change the iterate_upper_bound on each slice operation. Shallow copy seemed cleanest.
* Hold a reference to the upper bound slice on ReadOptions, in contrast to Snapshot.

Signed a Facebook CLA this morning.
Closes https://github.com/facebook/rocksdb/pull/2872

Differential Revision: D5824446

Pulled By: sagar0

fbshipit-source-id: 74fc51313a10a81ecd348625e2a50ca5b7766888
main
Ben Clay 7 years ago committed by Facebook Github Bot
parent edcbb36944
commit 382277d0fe
  1. 36
      java/rocksjni/options.cc
  2. 71
      java/src/main/java/org/rocksdb/ReadOptions.java
  3. 23
      java/src/main/java/org/rocksdb/Slice.java
  4. 54
      java/src/test/java/org/rocksdb/ReadOptionsTest.java

@ -5728,6 +5728,19 @@ jlong Java_org_rocksdb_ReadOptions_newReadOptions(
return reinterpret_cast<jlong>(read_options); return reinterpret_cast<jlong>(read_options);
} }
/*
* Class: org_rocksdb_ReadOptions
* Method: copyReadOptions
* Signature: (J)J
*/
jlong Java_org_rocksdb_ReadOptions_copyReadOptions(
JNIEnv* env, jclass jcls, jlong jhandle) {
auto old_read_opt = reinterpret_cast<rocksdb::ReadOptions*>(jhandle);
auto new_read_opt = new rocksdb::ReadOptions();
*new_read_opt = *old_read_opt;
return reinterpret_cast<jlong>(new_read_opt);
}
/* /*
* Class: org_rocksdb_ReadOptions * Class: org_rocksdb_ReadOptions
* Method: disposeInternal * Method: disposeInternal
@ -6003,6 +6016,29 @@ void Java_org_rocksdb_ReadOptions_setReadTier(
static_cast<rocksdb::ReadTier>(jread_tier); static_cast<rocksdb::ReadTier>(jread_tier);
} }
/*
* Class: org_rocksdb_ReadOptions
* Method: setIterateUpperBound
* Signature: (JJ)I
*/
void Java_org_rocksdb_ReadOptions_setIterateUpperBound(
JNIEnv* env, jobject jobj, jlong jhandle, jlong jupper_bound_slice_handle) {
reinterpret_cast<rocksdb::ReadOptions*>(jhandle)->iterate_upper_bound =
reinterpret_cast<rocksdb::Slice*>(jupper_bound_slice_handle);
}
/*
* Class: org_rocksdb_ReadOptions
* Method: iterateUpperBound
* Signature: (J)J
*/
jlong Java_org_rocksdb_ReadOptions_iterateUpperBound(
JNIEnv* env, jobject jobj, jlong jhandle) {
auto& upper_bound_slice_handle =
reinterpret_cast<rocksdb::ReadOptions*>(jhandle)->iterate_upper_bound;
return reinterpret_cast<jlong>(upper_bound_slice_handle);
}
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
// rocksdb::ComparatorOptions // rocksdb::ComparatorOptions

@ -16,6 +16,19 @@ public class ReadOptions extends RocksObject {
super(newReadOptions()); super(newReadOptions());
} }
/**
* Copy constructor.
*
* NOTE: This does a shallow copy, which means snapshot, iterate_upper_bound
* and other pointers will be cloned!
*
* @param other The ReadOptions to copy.
*/
public ReadOptions(ReadOptions other) {
super(copyReadOptions(other.nativeHandle_));
iterateUpperBoundSlice_ = other.iterateUpperBoundSlice_;
}
/** /**
* If true, all data read from underlying storage will be * If true, all data read from underlying storage will be
* verified against corresponding checksums. * verified against corresponding checksums.
@ -363,7 +376,62 @@ public class ReadOptions extends RocksObject {
return this; return this;
} }
/**
* Defines the extent upto which the forward iterator can returns entries.
* Once the bound is reached, Valid() will be false. iterate_upper_bound
* is exclusive ie the bound value is not a valid entry. If
* iterator_extractor is not null, the Seek target and iterator_upper_bound
* need to have the same prefix. This is because ordering is not guaranteed
* outside of prefix domain. There is no lower bound on the iterator.
*
* Default: nullptr
*
* @param iterateUpperBound Slice representing the upper bound
* @return the reference to the current ReadOptions.
*/
public ReadOptions setIterateUpperBound(final Slice iterateUpperBound) {
assert(isOwningHandle());
if (iterateUpperBound != null) {
// Hold onto a reference so it doesn't get garbaged collected out from under us.
iterateUpperBoundSlice_ = iterateUpperBound;
setIterateUpperBound(nativeHandle_, iterateUpperBoundSlice_.getNativeHandle());
}
return this;
}
/**
* Defines the extent upto which the forward iterator can returns entries.
* Once the bound is reached, Valid() will be false. iterate_upper_bound
* is exclusive ie the bound value is not a valid entry. If
* iterator_extractor is not null, the Seek target and iterator_upper_bound
* need to have the same prefix. This is because ordering is not guaranteed
* outside of prefix domain. There is no lower bound on the iterator.
*
* Default: nullptr
*
* @return Slice representing current iterate_upper_bound setting, or null if
* one does not exist.
*/
public Slice iterateUpperBound() {
assert(isOwningHandle());
long upperBoundSliceHandle = iterateUpperBound(nativeHandle_);
if (upperBoundSliceHandle != 0) {
// Disown the new slice - it's owned by the C++ side of the JNI boundary
// from the perspective of this method.
return new Slice(upperBoundSliceHandle, false);
}
return null;
}
// Hold a reference to any iterate upper bound that was set on this object
// until we're destroyed or it's overwritten. That way the caller can freely
// leave scope without us losing the Java Slice object, which during close()
// would also reap its associated rocksdb::Slice native object since it's
// possibly (likely) to be an owning handle.
protected Slice iterateUpperBoundSlice_;
private native static long newReadOptions(); private native static long newReadOptions();
private native static long copyReadOptions(long handle);
private native boolean verifyChecksums(long handle); private native boolean verifyChecksums(long handle);
private native void setVerifyChecksums(long handle, boolean verifyChecksums); private native void setVerifyChecksums(long handle, boolean verifyChecksums);
private native boolean fillCache(long handle); private native boolean fillCache(long handle);
@ -391,6 +459,9 @@ public class ReadOptions extends RocksObject {
private native boolean ignoreRangeDeletions(final long handle); private native boolean ignoreRangeDeletions(final long handle);
private native void setIgnoreRangeDeletions(final long handle, private native void setIgnoreRangeDeletions(final long handle,
final boolean ignoreRangeDeletions); final boolean ignoreRangeDeletions);
private native void setIterateUpperBound(final long handle,
final long upperBoundSliceHandle);
private native long iterateUpperBound(final long handle);
@Override protected final native void disposeInternal(final long handle); @Override protected final native void disposeInternal(final long handle);

@ -39,6 +39,29 @@ public class Slice extends AbstractSlice<byte[]> {
super(); super();
} }
/**
* <p>Package-private Slice constructor which is used to construct
* Slice instances from C++ side. As the reference to this
* object is also managed from C++ side the handle will be disowned.</p>
*
* @param nativeHandle address of native instance.
*/
Slice(final long nativeHandle) {
this(nativeHandle, false);
}
/**
* <p>Package-private Slice constructor which is used to construct
* Slice instances using a handle. </p>
*
* @param nativeHandle address of native instance.
* @param owningNativeHandle whether to own this reference from the C++ side or not
*/
Slice(final long nativeHandle, final boolean owningNativeHandle) {
super();
setNativeHandle(nativeHandle, owningNativeHandle);
}
/** /**
* <p>Constructs a slice where the data is taken from * <p>Constructs a slice where the data is taken from
* a String.</p> * a String.</p>

@ -5,6 +5,7 @@
package org.rocksdb; package org.rocksdb;
import java.util.Arrays;
import java.util.Random; import java.util.Random;
import org.junit.ClassRule; import org.junit.ClassRule;
@ -127,6 +128,35 @@ public class ReadOptionsTest {
} }
} }
@Test
public void iterateUpperBound() {
try (final ReadOptions opt = new ReadOptions()) {
Slice upperBound = buildRandomSlice();
opt.setIterateUpperBound(upperBound);
assertThat(Arrays.equals(upperBound.data(), opt.iterateUpperBound().data())).isTrue();
}
}
@Test
public void iterateUpperBoundNull() {
try (final ReadOptions opt = new ReadOptions()) {
assertThat(opt.iterateUpperBound()).isNull();
}
}
@Test
public void copyConstructor() {
try (final ReadOptions opt = new ReadOptions()) {
opt.setVerifyChecksums(false);
opt.setFillCache(false);
opt.setIterateUpperBound(buildRandomSlice());
ReadOptions other = new ReadOptions(opt);
assertThat(opt.verifyChecksums()).isEqualTo(other.verifyChecksums());
assertThat(opt.fillCache()).isEqualTo(other.fillCache());
assertThat(Arrays.equals(opt.iterateUpperBound().data(), other.iterateUpperBound().data())).isTrue();
}
}
@Test @Test
public void failSetVerifyChecksumUninitialized() { public void failSetVerifyChecksumUninitialized() {
try (final ReadOptions readOptions = try (final ReadOptions readOptions =
@ -191,6 +221,22 @@ public class ReadOptionsTest {
} }
} }
@Test
public void failSetIterateUpperBoundUninitialized() {
try (final ReadOptions readOptions =
setupUninitializedReadOptions(exception)) {
readOptions.setIterateUpperBound(null);
}
}
@Test
public void failIterateUpperBoundUninitialized() {
try (final ReadOptions readOptions =
setupUninitializedReadOptions(exception)) {
readOptions.iterateUpperBound();
}
}
private ReadOptions setupUninitializedReadOptions( private ReadOptions setupUninitializedReadOptions(
ExpectedException exception) { ExpectedException exception) {
final ReadOptions readOptions = new ReadOptions(); final ReadOptions readOptions = new ReadOptions();
@ -198,4 +244,12 @@ public class ReadOptionsTest {
exception.expect(AssertionError.class); exception.expect(AssertionError.class);
return readOptions; return readOptions;
} }
private Slice buildRandomSlice() {
final Random rand = new Random();
byte[] sliceBytes = new byte[rand.nextInt(100) + 1];
rand.nextBytes(sliceBytes);
return new Slice(sliceBytes);
}
} }

Loading…
Cancel
Save