mirror of https://github.com/facebook/rocksdb.git
ReadOptions - Add missing java API. (#9248)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/9248 Reviewed By: mrambacher Differential Revision: D33011237 Pulled By: jay-zhuang fbshipit-source-id: b6544ad40cb722e327bac60a0af711db253e36d7
This commit is contained in:
parent
96d0773a11
commit
7ac3a5d406
|
@ -8266,6 +8266,141 @@ jlong Java_org_rocksdb_ReadOptions_iterStartSeqnum(
|
|||
return static_cast<jlong>(opt->iter_start_seqnum);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: autoPrefixMode
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_ReadOptions_autoPrefixMode(JNIEnv*, jobject,
|
||||
jlong jhandle) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
return static_cast<jboolean>(opt->auto_prefix_mode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: setAutoPrefixMode
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_ReadOptions_setAutoPrefixMode(
|
||||
JNIEnv*, jobject, jlong jhandle, jboolean jauto_prefix_mode) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
opt->auto_prefix_mode = static_cast<bool>(jauto_prefix_mode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: timestamp
|
||||
* Signature: (J)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_ReadOptions_timestamp(JNIEnv*, jobject, jlong jhandle) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
auto& timestamp_slice_handle = opt->timestamp;
|
||||
return reinterpret_cast<jlong>(timestamp_slice_handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: setTimestamp
|
||||
* Signature: (JJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_ReadOptions_setTimestamp(JNIEnv*, jobject, jlong jhandle,
|
||||
jlong jtimestamp_slice_handle) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
opt->timestamp =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(jtimestamp_slice_handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: iterStartTs
|
||||
* Signature: (J)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_ReadOptions_iterStartTs(JNIEnv*, jobject,
|
||||
jlong jhandle) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
auto& iter_start_ts_handle = opt->iter_start_ts;
|
||||
return reinterpret_cast<jlong>(iter_start_ts_handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: setIterStartTs
|
||||
* Signature: (JJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_ReadOptions_setIterStartTs(JNIEnv*, jobject,
|
||||
jlong jhandle,
|
||||
jlong jiter_start_ts_handle) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
opt->iter_start_ts =
|
||||
reinterpret_cast<ROCKSDB_NAMESPACE::Slice*>(jiter_start_ts_handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: deadline
|
||||
* Signature: (J)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_ReadOptions_deadline(JNIEnv*, jobject, jlong jhandle) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
return static_cast<jlong>(opt->deadline.count());
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: setDeadline
|
||||
* Signature: (JJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_ReadOptions_setDeadline(JNIEnv*, jobject, jlong jhandle,
|
||||
jlong jdeadline) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
opt->deadline = std::chrono::microseconds(static_cast<int64_t>(jdeadline));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: ioTimeout
|
||||
* Signature: (J)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_ReadOptions_ioTimeout(JNIEnv*, jobject, jlong jhandle) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
return static_cast<jlong>(opt->io_timeout.count());
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: setIoTimeout
|
||||
* Signature: (JJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_ReadOptions_setIoTimeout(JNIEnv*, jobject, jlong jhandle,
|
||||
jlong jio_timeout) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
opt->io_timeout =
|
||||
std::chrono::microseconds(static_cast<int64_t>(jio_timeout));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: valueSizeSofLimit
|
||||
* Signature: (J)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_ReadOptions_valueSizeSoftLimit(JNIEnv*, jobject,
|
||||
jlong jhandle) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
return static_cast<jlong>(opt->value_size_soft_limit);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: setValueSizeSofLimit
|
||||
* Signature: (JJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_ReadOptions_setValueSizeSoftLimit(
|
||||
JNIEnv*, jobject, jlong jhandle, jlong jvalue_size_soft_limit) {
|
||||
auto* opt = reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jhandle);
|
||||
opt->value_size_soft_limit = static_cast<uint64_t>(jvalue_size_soft_limit);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// ROCKSDB_NAMESPACE::ComparatorOptions
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ public class ReadOptions extends RocksObject {
|
|||
super(copyReadOptions(other.nativeHandle_));
|
||||
this.iterateLowerBoundSlice_ = other.iterateLowerBoundSlice_;
|
||||
this.iterateUpperBoundSlice_ = other.iterateUpperBoundSlice_;
|
||||
this.timestampSlice_ = other.timestampSlice_;
|
||||
this.iterStartTs_ = other.iterStartTs_;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -560,6 +562,233 @@ public class ReadOptions extends RocksObject {
|
|||
return iterStartSeqnum(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* When true, by default use total_order_seek = true, and RocksDB can
|
||||
* selectively enable prefix seek mode if won't generate a different result
|
||||
* from total_order_seek, based on seek key, and iterator upper bound.
|
||||
* Not supported in ROCKSDB_LITE mode, in the way that even with value true
|
||||
* prefix mode is not used.
|
||||
* Default: false
|
||||
*
|
||||
* @return true if auto prefix mode is set.
|
||||
*
|
||||
*/
|
||||
public boolean autoPrefixMode() {
|
||||
assert (isOwningHandle());
|
||||
return autoPrefixMode(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* When true, by default use total_order_seek = true, and RocksDB can
|
||||
* selectively enable prefix seek mode if won't generate a different result
|
||||
* from total_order_seek, based on seek key, and iterator upper bound.
|
||||
* Not supported in ROCKSDB_LITE mode, in the way that even with value true
|
||||
* prefix mode is not used.
|
||||
* Default: false
|
||||
* @param mode auto prefix mode
|
||||
* @return the reference to the current ReadOptions.
|
||||
*/
|
||||
public ReadOptions setAutoPrefixMode(final boolean mode) {
|
||||
assert (isOwningHandle());
|
||||
setAutoPrefixMode(nativeHandle_, mode);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp of operation. Read should return the latest data visible to the
|
||||
* specified timestamp. All timestamps of the same database must be of the
|
||||
* same length and format. The user is responsible for providing a customized
|
||||
* compare function via Comparator to order >key, timestamp> tuples.
|
||||
* For iterator, iter_start_ts is the lower bound (older) and timestamp
|
||||
* serves as the upper bound. Versions of the same record that fall in
|
||||
* the timestamp range will be returned. If iter_start_ts is nullptr,
|
||||
* only the most recent version visible to timestamp is returned.
|
||||
* The user-specified timestamp feature is still under active development,
|
||||
* and the API is subject to change.
|
||||
*
|
||||
* Default: null
|
||||
* @see #iterStartTs()
|
||||
* @return Reference to timestamp or null if there is no timestamp defined.
|
||||
*/
|
||||
public Slice timestamp() {
|
||||
assert (isOwningHandle());
|
||||
final long timestampSliceHandle = timestamp(nativeHandle_);
|
||||
if (timestampSliceHandle != 0) {
|
||||
return new Slice(timestampSliceHandle);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp of operation. Read should return the latest data visible to the
|
||||
* specified timestamp. All timestamps of the same database must be of the
|
||||
* same length and format. The user is responsible for providing a customized
|
||||
* compare function via Comparator to order {@code <key, timestamp>} tuples.
|
||||
* For iterator, {@code iter_start_ts} is the lower bound (older) and timestamp
|
||||
* serves as the upper bound. Versions of the same record that fall in
|
||||
* the timestamp range will be returned. If iter_start_ts is nullptr,
|
||||
* only the most recent version visible to timestamp is returned.
|
||||
* The user-specified timestamp feature is still under active development,
|
||||
* and the API is subject to change.
|
||||
*
|
||||
* Default: null
|
||||
* @see #setIterStartTs(AbstractSlice)
|
||||
* @param timestamp Slice representing the timestamp
|
||||
* @return the reference to the current ReadOptions.
|
||||
*/
|
||||
public ReadOptions setTimestamp(final AbstractSlice<?> timestamp) {
|
||||
assert (isOwningHandle());
|
||||
setTimestamp(nativeHandle_, timestamp == null ? 0 : timestamp.getNativeHandle());
|
||||
timestampSlice_ = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp of operation. Read should return the latest data visible to the
|
||||
* specified timestamp. All timestamps of the same database must be of the
|
||||
* same length and format. The user is responsible for providing a customized
|
||||
* compare function via Comparator to order {@code <key, timestamp>} tuples.
|
||||
* For iterator, {@code iter_start_ts} is the lower bound (older) and timestamp
|
||||
* serves as the upper bound. Versions of the same record that fall in
|
||||
* the timestamp range will be returned. If iter_start_ts is nullptr,
|
||||
* only the most recent version visible to timestamp is returned.
|
||||
* The user-specified timestamp feature is still under active development,
|
||||
* and the API is subject to change.
|
||||
*
|
||||
* Default: null
|
||||
* @return Reference to lower bound timestamp or null if there is no lower bound timestamp
|
||||
* defined.
|
||||
*/
|
||||
public Slice iterStartTs() {
|
||||
assert (isOwningHandle());
|
||||
final long iterStartTsHandle = iterStartTs(nativeHandle_);
|
||||
if (iterStartTsHandle != 0) {
|
||||
return new Slice(iterStartTsHandle);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp of operation. Read should return the latest data visible to the
|
||||
* specified timestamp. All timestamps of the same database must be of the
|
||||
* same length and format. The user is responsible for providing a customized
|
||||
* compare function via Comparator to order {@code <key, timestamp>} tuples.
|
||||
* For iterator, {@code iter_start_ts} is the lower bound (older) and timestamp
|
||||
* serves as the upper bound. Versions of the same record that fall in
|
||||
* the timestamp range will be returned. If iter_start_ts is nullptr,
|
||||
* only the most recent version visible to timestamp is returned.
|
||||
* The user-specified timestamp feature is still under active development,
|
||||
* and the API is subject to change.
|
||||
*
|
||||
* Default: null
|
||||
*
|
||||
* @param iterStartTs Reference to lower bound timestamp or null if there is no lower bound
|
||||
* timestamp defined
|
||||
* @return the reference to the current ReadOptions.
|
||||
*/
|
||||
public ReadOptions setIterStartTs(final AbstractSlice<?> iterStartTs) {
|
||||
assert (isOwningHandle());
|
||||
setIterStartTs(nativeHandle_, iterStartTs == null ? 0 : iterStartTs.getNativeHandle());
|
||||
iterStartTs_ = iterStartTs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deadline for completing an API call (Get/MultiGet/Seek/Next for now)
|
||||
* in microseconds.
|
||||
* It should be set to microseconds since epoch, i.e, {@code gettimeofday} or
|
||||
* equivalent plus allowed duration in microseconds. The best way is to use
|
||||
* {@code env->NowMicros() + some timeout}.
|
||||
* This is best efforts. The call may exceed the deadline if there is IO
|
||||
* involved and the file system doesn't support deadlines, or due to
|
||||
* checking for deadline periodically rather than for every key if
|
||||
* processing a batch
|
||||
*
|
||||
* @return deadline time in microseconds
|
||||
*/
|
||||
public long deadline() {
|
||||
assert (isOwningHandle());
|
||||
return deadline(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deadline for completing an API call (Get/MultiGet/Seek/Next for now)
|
||||
* in microseconds.
|
||||
* It should be set to microseconds since epoch, i.e, {@code gettimeofday} or
|
||||
* equivalent plus allowed duration in microseconds. The best way is to use
|
||||
* {@code env->NowMicros() + some timeout}.
|
||||
* This is best efforts. The call may exceed the deadline if there is IO
|
||||
* involved and the file system doesn't support deadlines, or due to
|
||||
* checking for deadline periodically rather than for every key if
|
||||
* processing a batch
|
||||
*
|
||||
* @param deadlineTime deadline time in microseconds.
|
||||
* @return the reference to the current ReadOptions.
|
||||
*/
|
||||
public ReadOptions setDeadline(final long deadlineTime) {
|
||||
assert (isOwningHandle());
|
||||
setDeadline(nativeHandle_, deadlineTime);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A timeout in microseconds to be passed to the underlying FileSystem for
|
||||
* reads. As opposed to deadline, this determines the timeout for each
|
||||
* individual file read request. If a MultiGet/Get/Seek/Next etc call
|
||||
* results in multiple reads, each read can last up to io_timeout us.
|
||||
* @return ioTimeout time in microseconds
|
||||
*/
|
||||
public long ioTimeout() {
|
||||
assert (isOwningHandle());
|
||||
return ioTimeout(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* A timeout in microseconds to be passed to the underlying FileSystem for
|
||||
* reads. As opposed to deadline, this determines the timeout for each
|
||||
* individual file read request. If a MultiGet/Get/Seek/Next etc call
|
||||
* results in multiple reads, each read can last up to io_timeout us.
|
||||
*
|
||||
* @param ioTimeout time in microseconds.
|
||||
* @return the reference to the current ReadOptions.
|
||||
*/
|
||||
public ReadOptions setIoTimeout(final long ioTimeout) {
|
||||
assert (isOwningHandle());
|
||||
setIoTimeout(nativeHandle_, ioTimeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* It limits the maximum cumulative value size of the keys in batch while
|
||||
* reading through MultiGet. Once the cumulative value size exceeds this
|
||||
* soft limit then all the remaining keys are returned with status Aborted.
|
||||
*
|
||||
* Default: {@code std::numeric_limits<uint64_t>::max()}
|
||||
* @return actual valueSizeSofLimit
|
||||
*/
|
||||
public long valueSizeSoftLimit() {
|
||||
assert (isOwningHandle());
|
||||
return valueSizeSoftLimit(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* It limits the maximum cumulative value size of the keys in batch while
|
||||
* reading through MultiGet. Once the cumulative value size exceeds this
|
||||
* soft limit then all the remaining keys are returned with status Aborted.
|
||||
*
|
||||
* Default: {@code std::numeric_limits<uint64_t>::max()}
|
||||
*
|
||||
* @param valueSizeSofLimit
|
||||
* @return the reference to the current ReadOptions
|
||||
*/
|
||||
public ReadOptions setValueSizeSoftLimit(final long valueSizeSofLimit) {
|
||||
assert (isOwningHandle());
|
||||
setValueSizeSoftLimit(nativeHandle_, valueSizeSofLimit);
|
||||
return this;
|
||||
}
|
||||
|
||||
// instance variables
|
||||
// NOTE: If you add new member variables, please update the copy constructor above!
|
||||
//
|
||||
|
@ -570,6 +799,8 @@ public class ReadOptions extends RocksObject {
|
|||
// it's possibly (likely) to be an owning handle.
|
||||
private AbstractSlice<?> iterateLowerBoundSlice_;
|
||||
private AbstractSlice<?> iterateUpperBoundSlice_;
|
||||
private AbstractSlice<?> timestampSlice_;
|
||||
private AbstractSlice<?> iterStartTs_;
|
||||
|
||||
private native static long newReadOptions();
|
||||
private native static long newReadOptions(final boolean verifyChecksums,
|
||||
|
@ -617,4 +848,16 @@ public class ReadOptions extends RocksObject {
|
|||
final long tableFilterHandle);
|
||||
private native void setIterStartSeqnum(final long handle, final long seqNum);
|
||||
private native long iterStartSeqnum(final long handle);
|
||||
private native boolean autoPrefixMode(final long handle);
|
||||
private native void setAutoPrefixMode(final long handle, final boolean autoPrefixMode);
|
||||
private native long timestamp(final long handle);
|
||||
private native void setTimestamp(final long handle, final long timestampSliceHandle);
|
||||
private native long iterStartTs(final long handle);
|
||||
private native void setIterStartTs(final long handle, final long iterStartTsHandle);
|
||||
private native long deadline(final long handle);
|
||||
private native void setDeadline(final long handle, final long deadlineTime);
|
||||
private native long ioTimeout(final long handle);
|
||||
private native void setIoTimeout(final long handle, final long ioTimeout);
|
||||
private native long valueSizeSoftLimit(final long handle);
|
||||
private native void setValueSizeSoftLimit(final long handle, final long softLimit);
|
||||
}
|
||||
|
|
|
@ -39,11 +39,15 @@ public class ReadOptionsTest {
|
|||
opt.setFillCache(false);
|
||||
opt.setIterateUpperBound(buildRandomSlice());
|
||||
opt.setIterateLowerBound(buildRandomSlice());
|
||||
opt.setTimestamp(buildRandomSlice());
|
||||
opt.setIterStartTs(buildRandomSlice());
|
||||
try (final 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();
|
||||
assertThat(Arrays.equals(opt.iterateLowerBound().data(), other.iterateLowerBound().data())).isTrue();
|
||||
assertThat(Arrays.equals(opt.timestamp().data(), other.timestamp().data())).isTrue();
|
||||
assertThat(Arrays.equals(opt.iterStartTs().data(), other.iterStartTs().data())).isTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,6 +211,60 @@ public class ReadOptionsTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoPrefixMode() {
|
||||
try (final ReadOptions opt = new ReadOptions()) {
|
||||
opt.setAutoPrefixMode(true);
|
||||
assertThat(opt.autoPrefixMode()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timestamp() {
|
||||
try (final ReadOptions opt = new ReadOptions()) {
|
||||
Slice timestamp = buildRandomSlice();
|
||||
opt.setTimestamp(timestamp);
|
||||
assertThat(Arrays.equals(timestamp.data(), opt.timestamp().data())).isTrue();
|
||||
opt.setTimestamp(null);
|
||||
assertThat(opt.timestamp()).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void iterStartTs() {
|
||||
try (final ReadOptions opt = new ReadOptions()) {
|
||||
Slice itertStartTsSlice = buildRandomSlice();
|
||||
opt.setIterStartTs(itertStartTsSlice);
|
||||
assertThat(Arrays.equals(itertStartTsSlice.data(), opt.iterStartTs().data())).isTrue();
|
||||
opt.setIterStartTs(null);
|
||||
assertThat(opt.iterStartTs()).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deadline() {
|
||||
try (final ReadOptions opt = new ReadOptions()) {
|
||||
opt.setDeadline(1999l);
|
||||
assertThat(opt.deadline()).isEqualTo(1999l);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ioTimeout() {
|
||||
try (final ReadOptions opt = new ReadOptions()) {
|
||||
opt.setIoTimeout(34555l);
|
||||
assertThat(opt.ioTimeout()).isEqualTo(34555l);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void valueSizeSoftLimit() {
|
||||
try (final ReadOptions opt = new ReadOptions()) {
|
||||
opt.setValueSizeSoftLimit(12134324l);
|
||||
assertThat(opt.valueSizeSoftLimit()).isEqualTo(12134324l);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failSetVerifyChecksumUninitialized() {
|
||||
try (final ReadOptions readOptions =
|
||||
|
|
Loading…
Reference in New Issue