From 86cf7266c39137dfb0b23a0a8c4d0bd64f2dc6fb Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Mon, 18 Oct 2021 17:19:04 -0700 Subject: [PATCH] keyMayExist() supports ByteBuffer (#9013) Summary: closes https://github.com/facebook/rocksdb/issues/7917 Implemented ByteBuffer API variants of Java keyMayExist() uniformly with and without column families, read options and return data values. Implemented 2 supporting C++ JNI methods. Pull Request resolved: https://github.com/facebook/rocksdb/pull/9013 Reviewed By: mrambacher Differential Revision: D31665989 Pulled By: jay-zhuang fbshipit-source-id: 8adc1730217dba38d6fa7b31d788650a33e28af1 --- HISTORY.md | 1 + java/CMakeLists.txt | 1 + java/Makefile | 1 + java/rocksjni/rocksjni.cc | 155 ++++- .../main/java/org/rocksdb/KeyMayExist.java | 36 + java/src/main/java/org/rocksdb/RocksDB.java | 199 +++++- .../java/org/rocksdb/KeyMayExistTest.java | 654 +++++++++++++----- 7 files changed, 857 insertions(+), 190 deletions(-) create mode 100644 java/src/main/java/org/rocksdb/KeyMayExist.java diff --git a/HISTORY.md b/HISTORY.md index 457e41e11..ee6152d59 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -16,6 +16,7 @@ * Add remote compaction read/write bytes statistics: `REMOTE_COMPACT_READ_BYTES`, `REMOTE_COMPACT_WRITE_BYTES`. * Introduce an experimental feature to dump out the blocks from block cache and insert them to the secondary cache to reduce the cache warmup time (e.g., used while migrating DB instance). More information are in `class CacheDumper` and `CacheDumpedLoader` at `rocksdb/utilities/cache_dump_load.h` Note that, this feature is subject to the potential change in the future, it is still experimental. * Introduced a new BlobDB configuration option `blob_garbage_collection_force_threshold`, which can be used to trigger compactions targeting the SST files which reference the oldest blob files when the ratio of garbage in those blob files meets or exceeds the specified threshold. This can reduce space amplification with skewed workloads where the affected SST files might not otherwise get picked up for compaction. +* [JAVA] `keyMayExist()` supports ByteBuffer. ### Public API change * Made SystemClock extend the Customizable class and added a CreateFromString method. Implementations need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method. diff --git a/java/CMakeLists.txt b/java/CMakeLists.txt index 3ec6099fc..a92ce7832 100644 --- a/java/CMakeLists.txt +++ b/java/CMakeLists.txt @@ -167,6 +167,7 @@ set(JAVA_MAIN_CLASSES src/main/java/org/rocksdb/LevelMetaData.java src/main/java/org/rocksdb/ConcurrentTaskLimiter.java src/main/java/org/rocksdb/ConcurrentTaskLimiterImpl.java + src/main/java/org/rocksdb/KeyMayExist.java src/main/java/org/rocksdb/LiveFileMetaData.java src/main/java/org/rocksdb/LogFile.java src/main/java/org/rocksdb/Logger.java diff --git a/java/Makefile b/java/Makefile index 03ff58651..d7f478903 100644 --- a/java/Makefile +++ b/java/Makefile @@ -40,6 +40,7 @@ NATIVE_JAVA_CLASSES = \ org.rocksdb.HdfsEnv\ org.rocksdb.ConcurrentTaskLimiter\ org.rocksdb.ConcurrentTaskLimiterImpl\ + org.rocksdb.KeyMayExist\ org.rocksdb.Logger\ org.rocksdb.LRUCache\ org.rocksdb.MemoryUsageType\ diff --git a/java/rocksjni/rocksjni.cc b/java/rocksjni/rocksjni.cc index 96d4177fb..077c67be2 100644 --- a/java/rocksjni/rocksjni.cc +++ b/java/rocksjni/rocksjni.cc @@ -1929,6 +1929,49 @@ bool key_may_exist_helper(JNIEnv* env, jlong jdb_handle, jlong jcf_handle, return exists; } +bool key_may_exist_direct_helper(JNIEnv* env, jlong jdb_handle, + jlong jcf_handle, jlong jread_opts_handle, + jobject jkey, jint jkey_offset, jint jkey_len, + bool* has_exception, std::string* value, + bool* value_found) { + auto* db = reinterpret_cast(jdb_handle); + ROCKSDB_NAMESPACE::ColumnFamilyHandle* cf_handle; + if (jcf_handle == 0) { + cf_handle = db->DefaultColumnFamily(); + } else { + cf_handle = + reinterpret_cast(jcf_handle); + } + ROCKSDB_NAMESPACE::ReadOptions read_opts = + jread_opts_handle == 0 + ? ROCKSDB_NAMESPACE::ReadOptions() + : *(reinterpret_cast( + jread_opts_handle)); + + char* key = reinterpret_cast(env->GetDirectBufferAddress(jkey)); + if (key == nullptr) { + ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( + env, + "Invalid key argument (argument is not a valid direct ByteBuffer)"); + *has_exception = true; + return false; + } + if (env->GetDirectBufferCapacity(jkey) < (jkey_offset + jkey_len)) { + ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( + env, + "Invalid key argument. Capacity is less than requested region (offset " + "+ length)."); + *has_exception = true; + return false; + } + + ROCKSDB_NAMESPACE::Slice key_slice(key, jkey_len); + + const bool exists = + db->KeyMayExist(read_opts, cf_handle, key_slice, value, value_found); + + return exists; +} /* * Class: org_rocksdb_RocksDB @@ -1957,6 +2000,101 @@ jboolean Java_org_rocksdb_RocksDB_keyMayExist( return static_cast(exists); } +/* + * Class: org_rocksdb_RocksDB + * Method: keyMayExistDirect + * Signature: (JJJLjava/nio/ByteBuffer;II)Z + */ +jboolean Java_org_rocksdb_RocksDB_keyMayExistDirect( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, + jlong jread_opts_handle, jobject jkey, jint jkey_offset, jint jkey_len) { + bool has_exception = false; + std::string value; + bool value_found = false; + + const bool exists = key_may_exist_direct_helper( + env, jdb_handle, jcf_handle, jread_opts_handle, jkey, jkey_offset, + jkey_len, &has_exception, &value, &value_found); + if (has_exception) { + // java exception already raised + return false; + } + + return static_cast(exists); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: keyMayExistDirectFoundValue + * Signature: + * (JJJLjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)[J + */ +jintArray Java_org_rocksdb_RocksDB_keyMayExistDirectFoundValue( + JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, + jlong jread_opts_handle, jobject jkey, jint jkey_offset, jint jkey_len, + jobject jval, jint jval_offset, jint jval_len) { + char* val_buffer = reinterpret_cast(env->GetDirectBufferAddress(jval)); + if (val_buffer == nullptr) { + ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( + env, + "Invalid value argument (argument is not a valid direct ByteBuffer)"); + return nullptr; + } + + if (env->GetDirectBufferCapacity(jval) < (jval_offset + jval_len)) { + ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew( + env, + "Invalid value argument. Capacity is less than requested region " + "(offset + length)."); + return nullptr; + } + + bool has_exception = false; + std::string cvalue; + bool value_found = false; + + const bool exists = key_may_exist_direct_helper( + env, jdb_handle, jcf_handle, jread_opts_handle, jkey, jkey_offset, + jkey_len, &has_exception, &cvalue, &value_found); + + if (has_exception) { + // java exception already raised + return nullptr; + } + + const jint cvalue_len = static_cast(cvalue.size()); + const jint length = std::min(jval_len, cvalue_len); + memcpy(val_buffer + jval_offset, cvalue.c_str(), length); + + // keep consistent with java KeyMayExistEnum.values() + const int kNotExist = 0; + const int kExistsWithoutValue = 1; + const int kExistsWithValue = 2; + + // TODO fix return value/type + // exists/value_found/neither + // cvalue_len + jintArray jresult = env->NewIntArray(2); + const jint jexists = + exists ? (value_found ? kExistsWithValue : kExistsWithoutValue) + : kNotExist; + + env->SetIntArrayRegion(jresult, 0, 1, &jexists); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jresult); + return nullptr; + } + env->SetIntArrayRegion(jresult, 1, 1, &cvalue_len); + if (env->ExceptionCheck()) { + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jresult); + return nullptr; + } + + return jresult; +} + /* * Class: org_rocksdb_RocksDB * Method: keyMayExistFoundValue @@ -1964,17 +2102,14 @@ jboolean Java_org_rocksdb_RocksDB_keyMayExist( */ jobjectArray Java_org_rocksdb_RocksDB_keyMayExistFoundValue( JNIEnv* env, jobject, jlong jdb_handle, jlong jcf_handle, - jlong jread_opts_handle, - jbyteArray jkey, jint jkey_offset, jint jkey_len) { - + jlong jread_opts_handle, jbyteArray jkey, jint jkey_offset, jint jkey_len) { bool has_exception = false; std::string value; bool value_found = false; const bool exists = key_may_exist_helper( - env, jdb_handle, jcf_handle, jread_opts_handle, - jkey, jkey_offset, jkey_len, - &has_exception, &value, &value_found); + env, jdb_handle, jcf_handle, jread_opts_handle, jkey, jkey_offset, + jkey_len, &has_exception, &value, &value_found); if (has_exception) { // java exception already raised @@ -2009,12 +2144,12 @@ jobjectArray Java_org_rocksdb_RocksDB_keyMayExistFoundValue( env->DeleteLocalRef(jresult_flags); return nullptr; } - + env->SetObjectArrayElement(jresults, 0, jresult_flags); if (env->ExceptionCheck()) { - // exception thrown: ArrayIndexOutOfBoundsException - env->DeleteLocalRef(jresult_flags); - return nullptr; + // exception thrown: ArrayIndexOutOfBoundsException + env->DeleteLocalRef(jresult_flags); + return nullptr; } env->DeleteLocalRef(jresult_flags); diff --git a/java/src/main/java/org/rocksdb/KeyMayExist.java b/java/src/main/java/org/rocksdb/KeyMayExist.java new file mode 100644 index 000000000..dcc6e4da3 --- /dev/null +++ b/java/src/main/java/org/rocksdb/KeyMayExist.java @@ -0,0 +1,36 @@ +// 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 java.util.Objects; + +public class KeyMayExist { + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + final KeyMayExist that = (KeyMayExist) o; + return (valueLength == that.valueLength && exists == that.exists); + } + + @Override + public int hashCode() { + return Objects.hash(exists, valueLength); + } + + enum KeyMayExistEnum { kNotExist, kExistsWithoutValue, kExistsWithValue } + ; + + public KeyMayExist(final KeyMayExistEnum exists, final int valueLength) { + this.exists = exists; + this.valueLength = valueLength; + } + + final KeyMayExistEnum exists; + final int valueLength; +} diff --git a/java/src/main/java/org/rocksdb/RocksDB.java b/java/src/main/java/org/rocksdb/RocksDB.java index bec702faf..a65888fd1 100644 --- a/java/src/main/java/org/rocksdb/RocksDB.java +++ b/java/src/main/java/org/rocksdb/RocksDB.java @@ -2218,8 +2218,8 @@ public class RocksDB extends RocksObject { assert(keys.size() != 0); final byte[][] keysArray = keys.toArray(new byte[0][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; + final int[] keyOffsets = new int[keysArray.length]; + final int[] keyLengths = new int[keysArray.length]; for(int i = 0; i < keyLengths.length; i++) { keyLengths[i] = keysArray[i].length; } @@ -2278,8 +2278,8 @@ public class RocksDB extends RocksObject { } final byte[][] keysArray = keys.toArray(new byte[0][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; + final int[] keyOffsets = new int[keysArray.length]; + final int[] keyLengths = new int[keysArray.length]; for(int i = 0; i < keyLengths.length; i++) { keyLengths[i] = keysArray[i].length; } @@ -2317,8 +2317,8 @@ public class RocksDB extends RocksObject { assert(keys.size() != 0); final byte[][] keysArray = keys.toArray(new byte[0][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; + final int[] keyOffsets = new int[keysArray.length]; + final int[] keyLengths = new int[keysArray.length]; for(int i = 0; i < keyLengths.length; i++) { keyLengths[i] = keysArray[i].length; } @@ -2378,8 +2378,8 @@ public class RocksDB extends RocksObject { } final byte[][] keysArray = keys.toArray(new byte[0][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; + final int[] keyOffsets = new int[keysArray.length]; + final int[] keyLengths = new int[keysArray.length]; for(int i = 0; i < keyLengths.length; i++) { keyLengths[i] = keysArray[i].length; } @@ -2415,8 +2415,8 @@ public class RocksDB extends RocksObject { assert(keys.size() != 0); final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; + final int[] keyOffsets = new int[keysArray.length]; + final int[] keyLengths = new int[keysArray.length]; for(int i = 0; i < keyLengths.length; i++) { keyLengths[i] = keysArray[i].length; } @@ -2461,8 +2461,8 @@ public class RocksDB extends RocksObject { } final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; + final int[] keyOffsets = new int[keysArray.length]; + final int[] keyLengths = new int[keysArray.length]; for(int i = 0; i < keyLengths.length; i++) { keyLengths[i] = keysArray[i].length; } @@ -2488,8 +2488,8 @@ public class RocksDB extends RocksObject { assert(keys.size() != 0); final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; + final int[] keyOffsets = new int[keysArray.length]; + final int[] keyLengths = new int[keysArray.length]; for(int i = 0; i < keyLengths.length; i++) { keyLengths[i] = keysArray[i].length; } @@ -2534,8 +2534,8 @@ public class RocksDB extends RocksObject { } final byte[][] keysArray = keys.toArray(new byte[keys.size()][]); - final int keyOffsets[] = new int[keysArray.length]; - final int keyLengths[] = new int[keysArray.length]; + final int[] keyOffsets = new int[keysArray.length]; + final int[] keyLengths = new int[keysArray.length]; for(int i = 0; i < keyLengths.length; i++) { keyLengths[i] = keysArray[i].length; } @@ -2548,7 +2548,7 @@ public class RocksDB extends RocksObject { * If the key definitely does not exist in the database, then this method * returns false, otherwise it returns true if the key might exist. * That is to say that this method is probabilistic and may return false - * positives, but never a true negative. + * positives, but never a false negative. * * If the caller wants to obtain value when the key * is found in memory, then {@code valueHolder} must be set. @@ -2574,7 +2574,7 @@ public class RocksDB extends RocksObject { * If the key definitely does not exist in the database, then this method * returns false, otherwise it returns true if the key might exist. * That is to say that this method is probabilistic and may return false - * positives, but never a true negative. + * positives, but never a false negative. * * If the caller wants to obtain value when the key * is found in memory, then {@code valueHolder} must be set. @@ -2605,7 +2605,7 @@ public class RocksDB extends RocksObject { * If the key definitely does not exist in the database, then this method * returns false, otherwise it returns true if the key might exist. * That is to say that this method is probabilistic and may return false - * positives, but never a true negative. + * positives, but never a false negative. * * If the caller wants to obtain value when the key * is found in memory, then {@code valueHolder} must be set. @@ -2634,7 +2634,7 @@ public class RocksDB extends RocksObject { * If the key definitely does not exist in the database, then this method * returns false, otherwise it returns true if the key might exist. * That is to say that this method is probabilistic and may return false - * positives, but never a true negative. + * positives, but never a false negative. * * If the caller wants to obtain value when the key * is found in memory, then {@code valueHolder} must be set. @@ -2762,7 +2762,7 @@ public class RocksDB extends RocksObject { * If the key definitely does not exist in the database, then this method * returns false, otherwise it returns true if the key might exist. * That is to say that this method is probabilistic and may return false - * positives, but never a true negative. + * positives, but never a false negative. * * If the caller wants to obtain value when the key * is found in memory, then {@code valueHolder} must be set. @@ -2815,6 +2815,158 @@ public class RocksDB extends RocksObject { } } + /** + * If the key definitely does not exist in the database, then this method + * returns false, otherwise it returns true if the key might exist. + * That is to say that this method is probabilistic and may return false + * positives, but never a false negative. + * + * @param key bytebuffer containing the value of the key + * @return false if the key definitely does not exist in the database, + * otherwise true. + */ + public boolean keyMayExist(final ByteBuffer key) { + return keyMayExist(null, (ReadOptions) null, key); + } + + /** + * If the key definitely does not exist in the database, then this method + * returns false, otherwise it returns true if the key might exist. + * That is to say that this method is probabilistic and may return false + * positives, but never a false negative. + * + * @param columnFamilyHandle the {@link ColumnFamilyHandle} to look for the key in + * @param key bytebuffer containing the value of the key + * @return false if the key definitely does not exist in the database, + * otherwise true. + */ + public boolean keyMayExist(final ColumnFamilyHandle columnFamilyHandle, final ByteBuffer key) { + return keyMayExist(columnFamilyHandle, (ReadOptions) null, key); + } + + /** + * If the key definitely does not exist in the database, then this method + * returns false, otherwise it returns true if the key might exist. + * That is to say that this method is probabilistic and may return false + * positives, but never a false negative. + * + * @param readOptions the {@link ReadOptions} to use when reading the key/value + * @param key bytebuffer containing the value of the key + * @return false if the key definitely does not exist in the database, + * otherwise true. + */ + public boolean keyMayExist(final ReadOptions readOptions, final ByteBuffer key) { + return keyMayExist(null, readOptions, key); + } + + /** + * If the key definitely does not exist in the database, then this method + * returns {@link KeyMayExist.KeyMayExistEnum#kNotExist}, + * otherwise if it can with best effort retreive the value, it returns {@link + * KeyMayExist.KeyMayExistEnum#kExistsWithValue} otherwise it returns {@link + * KeyMayExist.KeyMayExistEnum#kExistsWithoutValue}. The choice not to return a value which might + * exist is at the discretion of the implementation; the only guarantee is that {@link + * KeyMayExist.KeyMayExistEnum#kNotExist} is an assurance that the key does not exist. + * + * @param key bytebuffer containing the value of the key + * @param value bytebuffer which will receive a value if the key exists and a value is known + * @return a {@link KeyMayExist} object reporting if key may exist and if a value is provided + */ + public KeyMayExist keyMayExist(final ByteBuffer key, final ByteBuffer value) { + return keyMayExist(null, null, key, value); + } + + /** + * If the key definitely does not exist in the database, then this method + * returns {@link KeyMayExist.KeyMayExistEnum#kNotExist}, + * otherwise if it can with best effort retreive the value, it returns {@link + * KeyMayExist.KeyMayExistEnum#kExistsWithValue} otherwise it returns {@link + * KeyMayExist.KeyMayExistEnum#kExistsWithoutValue}. The choice not to return a value which might + * exist is at the discretion of the implementation; the only guarantee is that {@link + * KeyMayExist.KeyMayExistEnum#kNotExist} is an assurance that the key does not exist. + * + * @param columnFamilyHandle the {@link ColumnFamilyHandle} to look for the key in + * @param key bytebuffer containing the value of the key + * @param value bytebuffer which will receive a value if the key exists and a value is known + * @return a {@link KeyMayExist} object reporting if key may exist and if a value is provided + */ + public KeyMayExist keyMayExist( + final ColumnFamilyHandle columnFamilyHandle, final ByteBuffer key, final ByteBuffer value) { + return keyMayExist(columnFamilyHandle, null, key, value); + } + + /** + * If the key definitely does not exist in the database, then this method + * returns {@link KeyMayExist.KeyMayExistEnum#kNotExist}, + * otherwise if it can with best effort retreive the value, it returns {@link + * KeyMayExist.KeyMayExistEnum#kExistsWithValue} otherwise it returns {@link + * KeyMayExist.KeyMayExistEnum#kExistsWithoutValue}. The choice not to return a value which might + * exist is at the discretion of the implementation; the only guarantee is that {@link + * KeyMayExist.KeyMayExistEnum#kNotExist} is an assurance that the key does not exist. + * + * @param readOptions the {@link ReadOptions} to use when reading the key/value + * @param key bytebuffer containing the value of the key + * @param value bytebuffer which will receive a value if the key exists and a value is known + * @return a {@link KeyMayExist} object reporting if key may exist and if a value is provided + */ + public KeyMayExist keyMayExist( + final ReadOptions readOptions, final ByteBuffer key, final ByteBuffer value) { + return keyMayExist(null, readOptions, key, value); + } + + /** + * If the key definitely does not exist in the database, then this method + * returns false, otherwise it returns true if the key might exist. + * That is to say that this method is probabilistic and may return false + * positives, but never a false negative. + * + * @param columnFamilyHandle + * @param readOptions + * @param key + * @return + */ + public boolean keyMayExist(final ColumnFamilyHandle columnFamilyHandle, + final ReadOptions readOptions, final ByteBuffer key) { + assert key != null : "key ByteBuffer parameter cannot be null"; + assert key.isDirect() : "key parameter must be a direct ByteBuffer"; + return keyMayExistDirect(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, + readOptions == null ? 0 : readOptions.nativeHandle_, key, key.position(), key.limit()); + } + + /** + * If the key definitely does not exist in the database, then this method + * returns {@link KeyMayExist.KeyMayExistEnum#kNotExist}, + * otherwise if it can with best effort retreive the value, it returns {@link + * KeyMayExist.KeyMayExistEnum#kExistsWithValue} otherwise it returns {@link + * KeyMayExist.KeyMayExistEnum#kExistsWithoutValue}. The choice not to return a value which might + * exist is at the discretion of the implementation; the only guarantee is that {@link + * KeyMayExist.KeyMayExistEnum#kNotExist} is an assurance that the key does not exist. + * + * @param columnFamilyHandle the {@link ColumnFamilyHandle} to look for the key in + * @param readOptions the {@link ReadOptions} to use when reading the key/value + * @param key bytebuffer containing the value of the key + * @param value bytebuffer which will receive a value if the key exists and a value is known + * @return a {@link KeyMayExist} object reporting if key may exist and if a value is provided + */ + public KeyMayExist keyMayExist(final ColumnFamilyHandle columnFamilyHandle, + final ReadOptions readOptions, final ByteBuffer key, final ByteBuffer value) { + assert key != null : "key ByteBuffer parameter cannot be null"; + assert key.isDirect() : "key parameter must be a direct ByteBuffer"; + assert value + != null + : "value ByteBuffer parameter cannot be null. If you do not need the value, use a different version of the method"; + assert value.isDirect() : "value parameter must be a direct ByteBuffer"; + + final int[] result = keyMayExistDirectFoundValue(nativeHandle_, + columnFamilyHandle == null ? 0 : columnFamilyHandle.nativeHandle_, + readOptions == null ? 0 : readOptions.nativeHandle_, key, key.position(), key.remaining(), + value, value.position(), value.remaining()); + final int valueLength = result[1]; + value.limit(value.position() + Math.min(valueLength, value.remaining())); + return new KeyMayExist(KeyMayExist.KeyMayExistEnum.values()[result[0]], valueLength); + } + /** *

Return a heap-allocated iterator over the contents of the * database. The result of newIterator() is initially invalid @@ -4674,6 +4826,11 @@ public class RocksDB extends RocksObject { private native int getDirect(long handle, long readOptHandle, ByteBuffer key, int keyOffset, int keyLength, ByteBuffer value, int valueOffset, int valueLength, long cfHandle) throws RocksDBException; + private native boolean keyMayExistDirect(final long handle, final long cfHhandle, + final long readOptHandle, final ByteBuffer key, final int keyOffset, final int keyLength); + private native int[] keyMayExistDirectFoundValue(final long handle, final long cfHhandle, + final long readOptHandle, final ByteBuffer key, final int keyOffset, final int keyLength, + final ByteBuffer value, final int valueOffset, final int valueLength); private native void deleteDirect(long handle, long optHandle, ByteBuffer key, int keyOffset, int keyLength, long cfHandle) throws RocksDBException; private native long getLongProperty(final long nativeHandle, diff --git a/java/src/test/java/org/rocksdb/KeyMayExistTest.java b/java/src/test/java/org/rocksdb/KeyMayExistTest.java index 45b06be35..3f3bec6ba 100644 --- a/java/src/test/java/org/rocksdb/KeyMayExistTest.java +++ b/java/src/test/java/org/rocksdb/KeyMayExistTest.java @@ -4,20 +4,19 @@ // (found in the LICENSE.Apache file in the root directory). package org.rocksdb; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.*; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; public class KeyMayExistTest { - @ClassRule public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE = new RocksNativeLibraryResource(); @@ -25,168 +24,505 @@ public class KeyMayExistTest { @Rule public TemporaryFolder dbFolder = new TemporaryFolder(); + @Rule public ExpectedException exceptionRule = ExpectedException.none(); + + List cfDescriptors; + List columnFamilyHandleList = new ArrayList<>(); + RocksDB db; + + // Slice key + int offset; + int len; + + byte[] sliceKey; + byte[] sliceValue; + + @Before + public void before() throws RocksDBException { + cfDescriptors = Arrays.asList(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), + new ColumnFamilyDescriptor("new_cf".getBytes())); + final DBOptions options = + new DBOptions().setCreateIfMissing(true).setCreateMissingColumnFamilies(true); + + db = RocksDB.open( + options, dbFolder.getRoot().getAbsolutePath(), cfDescriptors, columnFamilyHandleList); + + // Build the slice key + final StringBuilder builder = new StringBuilder("prefix"); + offset = builder.toString().length(); + builder.append("slice key 0"); + len = builder.toString().length() - offset; + builder.append("suffix"); + sliceKey = builder.toString().getBytes(UTF_8); + sliceValue = "slice value 0".getBytes(UTF_8); + } + + @After + public void after() { + for (final ColumnFamilyHandle columnFamilyHandle : columnFamilyHandleList) { + columnFamilyHandle.close(); + } + db.close(); + } + @Test public void keyMayExist() throws RocksDBException { - final List cfDescriptors = Arrays.asList( - new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY), - new ColumnFamilyDescriptor("new_cf".getBytes()) - ); - - final List columnFamilyHandleList = new ArrayList<>(); - try (final DBOptions options = new DBOptions() - .setCreateIfMissing(true) - .setCreateMissingColumnFamilies(true); - final RocksDB db = RocksDB.open(options, - dbFolder.getRoot().getAbsolutePath(), - cfDescriptors, columnFamilyHandleList)) { - try { - assertThat(columnFamilyHandleList.size()). - isEqualTo(2); - db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); - // Test without column family - final Holder holder = new Holder<>(); - 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(); - - // Slice key - final StringBuilder builder = new StringBuilder("prefix"); - final int offset = builder.toString().length(); - builder.append("slice key 0"); - final int len = builder.toString().length() - offset; - builder.append("suffix"); - - final byte[] sliceKey = builder.toString().getBytes(UTF_8); - final byte[] sliceValue = "slice value 0".getBytes(UTF_8); - db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); - - exists = db.keyMayExist(sliceKey, offset, len, holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(holder.getValue()).isEqualTo(sliceValue); - - exists = db.keyMayExist(sliceKey, offset, len, null); - assertThat(exists).isTrue(); - - // Test without column family but with readOptions - try (final ReadOptions readOptions = new ReadOptions()) { - exists = db.keyMayExist(readOptions, "key".getBytes(UTF_8), holder); - 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(); - - exists = db.keyMayExist(readOptions, sliceKey, offset, len, holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(holder.getValue()).isEqualTo(sliceValue); - - exists = db.keyMayExist(readOptions, sliceKey, offset, len, null); - assertThat(exists).isTrue(); - } - - // Test with column family - exists = db.keyMayExist(columnFamilyHandleList.get(0), "key".getBytes(UTF_8), - holder); - 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(); - - // Test slice sky with column family - exists = db.keyMayExist(columnFamilyHandleList.get(0), sliceKey, offset, len, - 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(); - - // Test with column family and readOptions - try (final ReadOptions readOptions = new ReadOptions()) { - exists = db.keyMayExist(columnFamilyHandleList.get(0), readOptions, - "key".getBytes(UTF_8), holder); - assertThat(exists).isTrue(); - 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(); - - // Test slice key with column family and read options - exists = db.keyMayExist(columnFamilyHandleList.get(0), readOptions, - sliceKey, offset, len, holder); - assertThat(exists).isTrue(); - assertThat(holder.getValue()).isNotNull(); - assertThat(holder.getValue()).isEqualTo(sliceValue); - - exists = db.keyMayExist(columnFamilyHandleList.get(0), readOptions, - sliceKey, offset, len, null); - assertThat(exists).isTrue(); - } - - // KeyMayExist in CF1 must return null value - exists = db.keyMayExist(columnFamilyHandleList.get(1), - "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 - exists = db.keyMayExist(columnFamilyHandleList.get(1), - 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 { - for (final ColumnFamilyHandle columnFamilyHandle : - columnFamilyHandleList) { - columnFamilyHandle.close(); - } - } - } + assertThat(columnFamilyHandleList.size()).isEqualTo(2); + + // Standard key + db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); + + // Test without column family + final Holder holder = new Holder<>(); + 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(); } @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); + public void keyMayExistReadOptions() throws RocksDBException { + // Test without column family but with readOptions + try (final ReadOptions readOptions = new ReadOptions()) { + // Standard key + db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); + + // Slice key + db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); final Holder holder = new Holder<>(); - boolean exists = db.keyMayExist("key".getBytes(UTF_8), holder); + boolean exists = db.keyMayExist(readOptions, "key".getBytes(UTF_8), holder); + 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(); + + exists = db.keyMayExist(readOptions, sliceKey, offset, len, holder); assertThat(exists).isTrue(); assertThat(holder.getValue()).isNotNull(); - assertThat(holder.getValue()).isEqualTo(value); + assertThat(holder.getValue()).isEqualTo(sliceValue); - exists = db.keyMayExist("key".getBytes(UTF_8), null); + exists = db.keyMayExist(readOptions, sliceKey, offset, len, null); assertThat(exists).isTrue(); } } + + @Test + public void keyMayExistColumnFamily() throws RocksDBException { + // Standard key + db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); + + // Slice key + db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); + + // Test slice key with column family + final Holder holder = new Holder<>(); + boolean exists = db.keyMayExist(columnFamilyHandleList.get(0), sliceKey, offset, len, 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(); + } + + @Test + public void keyMayExistColumnFamilyReadOptions() throws RocksDBException { + // Standard key + db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); + + // Slice key + db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); + + // Test slice key with column family and read options + final Holder holder = new Holder<>(); + try (final ReadOptions readOptions = new ReadOptions()) { + boolean exists = + db.keyMayExist(columnFamilyHandleList.get(0), readOptions, "key".getBytes(UTF_8), holder); + assertThat(exists).isTrue(); + 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(); + + // Test slice key with column family and read options + exists = + db.keyMayExist(columnFamilyHandleList.get(0), readOptions, sliceKey, offset, len, holder); + assertThat(exists).isTrue(); + assertThat(holder.getValue()).isNotNull(); + assertThat(holder.getValue()).isEqualTo(sliceValue); + + exists = + db.keyMayExist(columnFamilyHandleList.get(0), readOptions, sliceKey, offset, len, null); + assertThat(exists).isTrue(); + } + } + + @Test + public void keyMayExistSliceKey() throws RocksDBException { + assertThat(columnFamilyHandleList.size()).isEqualTo(2); + + // Standard key + db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); + + // Slice key + db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); + + final Holder holder = new Holder<>(); + boolean exists = db.keyMayExist(sliceKey, offset, len, holder); + assertThat(exists).isTrue(); + assertThat(holder.getValue()).isNotNull(); + assertThat(holder.getValue()).isEqualTo(sliceValue); + + exists = db.keyMayExist(sliceKey, offset, len, null); + assertThat(exists).isTrue(); + + exists = db.keyMayExist("slice key".getBytes(UTF_8), null); + assertThat(exists).isFalse(); + + exists = db.keyMayExist("slice key 0".getBytes(UTF_8), null); + assertThat(exists).isTrue(); + + // Test with column family + exists = db.keyMayExist(columnFamilyHandleList.get(0), "key".getBytes(UTF_8), holder); + 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(); + + // KeyMayExist in CF1 must return null value + exists = db.keyMayExist(columnFamilyHandleList.get(1), "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 + exists = db.keyMayExist(columnFamilyHandleList.get(1), sliceKey, 1, 3, holder); + assertThat(exists).isFalse(); + assertThat(holder.getValue()).isNull(); + exists = db.keyMayExist(columnFamilyHandleList.get(1), sliceKey, 1, 3, null); + assertThat(exists).isFalse(); + } + + @Test + public void keyMayExistCF1() throws RocksDBException { + // Standard key + db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); + + // Slice key + db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); + + // KeyMayExist in CF1 must return null value + final Holder holder = new Holder<>(); + boolean exists = db.keyMayExist(columnFamilyHandleList.get(1), "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(); + } + + @Test + public void keyMayExistCF1Slice() throws RocksDBException { + // Standard key + db.put("key".getBytes(UTF_8), "value".getBytes(UTF_8)); + + // Slice key + db.put(sliceKey, offset, len, sliceValue, 0, sliceValue.length); + + // slice key + final Holder holder = new Holder<>(); + boolean exists = db.keyMayExist(columnFamilyHandleList.get(1), sliceKey, 1, 3, holder); + assertThat(exists).isFalse(); + assertThat(holder.getValue()).isNull(); + exists = db.keyMayExist(columnFamilyHandleList.get(1), sliceKey, 1, 3, null); + assertThat(exists).isFalse(); + } + + @Test + public void keyMayExistBB() throws RocksDBException { + // Standard key + db.put("keyBB".getBytes(UTF_8), "valueBB".getBytes(UTF_8)); + + final byte[] key = "keyBB".getBytes(UTF_8); + final byte[] value = "valueBB".getBytes(UTF_8); + + final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); + keyBuffer.put(key, 0, key.length); + keyBuffer.flip(); + + assertThat(db.keyMayExist(keyBuffer)).isEqualTo(true); + + final ByteBuffer valueBuffer = ByteBuffer.allocateDirect(value.length + 24); + valueBuffer.position(12); + KeyMayExist keyMayExist = db.keyMayExist(keyBuffer, valueBuffer); + assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); + assertThat(keyMayExist.valueLength).isEqualTo(value.length); + assertThat(valueBuffer.position()).isEqualTo(12); + assertThat(valueBuffer.limit()).isEqualTo(12 + value.length); + byte[] valueGet = new byte[value.length]; + valueBuffer.get(valueGet); + assertThat(valueGet).isEqualTo(value); + + valueBuffer.limit(value.length + 24); + valueBuffer.position(25); + keyMayExist = db.keyMayExist(keyBuffer, valueBuffer); + assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); + assertThat(keyMayExist.valueLength).isEqualTo(value.length); + assertThat(valueBuffer.position()).isEqualTo(25); + assertThat(valueBuffer.limit()).isEqualTo(24 + value.length); + valueGet = new byte[value.length - 1]; + valueBuffer.get(valueGet); + assertThat(valueGet).isEqualTo(Arrays.copyOfRange(value, 0, value.length - 1)); + + exceptionRule.expect(BufferUnderflowException.class); + valueGet = new byte[value.length]; + valueBuffer.get(valueGet); + } + + @Test + public void keyMayExistBBReadOptions() throws RocksDBException { + // Standard key + db.put("keyBB".getBytes(UTF_8), "valueBB".getBytes(UTF_8)); + + final byte[] key = "keyBB".getBytes(UTF_8); + final byte[] value = "valueBB".getBytes(UTF_8); + + final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); + keyBuffer.put(key, 0, key.length); + keyBuffer.flip(); + + try (final ReadOptions readOptions = new ReadOptions()) { + assertThat(db.keyMayExist(readOptions, keyBuffer)).isEqualTo(true); + + final ByteBuffer valueBuffer = ByteBuffer.allocateDirect(value.length + 24); + valueBuffer.position(12); + KeyMayExist keyMayExist = db.keyMayExist(readOptions, keyBuffer, valueBuffer); + assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); + assertThat(keyMayExist.valueLength).isEqualTo(value.length); + assertThat(valueBuffer.position()).isEqualTo(12); + assertThat(valueBuffer.limit()).isEqualTo(12 + value.length); + byte[] valueGet = new byte[value.length]; + valueBuffer.get(valueGet); + assertThat(valueGet).isEqualTo(value); + + valueBuffer.limit(value.length + 24); + valueBuffer.position(25); + keyMayExist = db.keyMayExist(readOptions, keyBuffer, valueBuffer); + assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); + assertThat(keyMayExist.valueLength).isEqualTo(value.length); + assertThat(valueBuffer.position()).isEqualTo(25); + assertThat(valueBuffer.limit()).isEqualTo(24 + value.length); + valueGet = new byte[value.length - 1]; + valueBuffer.get(valueGet); + assertThat(valueGet).isEqualTo(Arrays.copyOfRange(value, 0, value.length - 1)); + + exceptionRule.expect(BufferUnderflowException.class); + valueGet = new byte[value.length]; + valueBuffer.get(valueGet); + } + } + + @Test + public void keyMayExistBBNullValue() throws RocksDBException { + // Standard key + db.put("keyBB".getBytes(UTF_8), "valueBB".getBytes(UTF_8)); + + final byte[] key = "keyBB".getBytes(UTF_8); + + final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); + keyBuffer.put(key, 0, key.length); + keyBuffer.flip(); + + exceptionRule.expect(AssertionError.class); + exceptionRule.expectMessage( + "value ByteBuffer parameter cannot be null. If you do not need the value, use a different version of the method"); + final KeyMayExist keyMayExist = db.keyMayExist(keyBuffer, null); + } + + @Test + public void keyMayExistBBCF() throws RocksDBException { + // Standard key + db.put(columnFamilyHandleList.get(0), "keyBBCF0".getBytes(UTF_8), "valueBBCF0".getBytes(UTF_8)); + db.put(columnFamilyHandleList.get(1), "keyBBCF1".getBytes(UTF_8), "valueBBCF1".getBytes(UTF_8)); + + // 0 is the default CF + byte[] key = "keyBBCF0".getBytes(UTF_8); + ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); + keyBuffer.put(key, 0, key.length); + keyBuffer.flip(); + + assertThat(db.keyMayExist(keyBuffer)).isEqualTo(true); + assertThat(db.keyMayExist(columnFamilyHandleList.get(1), keyBuffer)).isEqualTo(false); + assertThat(db.keyMayExist(columnFamilyHandleList.get(0), keyBuffer)).isEqualTo(true); + + // 1 is just a CF + key = "keyBBCF1".getBytes(UTF_8); + keyBuffer = ByteBuffer.allocateDirect(key.length); + keyBuffer.put(key, 0, key.length); + keyBuffer.flip(); + + assertThat(db.keyMayExist(keyBuffer)).isEqualTo(false); + assertThat(db.keyMayExist(columnFamilyHandleList.get(1), keyBuffer)).isEqualTo(true); + assertThat(db.keyMayExist(columnFamilyHandleList.get(0), keyBuffer)).isEqualTo(false); + + exceptionRule.expect(AssertionError.class); + exceptionRule.expectMessage( + "value ByteBuffer parameter cannot be null. If you do not need the value, use a different version of the method"); + final KeyMayExist keyMayExist = db.keyMayExist(columnFamilyHandleList.get(0), keyBuffer, null); + } + + @Test + public void keyMayExistBBCFReadOptions() throws RocksDBException { + // Standard key + db.put(columnFamilyHandleList.get(0), "keyBBCF0".getBytes(UTF_8), "valueBBCF0".getBytes(UTF_8)); + db.put(columnFamilyHandleList.get(1), "keyBBCF1".getBytes(UTF_8), "valueBBCF1".getBytes(UTF_8)); + + // 0 is the default CF + byte[] key = "keyBBCF0".getBytes(UTF_8); + ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); + keyBuffer.put(key, 0, key.length); + keyBuffer.flip(); + + try (final ReadOptions readOptions = new ReadOptions()) { + assertThat(db.keyMayExist(keyBuffer)).isEqualTo(true); + assertThat(db.keyMayExist(columnFamilyHandleList.get(1), readOptions, keyBuffer)) + .isEqualTo(false); + assertThat(db.keyMayExist(columnFamilyHandleList.get(0), readOptions, keyBuffer)) + .isEqualTo(true); + + // 1 is just a CF + key = "keyBBCF1".getBytes(UTF_8); + keyBuffer = ByteBuffer.allocateDirect(key.length); + keyBuffer.put(key, 0, key.length); + keyBuffer.flip(); + + assertThat(db.keyMayExist(readOptions, keyBuffer)).isEqualTo(false); + assertThat(db.keyMayExist(columnFamilyHandleList.get(1), readOptions, keyBuffer)) + .isEqualTo(true); + assertThat(db.keyMayExist(columnFamilyHandleList.get(0), readOptions, keyBuffer)) + .isEqualTo(false); + + exceptionRule.expect(AssertionError.class); + exceptionRule.expectMessage( + "value ByteBuffer parameter cannot be null. If you do not need the value, use a different version of the method"); + final KeyMayExist keyMayExist = + db.keyMayExist(columnFamilyHandleList.get(0), readOptions, keyBuffer, null); + } + } + + @Test + public void keyMayExistBBCFOffset() throws RocksDBException { + db.put(columnFamilyHandleList.get(1), "keyBBCF1".getBytes(UTF_8), "valueBBCF1".getBytes(UTF_8)); + + final byte[] key = "keyBBCF1".getBytes(UTF_8); + final byte[] value = "valueBBCF1".getBytes(UTF_8); + + final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); + keyBuffer.put(key, 0, key.length); + keyBuffer.flip(); + + assertThat(db.keyMayExist(columnFamilyHandleList.get(1), keyBuffer)).isEqualTo(true); + + final ByteBuffer valueBuffer = ByteBuffer.allocateDirect(value.length + 24); + valueBuffer.position(12); + KeyMayExist keyMayExist = db.keyMayExist(columnFamilyHandleList.get(1), keyBuffer, valueBuffer); + assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); + assertThat(keyMayExist.valueLength).isEqualTo(value.length); + assertThat(valueBuffer.position()).isEqualTo(12); + assertThat(valueBuffer.limit()).isEqualTo(12 + value.length); + byte[] valueGet = new byte[value.length]; + valueBuffer.get(valueGet); + assertThat(valueGet).isEqualTo(value); + + valueBuffer.limit(value.length + 24); + valueBuffer.position(25); + keyMayExist = db.keyMayExist(columnFamilyHandleList.get(1), keyBuffer, valueBuffer); + assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); + assertThat(keyMayExist.valueLength).isEqualTo(value.length); + assertThat(valueBuffer.position()).isEqualTo(25); + assertThat(valueBuffer.limit()).isEqualTo(24 + value.length); + valueGet = new byte[value.length - 1]; + valueBuffer.get(valueGet); + assertThat(valueGet).isEqualTo(Arrays.copyOfRange(value, 0, value.length - 1)); + + exceptionRule.expect(BufferUnderflowException.class); + valueGet = new byte[value.length]; + valueBuffer.get(valueGet); + } + + @Test + public void keyMayExistBBCFOffsetReadOptions() throws RocksDBException { + db.put(columnFamilyHandleList.get(1), "keyBBCF1".getBytes(UTF_8), "valueBBCF1".getBytes(UTF_8)); + + final byte[] key = "keyBBCF1".getBytes(UTF_8); + final byte[] value = "valueBBCF1".getBytes(UTF_8); + + final ByteBuffer keyBuffer = ByteBuffer.allocateDirect(key.length); + keyBuffer.put(key, 0, key.length); + keyBuffer.flip(); + + try (final ReadOptions readOptions = new ReadOptions()) { + assertThat(db.keyMayExist(columnFamilyHandleList.get(1), readOptions, keyBuffer)) + .isEqualTo(true); + + final ByteBuffer valueBuffer = ByteBuffer.allocateDirect(value.length + 24); + valueBuffer.position(12); + KeyMayExist keyMayExist = + db.keyMayExist(columnFamilyHandleList.get(1), readOptions, keyBuffer, valueBuffer); + assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); + assertThat(keyMayExist.valueLength).isEqualTo(value.length); + assertThat(valueBuffer.position()).isEqualTo(12); + assertThat(valueBuffer.limit()).isEqualTo(12 + value.length); + byte[] valueGet = new byte[value.length]; + valueBuffer.get(valueGet); + assertThat(valueGet).isEqualTo(value); + + valueBuffer.limit(value.length + 24); + valueBuffer.position(25); + keyMayExist = + db.keyMayExist(columnFamilyHandleList.get(1), readOptions, keyBuffer, valueBuffer); + assertThat(keyMayExist.exists).isEqualTo(KeyMayExist.KeyMayExistEnum.kExistsWithValue); + assertThat(keyMayExist.valueLength).isEqualTo(value.length); + assertThat(valueBuffer.position()).isEqualTo(25); + assertThat(valueBuffer.limit()).isEqualTo(24 + value.length); + valueGet = new byte[value.length - 1]; + valueBuffer.get(valueGet); + assertThat(valueGet).isEqualTo(Arrays.copyOfRange(value, 0, value.length - 1)); + + exceptionRule.expect(BufferUnderflowException.class); + valueGet = new byte[value.length]; + valueBuffer.get(valueGet); + } + } + + @Test + public void keyMayExistNonUnicodeString() throws RocksDBException { + 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 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(); + } }