From 817eeb29b437d1a68701571cc81331901e8c06e4 Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Mon, 22 Aug 2016 19:02:31 +0100 Subject: [PATCH] Add singleDelete to RocksJava (#1275) * Rename RocksDB#remove -> RocksDB#delete to match C++ API; Added deprecated versions of RocksDB#remove for backwards compatibility. * Add missing experimental feature RocksDB#singleDelete --- java/rocksjni/rocksjni.cc | 123 +++++++++- .../main/java/org/rocksdb/Experimental.java | 23 ++ java/src/main/java/org/rocksdb/RocksDB.java | 222 +++++++++++++++++- .../test/java/org/rocksdb/RocksDBTest.java | 34 ++- 4 files changed, 378 insertions(+), 24 deletions(-) create mode 100644 java/src/main/java/org/rocksdb/Experimental.java diff --git a/java/rocksjni/rocksjni.cc b/java/rocksjni/rocksjni.cc index 53a5b8548..e77452e61 100644 --- a/java/rocksjni/rocksjni.cc +++ b/java/rocksjni/rocksjni.cc @@ -824,7 +824,7 @@ jint Java_org_rocksdb_RocksDB_get__JJ_3BI_3BIJ( } ////////////////////////////////////////////////////////////////////////////// // rocksdb::DB::Delete() -void rocksdb_remove_helper( +void rocksdb_delete_helper( JNIEnv* env, rocksdb::DB* db, const rocksdb::WriteOptions& write_options, rocksdb::ColumnFamilyHandle* cf_handle, jbyteArray jkey, jint jkey_len) { jbyte* key = env->GetByteArrayElements(jkey, 0); @@ -850,25 +850,25 @@ void rocksdb_remove_helper( /* * Class: org_rocksdb_RocksDB - * Method: remove + * Method: delete * Signature: (J[BI)V */ -void Java_org_rocksdb_RocksDB_remove__J_3BI( +void Java_org_rocksdb_RocksDB_delete__J_3BI( JNIEnv* env, jobject jdb, jlong jdb_handle, jbyteArray jkey, jint jkey_len) { auto db = reinterpret_cast(jdb_handle); static const rocksdb::WriteOptions default_write_options = rocksdb::WriteOptions(); - rocksdb_remove_helper(env, db, default_write_options, nullptr, + rocksdb_delete_helper(env, db, default_write_options, nullptr, jkey, jkey_len); } /* * Class: org_rocksdb_RocksDB - * Method: remove + * Method: delete * Signature: (J[BIJ)V */ -void Java_org_rocksdb_RocksDB_remove__J_3BIJ( +void Java_org_rocksdb_RocksDB_delete__J_3BIJ( JNIEnv* env, jobject jdb, jlong jdb_handle, jbyteArray jkey, jint jkey_len, jlong jcf_handle) { auto db = reinterpret_cast(jdb_handle); @@ -876,7 +876,7 @@ void Java_org_rocksdb_RocksDB_remove__J_3BIJ( rocksdb::WriteOptions(); auto cf_handle = reinterpret_cast(jcf_handle); if (cf_handle != nullptr) { - rocksdb_remove_helper(env, db, default_write_options, cf_handle, + rocksdb_delete_helper(env, db, default_write_options, cf_handle, jkey, jkey_len); } else { rocksdb::RocksDBExceptionJni::ThrowNew(env, @@ -886,23 +886,23 @@ void Java_org_rocksdb_RocksDB_remove__J_3BIJ( /* * Class: org_rocksdb_RocksDB - * Method: remove + * Method: delete * Signature: (JJ[BIJ)V */ -void Java_org_rocksdb_RocksDB_remove__JJ_3BI( +void Java_org_rocksdb_RocksDB_delete__JJ_3BI( JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jwrite_options, jbyteArray jkey, jint jkey_len) { auto db = reinterpret_cast(jdb_handle); auto write_options = reinterpret_cast(jwrite_options); - rocksdb_remove_helper(env, db, *write_options, nullptr, jkey, jkey_len); + rocksdb_delete_helper(env, db, *write_options, nullptr, jkey, jkey_len); } /* * Class: org_rocksdb_RocksDB - * Method: remove + * Method: delete * Signature: (JJ[BIJ)V */ -void Java_org_rocksdb_RocksDB_remove__JJ_3BIJ( +void Java_org_rocksdb_RocksDB_delete__JJ_3BIJ( JNIEnv* env, jobject jdb, jlong jdb_handle, jlong jwrite_options, jbyteArray jkey, jint jkey_len, jlong jcf_handle) { @@ -910,12 +910,109 @@ void Java_org_rocksdb_RocksDB_remove__JJ_3BIJ( auto write_options = reinterpret_cast(jwrite_options); auto cf_handle = reinterpret_cast(jcf_handle); if (cf_handle != nullptr) { - rocksdb_remove_helper(env, db, *write_options, cf_handle, jkey, jkey_len); + rocksdb_delete_helper(env, db, *write_options, cf_handle, jkey, jkey_len); } else { rocksdb::RocksDBExceptionJni::ThrowNew(env, rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); } } + +////////////////////////////////////////////////////////////////////////////// +// rocksdb::DB::SingleDelete() +void rocksdb_single_delete_helper( + JNIEnv* env, rocksdb::DB* db, const rocksdb::WriteOptions& write_options, + rocksdb::ColumnFamilyHandle* cf_handle, jbyteArray jkey, jint jkey_len) { + jbyte* key = env->GetByteArrayElements(jkey, 0); + rocksdb::Slice key_slice(reinterpret_cast(key), jkey_len); + + rocksdb::Status s; + if (cf_handle != nullptr) { + s = db->SingleDelete(write_options, cf_handle, key_slice); + } else { + // backwards compatibility + s = db->SingleDelete(write_options, key_slice); + } + // trigger java unref on key and value. + // by passing JNI_ABORT, it will simply release the reference without + // copying the result back to the java byte array. + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + + if (!s.ok()) { + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + } +} + +/* + * Class: org_rocksdb_RocksDB + * Method: singleDelete + * Signature: (J[BI)V + */ +void Java_org_rocksdb_RocksDB_singleDelete__J_3BI( + JNIEnv* env, jobject jdb, jlong jdb_handle, + jbyteArray jkey, jint jkey_len) { + auto db = reinterpret_cast(jdb_handle); + static const rocksdb::WriteOptions default_write_options = + rocksdb::WriteOptions(); + rocksdb_single_delete_helper(env, db, default_write_options, nullptr, + jkey, jkey_len); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: singleDelete + * Signature: (J[BIJ)V + */ +void Java_org_rocksdb_RocksDB_singleDelete__J_3BIJ( + JNIEnv* env, jobject jdb, jlong jdb_handle, + jbyteArray jkey, jint jkey_len, jlong jcf_handle) { + auto db = reinterpret_cast(jdb_handle); + static const rocksdb::WriteOptions default_write_options = + rocksdb::WriteOptions(); + auto cf_handle = reinterpret_cast(jcf_handle); + if (cf_handle != nullptr) { + rocksdb_single_delete_helper(env, db, default_write_options, cf_handle, + jkey, jkey_len); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew(env, + rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + } +} + +/* + * Class: org_rocksdb_RocksDB + * Method: singleDelete + * Signature: (JJ[BIJ)V + */ +void Java_org_rocksdb_RocksDB_singleDelete__JJ_3BI( + JNIEnv* env, jobject jdb, jlong jdb_handle, + jlong jwrite_options, jbyteArray jkey, jint jkey_len) { + auto db = reinterpret_cast(jdb_handle); + auto write_options = reinterpret_cast(jwrite_options); + rocksdb_single_delete_helper(env, db, *write_options, nullptr, jkey, + jkey_len); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: singleDelete + * Signature: (JJ[BIJ)V + */ +void Java_org_rocksdb_RocksDB_singleDelete__JJ_3BIJ( + JNIEnv* env, jobject jdb, jlong jdb_handle, + jlong jwrite_options, jbyteArray jkey, jint jkey_len, + jlong jcf_handle) { + auto db = reinterpret_cast(jdb_handle); + auto write_options = reinterpret_cast(jwrite_options); + auto cf_handle = reinterpret_cast(jcf_handle); + if (cf_handle != nullptr) { + rocksdb_single_delete_helper(env, db, *write_options, cf_handle, jkey, + jkey_len); + } else { + rocksdb::RocksDBExceptionJni::ThrowNew(env, + rocksdb::Status::InvalidArgument("Invalid ColumnFamilyHandle.")); + } +} + ////////////////////////////////////////////////////////////////////////////// // rocksdb::DB::Merge diff --git a/java/src/main/java/org/rocksdb/Experimental.java b/java/src/main/java/org/rocksdb/Experimental.java new file mode 100644 index 000000000..dcbbd37ee --- /dev/null +++ b/java/src/main/java/org/rocksdb/Experimental.java @@ -0,0 +1,23 @@ +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. + +package org.rocksdb; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a feature as experimental, meaning that it is likely + * to change or even be removed/re-engineered in the future + */ +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface Experimental { + String value(); +} diff --git a/java/src/main/java/org/rocksdb/RocksDB.java b/java/src/main/java/org/rocksdb/RocksDB.java index 4a8e3b9bd..2781043d4 100644 --- a/java/src/main/java/org/rocksdb/RocksDB.java +++ b/java/src/main/java/org/rocksdb/RocksDB.java @@ -961,9 +961,26 @@ public class RocksDB extends RocksObject { * * @throws RocksDBException thrown if error happens in underlying * native library. + * + * @deprecated Use {@link #delete(byte[])} */ + @Deprecated public void remove(final byte[] key) throws RocksDBException { - remove(nativeHandle_, key, key.length); + delete(key); + } + + /** + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. + * + * @param key Key to delete within database + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public void delete(final byte[] key) throws RocksDBException { + delete(nativeHandle_, key, key.length); } /** @@ -977,10 +994,30 @@ public class RocksDB extends RocksObject { * * @throws RocksDBException thrown if error happens in underlying * native library. + * + * @deprecated Use {@link #delete(ColumnFamilyHandle, byte[])} */ + @Deprecated public void remove(final ColumnFamilyHandle columnFamilyHandle, final byte[] key) throws RocksDBException { - remove(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); + delete(columnFamilyHandle, key); + } + + /** + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key Key to delete within database + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public void delete(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key) throws RocksDBException { + delete(nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); } /** @@ -993,10 +1030,29 @@ public class RocksDB extends RocksObject { * * @throws RocksDBException thrown if error happens in underlying * native library. + * + * @deprecated Use {@link #delete(WriteOptions, byte[])} */ + @Deprecated public void remove(final WriteOptions writeOpt, final byte[] key) throws RocksDBException { - remove(nativeHandle_, writeOpt.nativeHandle_, key, key.length); + delete(writeOpt, key); + } + + /** + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. + * + * @param writeOpt WriteOptions to be used with delete operation + * @param key Key to delete within database + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public void delete(final WriteOptions writeOpt, final byte[] key) + throws RocksDBException { + delete(nativeHandle_, writeOpt.nativeHandle_, key, key.length); } /** @@ -1011,11 +1067,150 @@ public class RocksDB extends RocksObject { * * @throws RocksDBException thrown if error happens in underlying * native library. + * + * @deprecated Use {@link #delete(ColumnFamilyHandle, WriteOptions, byte[])} */ + @Deprecated public void remove(final ColumnFamilyHandle columnFamilyHandle, final WriteOptions writeOpt, final byte[] key) throws RocksDBException { - remove(nativeHandle_, writeOpt.nativeHandle_, key, key.length, + delete(columnFamilyHandle, writeOpt, key); + } + + /** + * Delete the database entry (if any) for "key". Returns OK on + * success, and a non-OK status on error. It is not an error if "key" + * did not exist in the database. + * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param writeOpt WriteOptions to be used with delete operation + * @param key Key to delete within database + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + public void delete(final ColumnFamilyHandle columnFamilyHandle, + final WriteOptions writeOpt, final byte[] key) + throws RocksDBException { + delete(nativeHandle_, writeOpt.nativeHandle_, key, key.length, + columnFamilyHandle.nativeHandle_); + } + + /** + * Remove the database entry for {@code key}. Requires that the key exists + * and was not overwritten. It is not an error if the key did not exist + * in the database. + * + * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple + * times), then the result of calling SingleDelete() on this key is undefined. + * SingleDelete() only behaves correctly if there has been only one Put() + * for this key since the previous call to SingleDelete() for this key. + * + * This feature is currently an experimental performance optimization + * for a very specific workload. It is up to the caller to ensure that + * SingleDelete is only used for a key that is not deleted using Delete() or + * written using Merge(). Mixing SingleDelete operations with Deletes and + * Merges can result in undefined behavior. + * + * @param key Key to delete within database + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final byte[] key) throws RocksDBException { + singleDelete(nativeHandle_, key, key.length); + } + + /** + * Remove the database entry for {@code key}. Requires that the key exists + * and was not overwritten. It is not an error if the key did not exist + * in the database. + * + * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple + * times), then the result of calling SingleDelete() on this key is undefined. + * SingleDelete() only behaves correctly if there has been only one Put() + * for this key since the previous call to SingleDelete() for this key. + * + * This feature is currently an experimental performance optimization + * for a very specific workload. It is up to the caller to ensure that + * SingleDelete is only used for a key that is not deleted using Delete() or + * written using Merge(). Mixing SingleDelete operations with Deletes and + * Merges can result in undefined behavior. + * + * @param columnFamilyHandle The column family to delete the key from + * @param key Key to delete within database + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, + final byte[] key) throws RocksDBException { + singleDelete(nativeHandle_, key, key.length, + columnFamilyHandle.nativeHandle_); + } + + /** + * Remove the database entry for {@code key}. Requires that the key exists + * and was not overwritten. It is not an error if the key did not exist + * in the database. + * + * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple + * times), then the result of calling SingleDelete() on this key is undefined. + * SingleDelete() only behaves correctly if there has been only one Put() + * for this key since the previous call to SingleDelete() for this key. + * + * This feature is currently an experimental performance optimization + * for a very specific workload. It is up to the caller to ensure that + * SingleDelete is only used for a key that is not deleted using Delete() or + * written using Merge(). Mixing SingleDelete operations with Deletes and + * Merges can result in undefined behavior. + * + * Note: consider setting {@link WriteOptions#setSync(boolean)} true. + * + * @param writeOpt Write options for the delete + * @param key Key to delete within database + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final WriteOptions writeOpt, final byte[] key) + throws RocksDBException { + singleDelete(nativeHandle_, writeOpt.nativeHandle_, key, key.length); + } + + /** + * Remove the database entry for {@code key}. Requires that the key exists + * and was not overwritten. It is not an error if the key did not exist + * in the database. + * + * If a key is overwritten (by calling {@link #put(byte[], byte[])} multiple + * times), then the result of calling SingleDelete() on this key is undefined. + * SingleDelete() only behaves correctly if there has been only one Put() + * for this key since the previous call to SingleDelete() for this key. + * + * This feature is currently an experimental performance optimization + * for a very specific workload. It is up to the caller to ensure that + * SingleDelete is only used for a key that is not deleted using Delete() or + * written using Merge(). Mixing SingleDelete operations with Deletes and + * Merges can result in undefined behavior. + * + * Note: consider setting {@link WriteOptions#setSync(boolean)} true. + * + * @param columnFamilyHandle The column family to delete the key from + * @param writeOpt Write options for the delete + * @param key Key to delete within database + * + * @throws RocksDBException thrown if error happens in underlying + * native library. + */ + @Experimental("Performance optimization for a very specific workload") + public void singleDelete(final ColumnFamilyHandle columnFamilyHandle, + final WriteOptions writeOpt, final byte[] key) throws RocksDBException { + singleDelete(nativeHandle_, writeOpt.nativeHandle_, key, key.length, columnFamilyHandle.nativeHandle_); } @@ -1849,15 +2044,26 @@ public class RocksDB extends RocksObject { protected native byte[] get( long handle, long readOptHandle, byte[] key, int keyLen, long cfHandle) throws RocksDBException; - protected native void remove( + protected native void delete( + long handle, byte[] key, int keyLen) throws RocksDBException; + protected native void delete( + long handle, byte[] key, int keyLen, long cfHandle) + throws RocksDBException; + protected native void delete( + long handle, long writeOptHandle, + byte[] key, int keyLen) throws RocksDBException; + protected native void delete( + long handle, long writeOptHandle, + byte[] key, int keyLen, long cfHandle) throws RocksDBException; + protected native void singleDelete( long handle, byte[] key, int keyLen) throws RocksDBException; - protected native void remove( + protected native void singleDelete( long handle, byte[] key, int keyLen, long cfHandle) throws RocksDBException; - protected native void remove( + protected native void singleDelete( long handle, long writeOptHandle, byte[] key, int keyLen) throws RocksDBException; - protected native void remove( + protected native void singleDelete( long handle, long writeOptHandle, byte[] key, int keyLen, long cfHandle) throws RocksDBException; protected native String getProperty0(long nativeHandle, diff --git a/java/src/test/java/org/rocksdb/RocksDBTest.java b/java/src/test/java/org/rocksdb/RocksDBTest.java index a90659c2b..0f32c6cbd 100644 --- a/java/src/test/java/org/rocksdb/RocksDBTest.java +++ b/java/src/test/java/org/rocksdb/RocksDBTest.java @@ -192,7 +192,7 @@ public class RocksDBTest { } @Test - public void remove() throws RocksDBException { + public void delete() throws RocksDBException { try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); final WriteOptions wOpt = new WriteOptions()) { db.put("key1".getBytes(), "value".getBytes()); @@ -201,8 +201,36 @@ public class RocksDBTest { "value".getBytes()); assertThat(db.get("key2".getBytes())).isEqualTo( "12345678".getBytes()); - db.remove("key1".getBytes()); - db.remove(wOpt, "key2".getBytes()); + db.delete("key1".getBytes()); + db.delete(wOpt, "key2".getBytes()); + assertThat(db.get("key1".getBytes())).isNull(); + assertThat(db.get("key2".getBytes())).isNull(); + } + } + + @Test + public void singleDelete() throws RocksDBException { + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); + final WriteOptions wOpt = new WriteOptions()) { + db.put("key1".getBytes(), "value".getBytes()); + db.put("key2".getBytes(), "12345678".getBytes()); + assertThat(db.get("key1".getBytes())).isEqualTo( + "value".getBytes()); + assertThat(db.get("key2".getBytes())).isEqualTo( + "12345678".getBytes()); + db.singleDelete("key1".getBytes()); + db.singleDelete(wOpt, "key2".getBytes()); + assertThat(db.get("key1".getBytes())).isNull(); + assertThat(db.get("key2".getBytes())).isNull(); + } + } + + @Test + public void singleDelete_nonExisting() throws RocksDBException { + try (final RocksDB db = RocksDB.open(dbFolder.getRoot().getAbsolutePath()); + final WriteOptions wOpt = new WriteOptions()) { + db.singleDelete("key1".getBytes()); + db.singleDelete(wOpt, "key2".getBytes()); assertThat(db.get("key1".getBytes())).isNull(); assertThat(db.get("key2".getBytes())).isNull(); }