diff --git a/java/rocksjni/options.cc b/java/rocksjni/options.cc index 8194abaf6..f52171244 100644 --- a/java/rocksjni/options.cc +++ b/java/rocksjni/options.cc @@ -5728,6 +5728,19 @@ jlong Java_org_rocksdb_ReadOptions_newReadOptions( return reinterpret_cast(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(jhandle); + auto new_read_opt = new rocksdb::ReadOptions(); + *new_read_opt = *old_read_opt; + return reinterpret_cast(new_read_opt); +} + /* * Class: org_rocksdb_ReadOptions * Method: disposeInternal @@ -6003,6 +6016,29 @@ void Java_org_rocksdb_ReadOptions_setReadTier( static_cast(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(jhandle)->iterate_upper_bound = + reinterpret_cast(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(jhandle)->iterate_upper_bound; + return reinterpret_cast(upper_bound_slice_handle); +} + ///////////////////////////////////////////////////////////////////// // rocksdb::ComparatorOptions diff --git a/java/src/main/java/org/rocksdb/ReadOptions.java b/java/src/main/java/org/rocksdb/ReadOptions.java index 9d7b99956..30c67a0e8 100644 --- a/java/src/main/java/org/rocksdb/ReadOptions.java +++ b/java/src/main/java/org/rocksdb/ReadOptions.java @@ -16,6 +16,19 @@ public class ReadOptions extends RocksObject { 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 * verified against corresponding checksums. @@ -363,7 +376,62 @@ public class ReadOptions extends RocksObject { 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 copyReadOptions(long handle); private native boolean verifyChecksums(long handle); private native void setVerifyChecksums(long handle, boolean verifyChecksums); private native boolean fillCache(long handle); @@ -391,6 +459,9 @@ public class ReadOptions extends RocksObject { private native boolean ignoreRangeDeletions(final long handle); private native void setIgnoreRangeDeletions(final long handle, 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); diff --git a/java/src/main/java/org/rocksdb/Slice.java b/java/src/main/java/org/rocksdb/Slice.java index a122c3769..08a940c3f 100644 --- a/java/src/main/java/org/rocksdb/Slice.java +++ b/java/src/main/java/org/rocksdb/Slice.java @@ -39,6 +39,29 @@ public class Slice extends AbstractSlice { super(); } + /** + *

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.

+ * + * @param nativeHandle address of native instance. + */ + Slice(final long nativeHandle) { + this(nativeHandle, false); + } + + /** + *

Package-private Slice constructor which is used to construct + * Slice instances using a handle.

+ * + * @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); + } + /** *

Constructs a slice where the data is taken from * a String.

diff --git a/java/src/test/java/org/rocksdb/ReadOptionsTest.java b/java/src/test/java/org/rocksdb/ReadOptionsTest.java index da048c443..f7d799909 100644 --- a/java/src/test/java/org/rocksdb/ReadOptionsTest.java +++ b/java/src/test/java/org/rocksdb/ReadOptionsTest.java @@ -5,6 +5,7 @@ package org.rocksdb; +import java.util.Arrays; import java.util.Random; 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 public void failSetVerifyChecksumUninitialized() { 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( ExpectedException exception) { final ReadOptions readOptions = new ReadOptions(); @@ -198,4 +244,12 @@ public class ReadOptionsTest { exception.expect(AssertionError.class); 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); + } + }