RocksDB#keyMayExist should not assume database values are unicode strings (#6186)

Summary:
Closes https://github.com/facebook/rocksdb/issues/6183
Pull Request resolved: https://github.com/facebook/rocksdb/pull/6186

Differential Revision: D19201281

Pulled By: pdillinger

fbshipit-source-id: 1c96b4ea09e826f91e44b0009eba3de0991d9053
main
Adam Retter 5 years ago committed by Facebook Github Bot
parent 4d3264e4ab
commit e697da0b18
  1. 1
      HISTORY.md
  2. 1
      java/CMakeLists.txt
  3. 209
      java/rocksjni/rocksjni.cc
  4. 46
      java/src/main/java/org/rocksdb/Holder.java
  5. 271
      java/src/main/java/org/rocksdb/RocksDB.java
  6. 3
      java/src/test/java/org/rocksdb/CompactionFilterFactoryTest.java
  7. 145
      java/src/test/java/org/rocksdb/KeyMayExistTest.java

@ -3,6 +3,7 @@
### Public API Change ### Public API Change
* Added a rocksdb::FileSystem class in include/rocksdb/file_system.h to encapsulate file creation/read/write operations, and an option DBOptions::file_system to allow a user to pass in an instance of rocksdb::FileSystem. If its a non-null value, this will take precendence over DBOptions::env for file operations. A new API rocksdb::FileSystem::Default() returns a platform default object. The DBOptions::env option and Env::Default() API will continue to be used for threading and other OS related functions, and where DBOptions::file_system is not specified, for file operations. For storage developers who are accustomed to rocksdb::Env, the interface in rocksdb::FileSystem is new and will probably undergo some changes as more storage systems are ported to it from rocksdb::Env. As of now, no env other than Posix has been ported to the new interface. * Added a rocksdb::FileSystem class in include/rocksdb/file_system.h to encapsulate file creation/read/write operations, and an option DBOptions::file_system to allow a user to pass in an instance of rocksdb::FileSystem. If its a non-null value, this will take precendence over DBOptions::env for file operations. A new API rocksdb::FileSystem::Default() returns a platform default object. The DBOptions::env option and Env::Default() API will continue to be used for threading and other OS related functions, and where DBOptions::file_system is not specified, for file operations. For storage developers who are accustomed to rocksdb::Env, the interface in rocksdb::FileSystem is new and will probably undergo some changes as more storage systems are ported to it from rocksdb::Env. As of now, no env other than Posix has been ported to the new interface.
* A new rocksdb::NewSstFileManager() API that allows the caller to pass in separate Env and FileSystem objects. * A new rocksdb::NewSstFileManager() API that allows the caller to pass in separate Env and FileSystem objects.
* Changed Java API for RocksDB.keyMayExist functions to use Holder<byte[]> instead of StringBuilder, so that retrieved values need not decode to Strings.
### Bug Fixes ### Bug Fixes
* Fix a bug that can cause unnecessary bg thread to be scheduled(#6104). * Fix a bug that can cause unnecessary bg thread to be scheduled(#6104).

@ -144,6 +144,7 @@ set(JAVA_MAIN_CLASSES
src/main/java/org/rocksdb/HdfsEnv.java src/main/java/org/rocksdb/HdfsEnv.java
src/main/java/org/rocksdb/HistogramData.java src/main/java/org/rocksdb/HistogramData.java
src/main/java/org/rocksdb/HistogramType.java src/main/java/org/rocksdb/HistogramType.java
src/main/java/org/rocksdb/Holder.java
src/main/java/org/rocksdb/IndexType.java src/main/java/org/rocksdb/IndexType.java
src/main/java/org/rocksdb/InfoLogLevel.java src/main/java/org/rocksdb/InfoLogLevel.java
src/main/java/org/rocksdb/IngestExternalFileOptions.java src/main/java/org/rocksdb/IngestExternalFileOptions.java

@ -1588,127 +1588,156 @@ jobjectArray Java_org_rocksdb_RocksDB_multiGet__JJ_3_3B_3I_3I_3J(
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// rocksdb::DB::KeyMayExist // rocksdb::DB::KeyMayExist
jboolean key_may_exist_helper(JNIEnv* env, rocksdb::DB* db, bool key_may_exist_helper(JNIEnv* env, jlong jdb_handle, jlong jcf_handle,
const rocksdb::ReadOptions& read_opt, jlong jread_opts_handle,
rocksdb::ColumnFamilyHandle* cf_handle, jbyteArray jkey, jint jkey_offset, jint jkey_len,
jbyteArray jkey, jint jkey_off, jint jkey_len, bool* has_exception, std::string* value, bool* value_found) {
jobject jstring_builder, bool* has_exception) { auto* db = reinterpret_cast<rocksdb::DB*>(jdb_handle);
rocksdb::ColumnFamilyHandle* cf_handle;
if (jcf_handle == 0) {
cf_handle = db->DefaultColumnFamily();
} else {
cf_handle = reinterpret_cast<rocksdb::ColumnFamilyHandle*>(jcf_handle);
}
rocksdb::ReadOptions read_opts =
jread_opts_handle == 0 ? rocksdb::ReadOptions()
: *(reinterpret_cast<rocksdb::ReadOptions*>(jread_opts_handle));
jbyte* key = new jbyte[jkey_len]; jbyte* key = new jbyte[jkey_len];
env->GetByteArrayRegion(jkey, jkey_off, jkey_len, key); env->GetByteArrayRegion(jkey, jkey_offset, jkey_len, key);
if (env->ExceptionCheck()) { if (env->ExceptionCheck()) {
// exception thrown: ArrayIndexOutOfBoundsException // exception thrown: ArrayIndexOutOfBoundsException
delete[] key; delete[] key;
*has_exception = true; *has_exception = true;
return false; return false;
} }
rocksdb::Slice key_slice(reinterpret_cast<char*>(key), jkey_len); rocksdb::Slice key_slice(reinterpret_cast<char*>(key), jkey_len);
std::string value; const bool exists = db->KeyMayExist(
bool value_found = false; read_opts, cf_handle, key_slice, value, value_found);
bool keyMayExist;
if (cf_handle != nullptr) {
keyMayExist =
db->KeyMayExist(read_opt, cf_handle, key_slice, &value, &value_found);
} else {
keyMayExist = db->KeyMayExist(read_opt, key_slice, &value, &value_found);
}
// cleanup // cleanup
delete[] key; delete[] key;
// extract the value return exists;
if (value_found && !value.empty()) {
jobject jresult_string_builder =
rocksdb::StringBuilderJni::append(env, jstring_builder, value.c_str());
if (jresult_string_builder == nullptr) {
*has_exception = true;
return false;
}
}
*has_exception = false;
return static_cast<jboolean>(keyMayExist);
} }
/* /*
* Class: org_rocksdb_RocksDB * Class: org_rocksdb_RocksDB
* Method: keyMayExist * Method: keyMayExist
* Signature: (J[BIILjava/lang/StringBuilder;)Z * Signature: (JJJ[BII)Z
*/ */
jboolean Java_org_rocksdb_RocksDB_keyMayExist__J_3BIILjava_lang_StringBuilder_2( jboolean Java_org_rocksdb_RocksDB_keyMayExist(
JNIEnv* env, jobject, jlong jdb_handle, JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle,
jbyteArray jkey, jint jkey_off, jint jkey_len, jobject jstring_builder) { jlong jread_opts_handle,
auto* db = reinterpret_cast<rocksdb::DB*>(jdb_handle); jbyteArray jkey, jint jkey_offset, jint jkey_len) {
bool has_exception = false; bool has_exception = false;
return key_may_exist_helper(env, db, rocksdb::ReadOptions(), nullptr, jkey, std::string value;
jkey_off, jkey_len, jstring_builder, &has_exception); bool value_found = false;
}
/* const bool exists = key_may_exist_helper(
* Class: org_rocksdb_RocksDB env, jdb_handle, jcf_handle, jread_opts_handle,
* Method: keyMayExist jkey, jkey_offset, jkey_len,
* Signature: (J[BIIJLjava/lang/StringBuilder;)Z &has_exception, &value, &value_found);
*/
jboolean if (has_exception) {
Java_org_rocksdb_RocksDB_keyMayExist__J_3BIIJLjava_lang_StringBuilder_2( // java exception already raised
JNIEnv* env, jobject, jlong jdb_handle, return false;
jbyteArray jkey, jint jkey_off, jint jkey_len,
jlong jcf_handle, jobject jstring_builder) {
auto* db = reinterpret_cast<rocksdb::DB*>(jdb_handle);
auto* cf_handle = reinterpret_cast<rocksdb::ColumnFamilyHandle*>(jcf_handle);
if (cf_handle != nullptr) {
bool has_exception = false;
return key_may_exist_helper(env, db, rocksdb::ReadOptions(), cf_handle,
jkey, jkey_off, jkey_len, jstring_builder,
&has_exception);
} else {
rocksdb::RocksDBExceptionJni::ThrowNew(
env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle."));
return true;
} }
return static_cast<jboolean>(exists);
} }
/* /*
* Class: org_rocksdb_RocksDB * Class: org_rocksdb_RocksDB
* Method: keyMayExist * Method: keyMayExistFoundValue
* Signature: (JJ[BIILjava/lang/StringBuilder;)Z * Signature: (JJJ[BII)[[B
*/ */
jboolean jobjectArray Java_org_rocksdb_RocksDB_keyMayExistFoundValue(
Java_org_rocksdb_RocksDB_keyMayExist__JJ_3BIILjava_lang_StringBuilder_2( JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle,
JNIEnv* env, jobject, jlong jdb_handle, jlong jread_options_handle, jlong jread_opts_handle,
jbyteArray jkey, jint jkey_off, jint jkey_len, jobject jstring_builder) { jbyteArray jkey, jint jkey_offset, jint jkey_len) {
auto* db = reinterpret_cast<rocksdb::DB*>(jdb_handle);
auto& read_options =
*reinterpret_cast<rocksdb::ReadOptions*>(jread_options_handle);
bool has_exception = false; bool has_exception = false;
return key_may_exist_helper(env, db, read_options, nullptr, jkey, jkey_off, std::string value;
jkey_len, jstring_builder, &has_exception); bool value_found = false;
}
/* const bool exists = key_may_exist_helper(
* Class: org_rocksdb_RocksDB env, jdb_handle, jcf_handle, jread_opts_handle,
* Method: keyMayExist jkey, jkey_offset, jkey_len,
* Signature: (JJ[BIIJLjava/lang/StringBuilder;)Z &has_exception, &value, &value_found);
*/
jboolean if (has_exception) {
Java_org_rocksdb_RocksDB_keyMayExist__JJ_3BIIJLjava_lang_StringBuilder_2( // java exception already raised
JNIEnv* env, jobject, jlong jdb_handle, jlong jread_options_handle, return nullptr;
jbyteArray jkey, jint jkey_off, jint jkey_len, jlong jcf_handle, }
jobject jstring_builder) {
auto* db = reinterpret_cast<rocksdb::DB*>(jdb_handle); jbyte result_flags[1];
auto& read_options = if (!exists) {
*reinterpret_cast<rocksdb::ReadOptions*>(jread_options_handle); result_flags[0] = 0;
auto* cf_handle = reinterpret_cast<rocksdb::ColumnFamilyHandle*>(jcf_handle); } else if (!value_found) {
if (cf_handle != nullptr) { result_flags[0] = 1;
bool has_exception = false;
return key_may_exist_helper(env, db, read_options, cf_handle, jkey,
jkey_off, jkey_len, jstring_builder, &has_exception);
} else { } else {
rocksdb::RocksDBExceptionJni::ThrowNew( // found
env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); result_flags[0] = 2;
return true; }
jobjectArray jresults = rocksdb::ByteJni::new2dByteArray(env, 2);
if (jresults == nullptr) {
// exception occurred
return nullptr;
} }
// prepare the result flag
jbyteArray jresult_flags = env->NewByteArray(1);
if (jresult_flags == nullptr) {
// exception thrown: OutOfMemoryError
return nullptr;
}
env->SetByteArrayRegion(jresult_flags, 0, 1, result_flags);
if (env->ExceptionCheck()) {
// exception thrown: ArrayIndexOutOfBoundsException
env->DeleteLocalRef(jresult_flags);
return nullptr;
}
env->SetObjectArrayElement(jresults, 0, jresult_flags);
if (env->ExceptionCheck()) {
// exception thrown: ArrayIndexOutOfBoundsException
env->DeleteLocalRef(jresult_flags);
return nullptr;
}
env->DeleteLocalRef(jresult_flags);
if (result_flags[0] == 2) {
// set the value
const jsize jvalue_len = static_cast<jsize>(value.size());
jbyteArray jresult_value = env->NewByteArray(jvalue_len);
if (jresult_value == nullptr) {
// exception thrown: OutOfMemoryError
return nullptr;
}
env->SetByteArrayRegion(jresult_value, 0, jvalue_len,
const_cast<jbyte*>(reinterpret_cast<const jbyte*>(value.data())));
if (env->ExceptionCheck()) {
// exception thrown: ArrayIndexOutOfBoundsException
env->DeleteLocalRef(jresult_value);
return nullptr;
}
env->SetObjectArrayElement(jresults, 1, jresult_value);
if (env->ExceptionCheck()) {
// exception thrown: ArrayIndexOutOfBoundsException
env->DeleteLocalRef(jresult_value);
return nullptr;
}
env->DeleteLocalRef(jresult_value);
}
return jresults;
} }
/* /*

@ -0,0 +1,46 @@
// Copyright (c) 2016, 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;
/**
* Simple instance reference wrapper.
*/
public class Holder<T> {
private /* @Nullable */ T value;
/**
* Constructs a new Holder with null instance.
*/
public Holder() {
}
/**
* Constructs a new Holder.
*
* @param value the instance or null
*/
public Holder(/* @Nullable */ final T value) {
this.value = value;
}
/**
* Get the instance reference.
*
* @return value the instance reference or null
*/
public /* @Nullable */ T getValue() {
return value;
}
/**
* Set the instance reference.
*
* @param value the instance reference or null
*/
public void setValue(/* @Nullable */ final T value) {
this.value = value;
}
}

@ -2189,68 +2189,94 @@ public class RocksDB extends RocksObject {
/** /**
* If the key definitely does not exist in the database, then this method * If the key definitely does not exist in the database, then this method
* returns false, else true. * returns null, else it returns an instance of KeyMayExistResult
* *
* This check is potentially lighter-weight than invoking DB::Get(). One way * If the caller wants to obtain value when the key
* to make this lighter weight is to avoid doing any IOs. * is found in memory, then {@code valueHolder} must be set.
*
* This check is potentially lighter-weight than invoking
* {@link #get(byte[])}. One way to make this lighter weight is to avoid
* doing any IOs.
* *
* @param key byte array of a key to search for * @param key byte array of a key to search for
* @param value StringBuilder instance which is a out parameter if a value is * @param valueHolder non-null to retrieve the value if it is found, or null
* found in block-cache. * if the value is not needed. If non-null, upon return of the function,
* @return boolean value indicating if key does not exist or might exist. * the {@code value} will be set if it could be retrieved.
*
* @return false if the key definitely does not exist in the database,
* otherwise true.
*/ */
public boolean keyMayExist(final byte[] key, final StringBuilder value) { public boolean keyMayExist(final byte[] key,
return keyMayExist(nativeHandle_, key, 0, key.length, value); /* @Nullable */ final Holder<byte[]> valueHolder) {
return keyMayExist(key, 0, key.length, valueHolder);
} }
/** /**
* If the key definitely does not exist in the database, then this method * If the key definitely does not exist in the database, then this method
* returns false, else true. * returns null, else it returns an instance of KeyMayExistResult
* *
* This check is potentially lighter-weight than invoking DB::Get(). One way * If the caller wants to obtain value when the key
* to make this lighter weight is to avoid doing any IOs. * is found in memory, then {@code valueHolder} must be set.
*
* This check is potentially lighter-weight than invoking
* {@link #get(byte[], int, int)}. One way to make this lighter weight is to
* avoid doing any IOs.
* *
* @param key byte array of a key to search for * @param key byte array of a key to search for
* @param offset the offset of the "key" array to be used, must be * @param offset the offset of the "key" array to be used, must be
* non-negative and no larger than "key".length * non-negative and no larger than "key".length
* @param len the length of the "key" array to be used, must be non-negative * @param len the length of the "key" array to be used, must be non-negative
* and no larger than "key".length * and no larger than "key".length
* @param value StringBuilder instance which is a out parameter if a value is * @param valueHolder non-null to retrieve the value if it is found, or null
* found in block-cache. * if the value is not needed. If non-null, upon return of the function,
* the {@code value} will be set if it could be retrieved.
* *
* @return boolean value indicating if key does not exist or might exist. * @return false if the key definitely does not exist in the database,
* otherwise true.
*/ */
public boolean keyMayExist(final byte[] key, final int offset, final int len, public boolean keyMayExist(final byte[] key,
final StringBuilder value) { final int offset, final int len,
checkBounds(offset, len, key.length); /* @Nullable */ final Holder<byte[]> valueHolder) {
return keyMayExist(nativeHandle_, key, offset, len, value); return keyMayExist((ColumnFamilyHandle)null, key, offset, len, valueHolder);
} }
/** /**
* If the key definitely does not exist in the database, then this method * If the key definitely does not exist in the database, then this method
* returns false, else true. * returns null, else it returns an instance of KeyMayExistResult
* *
* This check is potentially lighter-weight than invoking DB::Get(). One way * If the caller wants to obtain value when the key
* to make this lighter weight is to avoid doing any IOs. * is found in memory, then {@code valueHolder} must be set.
*
* This check is potentially lighter-weight than invoking
* {@link #get(ColumnFamilyHandle,byte[])}. One way to make this lighter
* weight is to avoid doing any IOs.
* *
* @param columnFamilyHandle {@link ColumnFamilyHandle} instance * @param columnFamilyHandle {@link ColumnFamilyHandle} instance
* @param key byte array of a key to search for * @param key byte array of a key to search for
* @param value StringBuilder instance which is a out parameter if a value is * @param valueHolder non-null to retrieve the value if it is found, or null
* found in block-cache. * if the value is not needed. If non-null, upon return of the function,
* @return boolean value indicating if key does not exist or might exist. * the {@code value} will be set if it could be retrieved.
*
* @return false if the key definitely does not exist in the database,
* otherwise true.
*/ */
public boolean keyMayExist(final ColumnFamilyHandle columnFamilyHandle, public boolean keyMayExist(
final byte[] key, final StringBuilder value) { final ColumnFamilyHandle columnFamilyHandle, final byte[] key,
return keyMayExist(nativeHandle_, key, 0, key.length, /* @Nullable */ final Holder<byte[]> valueHolder) {
columnFamilyHandle.nativeHandle_, value); return keyMayExist(columnFamilyHandle, key, 0, key.length,
valueHolder);
} }
/** /**
* If the key definitely does not exist in the database, then this method * If the key definitely does not exist in the database, then this method
* returns false, else true. * returns null, else it returns an instance of KeyMayExistResult
*
* If the caller wants to obtain value when the key
* is found in memory, then {@code valueHolder} must be set.
* *
* This check is potentially lighter-weight than invoking DB::Get(). One way * This check is potentially lighter-weight than invoking
* to make this lighter weight is to avoid doing any IOs. * {@link #get(ColumnFamilyHandle, byte[], int, int)}. One way to make this
* lighter weight is to avoid doing any IOs.
* *
* @param columnFamilyHandle {@link ColumnFamilyHandle} instance * @param columnFamilyHandle {@link ColumnFamilyHandle} instance
* @param key byte array of a key to search for * @param key byte array of a key to search for
@ -2258,42 +2284,58 @@ public class RocksDB extends RocksObject {
* non-negative and no larger than "key".length * non-negative and no larger than "key".length
* @param len the length of the "key" array to be used, must be non-negative * @param len the length of the "key" array to be used, must be non-negative
* and no larger than "key".length * and no larger than "key".length
* @param value StringBuilder instance which is a out parameter if a value is * @param valueHolder non-null to retrieve the value if it is found, or null
* found in block-cache. * if the value is not needed. If non-null, upon return of the function,
* @return boolean value indicating if key does not exist or might exist. * the {@code value} will be set if it could be retrieved.
*
* @return false if the key definitely does not exist in the database,
* otherwise true.
*/ */
public boolean keyMayExist(final ColumnFamilyHandle columnFamilyHandle, public boolean keyMayExist(
final byte[] key, int offset, int len, final StringBuilder value) { final ColumnFamilyHandle columnFamilyHandle,
checkBounds(offset, len, key.length); final byte[] key, int offset, int len,
return keyMayExist(nativeHandle_, key, offset, len, /* @Nullable */ final Holder<byte[]> valueHolder) {
columnFamilyHandle.nativeHandle_, value); return keyMayExist(columnFamilyHandle, null, key, offset, len,
valueHolder);
} }
/** /**
* If the key definitely does not exist in the database, then this method * If the key definitely does not exist in the database, then this method
* returns false, else true. * returns null, else it returns an instance of KeyMayExistResult
* *
* This check is potentially lighter-weight than invoking DB::Get(). One way * If the caller wants to obtain value when the key
* to make this lighter weight is to avoid doing any IOs. * is found in memory, then {@code valueHolder} must be set.
*
* This check is potentially lighter-weight than invoking
* {@link #get(ReadOptions, byte[])}. One way to make this
* lighter weight is to avoid doing any IOs.
* *
* @param readOptions {@link ReadOptions} instance * @param readOptions {@link ReadOptions} instance
* @param key byte array of a key to search for * @param key byte array of a key to search for
* @param value StringBuilder instance which is a out parameter if a value is * @param valueHolder non-null to retrieve the value if it is found, or null
* found in block-cache. * if the value is not needed. If non-null, upon return of the function,
* @return boolean value indicating if key does not exist or might exist. * the {@code value} will be set if it could be retrieved.
*
* @return false if the key definitely does not exist in the database,
* otherwise true.
*/ */
public boolean keyMayExist(final ReadOptions readOptions, public boolean keyMayExist(
final byte[] key, final StringBuilder value) { final ReadOptions readOptions, final byte[] key,
return keyMayExist(nativeHandle_, readOptions.nativeHandle_, /* @Nullable */ final Holder<byte[]> valueHolder) {
key, 0, key.length, value); return keyMayExist(readOptions, key, 0, key.length,
valueHolder);
} }
/** /**
* If the key definitely does not exist in the database, then this method * If the key definitely does not exist in the database, then this method
* returns false, else true. * returns null, else it returns an instance of KeyMayExistResult
* *
* This check is potentially lighter-weight than invoking DB::Get(). One way * If the caller wants to obtain value when the key
* to make this lighter weight is to avoid doing any IOs. * is found in memory, then {@code valueHolder} must be set.
*
* This check is potentially lighter-weight than invoking
* {@link #get(ReadOptions, byte[], int, int)}. One way to make this
* lighter weight is to avoid doing any IOs.
* *
* @param readOptions {@link ReadOptions} instance * @param readOptions {@link ReadOptions} instance
* @param key byte array of a key to search for * @param key byte array of a key to search for
@ -2301,65 +2343,103 @@ public class RocksDB extends RocksObject {
* non-negative and no larger than "key".length * non-negative and no larger than "key".length
* @param len the length of the "key" array to be used, must be non-negative * @param len the length of the "key" array to be used, must be non-negative
* and no larger than "key".length * and no larger than "key".length
* @param value StringBuilder instance which is a out parameter if a value is * @param valueHolder non-null to retrieve the value if it is found, or null
* found in block-cache. * if the value is not needed. If non-null, upon return of the function,
* @return boolean value indicating if key does not exist or might exist. * the {@code value} will be set if it could be retrieved.
*
* @return false if the key definitely does not exist in the database,
* otherwise true.
*/ */
public boolean keyMayExist(final ReadOptions readOptions, public boolean keyMayExist(
final ReadOptions readOptions,
final byte[] key, final int offset, final int len, final byte[] key, final int offset, final int len,
final StringBuilder value) { /* @Nullable */ final Holder<byte[]> valueHolder) {
checkBounds(offset, len, key.length); return keyMayExist(null, readOptions,
return keyMayExist(nativeHandle_, readOptions.nativeHandle_, key, offset, len, valueHolder);
key, offset, len, value);
} }
/** /**
* If the key definitely does not exist in the database, then this method * If the key definitely does not exist in the database, then this method
* returns false, else true. * returns null, else it returns an instance of KeyMayExistResult
* *
* This check is potentially lighter-weight than invoking DB::Get(). One way * If the caller wants to obtain value when the key
* to make this lighter weight is to avoid doing any IOs. * is found in memory, then {@code valueHolder} must be set.
*
* This check is potentially lighter-weight than invoking
* {@link #get(ColumnFamilyHandle, ReadOptions, byte[])}. One way to make this
* lighter weight is to avoid doing any IOs.
* *
* @param readOptions {@link ReadOptions} instance
* @param columnFamilyHandle {@link ColumnFamilyHandle} instance * @param columnFamilyHandle {@link ColumnFamilyHandle} instance
* @param readOptions {@link ReadOptions} instance
* @param key byte array of a key to search for * @param key byte array of a key to search for
* @param value StringBuilder instance which is a out parameter if a value is * @param valueHolder non-null to retrieve the value if it is found, or null
* found in block-cache. * if the value is not needed. If non-null, upon return of the function,
* @return boolean value indicating if key does not exist or might exist. * the {@code value} will be set if it could be retrieved.
*
* @return false if the key definitely does not exist in the database,
* otherwise true.
*/ */
public boolean keyMayExist(final ReadOptions readOptions, public boolean keyMayExist(
final ColumnFamilyHandle columnFamilyHandle, final byte[] key, final ColumnFamilyHandle columnFamilyHandle,
final StringBuilder value) { final ReadOptions readOptions, final byte[] key,
return keyMayExist(nativeHandle_, readOptions.nativeHandle_, /* @Nullable */ final Holder<byte[]> valueHolder) {
key, 0, key.length, columnFamilyHandle.nativeHandle_, return keyMayExist(columnFamilyHandle, readOptions,
value); key, 0, key.length, valueHolder);
} }
/** /**
* If the key definitely does not exist in the database, then this method * If the key definitely does not exist in the database, then this method
* returns false, else true. * returns null, else it returns an instance of KeyMayExistResult
* *
* This check is potentially lighter-weight than invoking DB::Get(). One way * If the caller wants to obtain value when the key
* to make this lighter weight is to avoid doing any IOs. * is found in memory, then {@code valueHolder} must be set.
*
* This check is potentially lighter-weight than invoking
* {@link #get(ColumnFamilyHandle, ReadOptions, byte[], int, int)}.
* One way to make this lighter weight is to avoid doing any IOs.
* *
* @param readOptions {@link ReadOptions} instance
* @param columnFamilyHandle {@link ColumnFamilyHandle} instance * @param columnFamilyHandle {@link ColumnFamilyHandle} instance
* @param readOptions {@link ReadOptions} instance
* @param key byte array of a key to search for * @param key byte array of a key to search for
* @param offset the offset of the "key" array to be used, must be * @param offset the offset of the "key" array to be used, must be
* non-negative and no larger than "key".length * non-negative and no larger than "key".length
* @param len the length of the "key" array to be used, must be non-negative * @param len the length of the "key" array to be used, must be non-negative
* and no larger than "key".length * and no larger than "key".length
* @param value StringBuilder instance which is a out parameter if a value is * @param valueHolder non-null to retrieve the value if it is found, or null
* found in block-cache. * if the value is not needed. If non-null, upon return of the function,
* @return boolean value indicating if key does not exist or might exist. * the {@code value} will be set if it could be retrieved.
*
* @return false if the key definitely does not exist in the database,
* otherwise true.
*/ */
public boolean keyMayExist(final ReadOptions readOptions, public boolean keyMayExist(
final ColumnFamilyHandle columnFamilyHandle, final byte[] key, final ColumnFamilyHandle columnFamilyHandle,
final int offset, final int len, final StringBuilder value) { final ReadOptions readOptions,
final byte[] key, final int offset, final int len,
/* @Nullable */ final Holder<byte[]> valueHolder) {
checkBounds(offset, len, key.length); checkBounds(offset, len, key.length);
return keyMayExist(nativeHandle_, readOptions.nativeHandle_, if (valueHolder == null) {
key, offset, len, columnFamilyHandle.nativeHandle_, return keyMayExist(nativeHandle_,
value); columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_,
readOptions == null ? 0 : readOptions.nativeHandle_,
key, offset, len);
} else {
final byte[][] result = keyMayExistFoundValue(
nativeHandle_,
columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_,
readOptions == null ? 0 : readOptions.nativeHandle_,
key, offset, len);
if (result[0][0] == 0x0) {
valueHolder.setValue(null);
return false;
} else if (result[0][0] == 0x1) {
valueHolder.setValue(null);
return true;
} else {
valueHolder.setValue(result[1]);
return true;
}
}
} }
/** /**
@ -4158,19 +4238,12 @@ public class RocksDB extends RocksObject {
private native byte[][] multiGet(final long dbHandle, final long rOptHandle, private native byte[][] multiGet(final long dbHandle, final long rOptHandle,
final byte[][] keys, final int[] keyOffsets, final int[] keyLengths, final byte[][] keys, final int[] keyOffsets, final int[] keyLengths,
final long[] columnFamilyHandles); final long[] columnFamilyHandles);
private native boolean keyMayExist(final long handle, final byte[] key, private native boolean keyMayExist(
final int keyOffset, final int keyLength, final long handle, final long cfHandle, final long readOptHandle,
final StringBuilder stringBuilder); final byte[] key, final int keyOffset, final int keyLength);
private native boolean keyMayExist(final long handle, final byte[] key, private native byte[][] keyMayExistFoundValue(
final int keyOffset, final int keyLength, final long cfHandle, final long handle, final long cfHandle, final long readOptHandle,
final StringBuilder stringBuilder); final byte[] key, final int keyOffset, final int keyLength);
private native boolean keyMayExist(final long handle,
final long optionsHandle, final byte[] key, final int keyOffset,
final int keyLength, final StringBuilder stringBuilder);
private native boolean keyMayExist(final long handle,
final long optionsHandle, final byte[] key, final int keyOffset,
final int keyLength, final long cfHandle,
final StringBuilder stringBuilder);
private native long iterator(final long handle); private native long iterator(final long handle);
private native long iterator(final long handle, final long readOptHandle); private native long iterator(final long handle, final long readOptHandle);
private native long iteratorCF(final long handle, final long cfHandle); private native long iteratorCF(final long handle, final long cfHandle);

@ -55,7 +55,8 @@ public class CompactionFilterFactoryTest {
rocksDb.compactRange(cfHandles.get(1)); rocksDb.compactRange(cfHandles.get(1));
assertThat(rocksDb.get(cfHandles.get(1), key1)).isEqualTo(value1); assertThat(rocksDb.get(cfHandles.get(1), key1)).isEqualTo(value1);
assertThat(rocksDb.keyMayExist(cfHandles.get(1), key2, new StringBuilder())).isFalse(); final boolean exists = rocksDb.keyMayExist(cfHandles.get(1), key2, null);
assertThat(exists).isFalse();
} finally { } finally {
for (final ColumnFamilyHandle cfHandle : cfHandles) { for (final ColumnFamilyHandle cfHandle : cfHandles) {
cfHandle.close(); cfHandle.close();

@ -13,6 +13,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
public class KeyMayExistTest { public class KeyMayExistTest {
@ -41,81 +42,118 @@ public class KeyMayExistTest {
try { try {
assertThat(columnFamilyHandleList.size()). assertThat(columnFamilyHandleList.size()).
isEqualTo(2); isEqualTo(2);
db.put("key".getBytes(), "value".getBytes()); db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8));
// Test without column family // Test without column family
StringBuilder retValue = new StringBuilder(); final Holder<byte[]> holder = new Holder<>();
boolean exists = db.keyMayExist("key".getBytes(), retValue); boolean exists = db.keyMayExist("key".getBytes(UTF_8), holder);
assertThat(exists).isTrue();
assertThat(holder.getValue()).isNotNull();
assertThat(new String(holder.getValue(), UTF_8)).isEqualTo("value");
exists = db.keyMayExist("key".getBytes(UTF_8), null);
assertThat(exists).isTrue(); assertThat(exists).isTrue();
assertThat(retValue.toString()).isEqualTo("value");
// Slice key // Slice key
StringBuilder builder = new StringBuilder("prefix"); final StringBuilder builder = new StringBuilder("prefix");
int offset = builder.toString().length(); final int offset = builder.toString().length();
builder.append("slice key 0"); builder.append("slice key 0");
int len = builder.toString().length() - offset; final int len = builder.toString().length() - offset;
builder.append("suffix"); builder.append("suffix");
byte[] sliceKey = builder.toString().getBytes(); final byte[] sliceKey = builder.toString().getBytes(UTF_8);
byte[] sliceValue = "slice value 0".getBytes(); final byte[] sliceValue = "slice value 0".getBytes(UTF_8);
db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length);
retValue = new StringBuilder(); exists = db.keyMayExist(sliceKey, offset, len, holder);
exists = db.keyMayExist(sliceKey, offset, len, retValue); assertThat(exists).isTrue();
assertThat(holder.getValue()).isNotNull();
assertThat(holder.getValue()).isEqualTo(sliceValue);
exists = db.keyMayExist(sliceKey, offset, len, null);
assertThat(exists).isTrue(); assertThat(exists).isTrue();
assertThat(retValue.toString().getBytes()).isEqualTo(sliceValue);
// Test without column family but with readOptions // Test without column family but with readOptions
try (final ReadOptions readOptions = new ReadOptions()) { try (final ReadOptions readOptions = new ReadOptions()) {
retValue = new StringBuilder(); exists = db.keyMayExist(readOptions, "key".getBytes(UTF_8), holder);
exists = db.keyMayExist(readOptions, "key".getBytes(), retValue); assertThat(exists).isTrue();
assertThat(holder.getValue()).isNotNull();
assertThat(new String(holder.getValue(), UTF_8)).isEqualTo("value");
exists = db.keyMayExist(readOptions, "key".getBytes(UTF_8), null);
assertThat(exists).isTrue(); assertThat(exists).isTrue();
assertThat(retValue.toString()).isEqualTo("value");
retValue = new StringBuilder(); exists = db.keyMayExist(readOptions, sliceKey, offset, len, holder);
exists = db.keyMayExist(readOptions, sliceKey, offset, len, retValue); assertThat(exists).isTrue();
assertThat(holder.getValue()).isNotNull();
assertThat(holder.getValue()).isEqualTo(sliceValue);
exists = db.keyMayExist(readOptions, sliceKey, offset, len, null);
assertThat(exists).isTrue(); assertThat(exists).isTrue();
assertThat(retValue.toString().getBytes()).isEqualTo(sliceValue);
} }
// Test with column family // Test with column family
retValue = new StringBuilder(); exists = db.keyMayExist(columnFamilyHandleList.get(0), "key".getBytes(UTF_8),
exists = db.keyMayExist(columnFamilyHandleList.get(0), "key".getBytes(), holder);
retValue); assertThat(exists).isTrue();
assertThat(holder.getValue()).isNotNull();
assertThat(new String(holder.getValue(), UTF_8)).isEqualTo("value");
exists = db.keyMayExist(columnFamilyHandleList.get(0), "key".getBytes(UTF_8),
null);
assertThat(exists).isTrue(); assertThat(exists).isTrue();
assertThat(retValue.toString()).isEqualTo("value");
// Test slice sky with column family // Test slice sky with column family
retValue = new StringBuilder();
exists = db.keyMayExist(columnFamilyHandleList.get(0), sliceKey, offset, len, exists = db.keyMayExist(columnFamilyHandleList.get(0), sliceKey, offset, len,
retValue); holder);
assertThat(exists).isTrue();
assertThat(holder.getValue()).isNotNull();
assertThat(holder.getValue()).isEqualTo(sliceValue);
exists = db.keyMayExist(columnFamilyHandleList.get(0), sliceKey, offset, len,
null);
assertThat(exists).isTrue(); assertThat(exists).isTrue();
assertThat(retValue.toString().getBytes()).isEqualTo(sliceValue);
// Test with column family and readOptions // Test with column family and readOptions
try (final ReadOptions readOptions = new ReadOptions()) { try (final ReadOptions readOptions = new ReadOptions()) {
retValue = new StringBuilder(); exists = db.keyMayExist(columnFamilyHandleList.get(0), readOptions,
exists = db.keyMayExist(readOptions, "key".getBytes(UTF_8), holder);
columnFamilyHandleList.get(0), "key".getBytes(), assertThat(exists).isTrue();
retValue); assertThat(holder.getValue()).isNotNull();
assertThat(new String(holder.getValue(), UTF_8)).isEqualTo("value");
exists = db.keyMayExist(columnFamilyHandleList.get(0), readOptions,
"key".getBytes(UTF_8), null);
assertThat(exists).isTrue(); assertThat(exists).isTrue();
assertThat(retValue.toString()).isEqualTo("value");
// Test slice key with column family and read options // Test slice key with column family and read options
retValue = new StringBuilder(); exists = db.keyMayExist(columnFamilyHandleList.get(0), readOptions,
exists = db.keyMayExist(readOptions, sliceKey, offset, len, holder);
columnFamilyHandleList.get(0), sliceKey, offset, len, assertThat(exists).isTrue();
retValue); assertThat(holder.getValue()).isNotNull();
assertThat(holder.getValue()).isEqualTo(sliceValue);
exists = db.keyMayExist(columnFamilyHandleList.get(0), readOptions,
sliceKey, offset, len, null);
assertThat(exists).isTrue(); assertThat(exists).isTrue();
assertThat(retValue.toString().getBytes()).isEqualTo(sliceValue);
} }
// KeyMayExist in CF1 must return false // KeyMayExist in CF1 must return null value
assertThat(db.keyMayExist(columnFamilyHandleList.get(1), exists = db.keyMayExist(columnFamilyHandleList.get(1),
"key".getBytes(), retValue)).isFalse(); "key".getBytes(UTF_8), holder);
assertThat(exists).isFalse();
assertThat(holder.getValue()).isNull();
exists = db.keyMayExist(columnFamilyHandleList.get(1),
"key".getBytes(UTF_8), null);
assertThat(exists).isFalse();
// slice key // slice key
assertThat(db.keyMayExist(columnFamilyHandleList.get(1), exists = db.keyMayExist(columnFamilyHandleList.get(1),
sliceKey, 1, 3, retValue)).isFalse(); sliceKey, 1, 3, holder);
assertThat(exists).isFalse();
assertThat(holder.getValue()).isNull();
exists = db.keyMayExist(columnFamilyHandleList.get(1),
sliceKey, 1, 3, null);
assertThat(exists).isFalse();
} finally { } finally {
for (final ColumnFamilyHandle columnFamilyHandle : for (final ColumnFamilyHandle columnFamilyHandle :
columnFamilyHandleList) { columnFamilyHandleList) {
@ -124,4 +162,31 @@ public class KeyMayExistTest {
} }
} }
} }
@Test
public void keyMayExistNonUnicodeString() throws RocksDBException {
try (final Options options = new Options()
.setCreateIfMissing(true)
.setCreateMissingColumnFamilies(true);
final RocksDB db = RocksDB.open(options,
dbFolder.getRoot().getAbsolutePath())) {
final byte key[] = "key".getBytes(UTF_8);
final byte value[] = { (byte)0x80 }; // invalid unicode code-point
db.put(key, value);
final byte buf[] = new byte[10];
final int read = db.get(key, buf);
assertThat(read).isEqualTo(1);
assertThat(buf).startsWith(value);
final Holder<byte[]> holder = new Holder<>();
boolean exists = db.keyMayExist("key".getBytes(UTF_8), holder);
assertThat(exists).isTrue();
assertThat(holder.getValue()).isNotNull();
assertThat(holder.getValue()).isEqualTo(value);
exists = db.keyMayExist("key".getBytes(UTF_8), null);
assertThat(exists).isTrue();
}
}
} }

Loading…
Cancel
Save