diff --git a/java/Makefile b/java/Makefile index 26fa38d05..c8f443f7b 100644 --- a/java/Makefile +++ b/java/Makefile @@ -36,6 +36,8 @@ NATIVE_JAVA_CLASSES = org.rocksdb.AbstractComparator\ org.rocksdb.test.WriteBatchInternal\ org.rocksdb.test.WriteBatchTest\ org.rocksdb.WriteOptions\ + org.rocksdb.WriteBatchWithIndex\ + org.rocksdb.WBWIRocksIterator ROCKSDB_MAJOR = $(shell egrep "ROCKSDB_MAJOR.[0-9]" ../include/rocksdb/version.h | cut -d ' ' -f 3) ROCKSDB_MINOR = $(shell egrep "ROCKSDB_MINOR.[0-9]" ../include/rocksdb/version.h | cut -d ' ' -f 3) @@ -81,6 +83,7 @@ JAVA_TESTS = org.rocksdb.test.BackupableDBOptionsTest\ org.rocksdb.test.WriteBatchHandlerTest\ org.rocksdb.test.WriteBatchTest\ org.rocksdb.test.WriteOptionsTest\ + org.rocksdb.test.WriteBatchWithIndexTest JAVA_TEST_LIBDIR = ./test-libs/ JAVA_JUNIT_JAR = $(JAVA_TEST_LIBDIR)junit-4.12-beta-2.jar diff --git a/java/org/rocksdb/AbstractRocksIterator.java b/java/org/rocksdb/AbstractRocksIterator.java new file mode 100644 index 000000000..cc7cf064f --- /dev/null +++ b/java/org/rocksdb/AbstractRocksIterator.java @@ -0,0 +1,105 @@ +// Copyright (c) 2014, 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; + +/** + * Base class implementation for Rocks Iterators + * in the Java API + *
+ *Multiple threads can invoke const methods on an RocksIterator without + * external synchronization, but if any of the threads may call a + * non-const method, all threads accessing the same RocksIterator must use + * external synchronization.
+ * + * @param P The type of the Parent Object from which the Rocks Iterator was + * created. This is used by disposeInternal to avoid double-free + * issues with the underlying C++ object. + * @see org.rocksdb.RocksObject + */ +public abstract class AbstractRocksIterator+ extends RocksObject implements RocksIteratorInterface { + final P parent_; + + protected AbstractRocksIterator(P parent, long nativeHandle) { + super(); + nativeHandle_ = nativeHandle; + // parent must point to a valid RocksDB instance. + assert (parent != null); + // RocksIterator must hold a reference to the related parent instance + // to guarantee that while a GC cycle starts RocksIterator instances + // are freed prior to parent instances. + parent_ = parent; + } + + @Override + public boolean isValid() { + assert (isInitialized()); + return isValid0(nativeHandle_); + } + + @Override + public void seekToFirst() { + assert (isInitialized()); + seekToFirst0(nativeHandle_); + } + + @Override + public void seekToLast() { + assert (isInitialized()); + seekToLast0(nativeHandle_); + } + + @Override + public void seek(byte[] target) { + assert (isInitialized()); + seek0(nativeHandle_, target, target.length); + } + + @Override + public void next() { + assert (isInitialized()); + next0(nativeHandle_); + } + + @Override + public void prev() { + assert (isInitialized()); + prev0(nativeHandle_); + } + + @Override + public void status() throws RocksDBException { + assert (isInitialized()); + status0(nativeHandle_); + } + + /** + *
Deletes underlying C++ iterator pointer.
+ * + *Note: the underlying handle can only be safely deleted if the parent + * instance related to a certain RocksIterator is still valid and initialized. + * Therefore {@code disposeInternal()} checks if the parent is initialized + * before freeing the native handle.
+ */ + @Override + protected void disposeInternal() { + synchronized (parent_) { + assert (isInitialized()); + if (parent_.isInitialized()) { + disposeInternal(nativeHandle_); + } + } + } + + abstract void disposeInternal(long handle); + abstract boolean isValid0(long handle); + abstract void seekToFirst0(long handle); + abstract void seekToLast0(long handle); + abstract void next0(long handle); + abstract void prev0(long handle); + abstract void seek0(long handle, byte[] target, int targetLen); + abstract void status0(long handle) throws RocksDBException; +} diff --git a/java/org/rocksdb/AbstractWriteBatch.java b/java/org/rocksdb/AbstractWriteBatch.java new file mode 100644 index 000000000..b380c5d8a --- /dev/null +++ b/java/org/rocksdb/AbstractWriteBatch.java @@ -0,0 +1,92 @@ +// Copyright (c) 2014, 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; + +public abstract class AbstractWriteBatch extends RocksObject implements WriteBatchInterface { + + @Override + public int count() { + assert (isInitialized()); + return count0(); + } + + @Override + public void put(byte[] key, byte[] value) { + assert (isInitialized()); + put(key, key.length, value, value.length); + } + + @Override + public void put(ColumnFamilyHandle columnFamilyHandle, byte[] key, byte[] value) { + assert (isInitialized()); + put(key, key.length, value, value.length, columnFamilyHandle.nativeHandle_); + } + + @Override + public void merge(byte[] key, byte[] value) { + assert (isInitialized()); + merge(key, key.length, value, value.length); + } + + @Override + public void merge(ColumnFamilyHandle columnFamilyHandle, byte[] key, byte[] value) { + assert (isInitialized()); + merge(key, key.length, value, value.length, columnFamilyHandle.nativeHandle_); + } + + @Override + public void remove(byte[] key) { + assert (isInitialized()); + remove(key, key.length); + } + + @Override + public void remove(ColumnFamilyHandle columnFamilyHandle, byte[] key) { + assert (isInitialized()); + remove(key, key.length, columnFamilyHandle.nativeHandle_); + } + + @Override + public void putLogData(byte[] blob) { + assert (isInitialized()); + putLogData(blob, blob.length); + } + + @Override + public void clear() { + assert (isInitialized()); + clear0(); + } + + /** + * Delete the c++ side pointer. + */ + @Override + protected void disposeInternal() { + assert (isInitialized()); + disposeInternal(nativeHandle_); + } + + abstract void disposeInternal(long handle); + + abstract int count0(); + + abstract void put(byte[] key, int keyLen, byte[] value, int valueLen); + + abstract void put(byte[] key, int keyLen, byte[] value, int valueLen, long cfHandle); + + abstract void merge(byte[] key, int keyLen, byte[] value, int valueLen); + + abstract void merge(byte[] key, int keyLen, byte[] value, int valueLen, long cfHandle); + + abstract void remove(byte[] key, int keyLen); + + abstract void remove(byte[] key, int keyLen, long cfHandle); + + abstract void putLogData(byte[] blob, int blobLen); + + abstract void clear0(); +} diff --git a/java/org/rocksdb/DirectSlice.java b/java/org/rocksdb/DirectSlice.java index c69b61460..765b01586 100644 --- a/java/org/rocksdb/DirectSlice.java +++ b/java/org/rocksdb/DirectSlice.java @@ -16,6 +16,9 @@ import java.nio.ByteBuffer; * values consider using @see org.rocksdb.Slice */ public class DirectSlice extends AbstractSliceAn iterator yields a sequence of key/value pairs from a source. - * The following class defines the interface. Multiple implementations - * are provided by this library. In particular, iterators are provided + *
An iterator that yields a sequence of key/value pairs from a source. + * Multiple implementations are provided by this library. + * In particular, iterators are provided * to access the contents of a Table or a DB.
* *Multiple threads can invoke const methods on an RocksIterator without @@ -18,67 +18,9 @@ package org.rocksdb; * * @see org.rocksdb.RocksObject */ -public class RocksIterator extends RocksObject { - public RocksIterator(RocksDB rocksDB, long nativeHandle) { - super(); - nativeHandle_ = nativeHandle; - // rocksDB must point to a valid RocksDB instance. - assert(rocksDB != null); - // RocksIterator must hold a reference to the related RocksDB instance - // to guarantee that while a GC cycle starts RocksDBIterator instances - // are freed prior to RocksDB instances. - rocksDB_ = rocksDB; - } - - /** - * An iterator is either positioned at a key/value pair, or - * not valid. This method returns true iff the iterator is valid. - * - * @return true if iterator is valid. - */ - public boolean isValid() { - assert(isInitialized()); - return isValid0(nativeHandle_); - } - - /** - * Position at the first key in the source. The iterator is Valid() - * after this call iff the source is not empty. - */ - public void seekToFirst() { - assert(isInitialized()); - seekToFirst0(nativeHandle_); - } - - /** - * Position at the last key in the source. The iterator is - * valid after this call iff the source is not empty. - */ - public void seekToLast() { - assert(isInitialized()); - seekToLast0(nativeHandle_); - } - - /** - *
Moves to the next entry in the source. After this call, Valid() is - * true iff the iterator was not positioned at the last entry in the source.
- * - *REQUIRES: {@link #isValid()}
- */ - public void next() { - assert(isInitialized()); - next0(nativeHandle_); - } - - /** - *Moves to the previous entry in the source. After this call, Valid() is - * true iff the iterator was not positioned at the first entry in source.
- * - *REQUIRES: {@link #isValid()}
- */ - public void prev() { - assert(isInitialized()); - prev0(nativeHandle_); +public class RocksIterator extends AbstractRocksIteratorPosition at the first key in the source that at or past target - * The iterator is valid after this call iff the source contains - * an entry that comes at or past target.
- * - * @param target byte array describing a key or a - * key prefix to seek for. - */ - public void seek(byte[] target) { - assert(isInitialized()); - seek0(nativeHandle_, target, target.length); - } + @Override final native void disposeInternal(long handle); + @Override final native boolean isValid0(long handle); + @Override final native void seekToFirst0(long handle); + @Override final native void seekToLast0(long handle); + @Override final native void next0(long handle); + @Override final native void prev0(long handle); + @Override final native void seek0(long handle, byte[] target, int targetLen); + @Override final native void status0(long handle) throws RocksDBException; - /** - * If an error has occurred, return it. Else return an ok status. - * If non-blocking IO is requested and this operation cannot be - * satisfied without doing some IO, then this returns Status::Incomplete(). - * - * @throws RocksDBException thrown if error happens in underlying - * native library. - */ - public void status() throws RocksDBException { - assert(isInitialized()); - status0(nativeHandle_); - } - - /** - *Deletes underlying C++ iterator pointer.
- * - *Note: the underlying handle can only be safely deleted if the RocksDB - * instance related to a certain RocksIterator is still valid and initialized. - * Therefore {@code disposeInternal()} checks if the RocksDB is initialized - * before freeing the native handle.
- */ - @Override protected void disposeInternal() { - synchronized (rocksDB_) { - assert (isInitialized()); - if (rocksDB_.isInitialized()) { - disposeInternal(nativeHandle_); - } - } - } - - private native boolean isValid0(long handle); - private native void disposeInternal(long handle); - private native void seekToFirst0(long handle); - private native void seekToLast0(long handle); - private native void next0(long handle); - private native void prev0(long handle); private native byte[] key0(long handle); private native byte[] value0(long handle); - private native void seek0(long handle, byte[] target, int targetLen); - private native void status0(long handle); - - final RocksDB rocksDB_; } diff --git a/java/org/rocksdb/RocksIteratorInterface.java b/java/org/rocksdb/RocksIteratorInterface.java new file mode 100644 index 000000000..15f3a9aa9 --- /dev/null +++ b/java/org/rocksdb/RocksIteratorInterface.java @@ -0,0 +1,80 @@ +// Copyright (c) 2014, 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; + +/** + *Defines the interface for an Iterator which provides + * access to data one entry at a time. Multiple implementations + * are provided by this library. In particular, iterators are provided + * to access the contents of a DB and Write Batch.
+ * + *Multiple threads can invoke const methods on an RocksIterator without + * external synchronization, but if any of the threads may call a + * non-const method, all threads accessing the same RocksIterator must use + * external synchronization.
+ * + * @see org.rocksdb.RocksObject + */ +public interface RocksIteratorInterface { + + /** + *An iterator is either positioned at an entry, or + * not valid. This method returns true if the iterator is valid.
+ * + * @return true if iterator is valid. + */ + public boolean isValid(); + + /** + *Position at the first entry in the source. The iterator is Valid() + * after this call if the source is not empty.
+ */ + public void seekToFirst(); + + /** + *Position at the last entry in the source. The iterator is + * valid after this call if the source is not empty.
+ */ + public void seekToLast(); + + /** + *Position at the first entry in the source whose key is that or + * past target.
+ * + *The iterator is valid after this call if the source contains + * a key that comes at or past target.
+ * + * @param target byte array describing a key or a + * key prefix to seek for. + */ + public void seek(byte[] target); + + /** + *Moves to the next entry in the source. After this call, Valid() is + * true if the iterator was not positioned at the last entry in the source.
+ * + *REQUIRES: {@link #isValid()}
+ */ + public void next(); + + /** + *Moves to the previous entry in the source. After this call, Valid() is + * true if the iterator was not positioned at the first entry in source.
+ * + *REQUIRES: {@link #isValid()}
+ */ + public void prev(); + + /** + *Store the mapping "key->value" in the database.
- * - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - */ - public void put(byte[] key, byte[] value) { - put(key, key.length, value, value.length); - } - - /** - *Store the mapping "key->value" within given column - * family.
- * - * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} - * instance - * @param key the specified key to be inserted. - * @param value the value associated with the specified key. - */ - public void put(ColumnFamilyHandle columnFamilyHandle, - byte[] key, byte[] value) { - put(key, key.length, value, value.length, - columnFamilyHandle.nativeHandle_); - } - - /** - *Merge "value" with the existing value of "key" in the database. - * "key->merge(existing, value)"
- * - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. - */ - public void merge(byte[] key, byte[] value) { - merge(key, key.length, value, value.length); - } - - /** - *Merge "value" with the existing value of "key" in given column family. - * "key->merge(existing, value)"
- * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key the specified key to be merged. - * @param value the value to be merged with the current value for - * the specified key. - */ - public void merge(ColumnFamilyHandle columnFamilyHandle, - byte[] key, byte[] value) { - merge(key, key.length, value, value.length, - columnFamilyHandle.nativeHandle_); - } - - /** - *If the database contains a mapping for "key", erase it. Else do nothing.
- * - * @param key Key to delete within database - */ - public void remove(byte[] key) { - remove(key, key.length); - } - - /** - *If column family contains a mapping for "key", erase it. Else do nothing.
- * - * @param columnFamilyHandle {@link ColumnFamilyHandle} instance - * @param key Key to delete within database - */ - public void remove(ColumnFamilyHandle columnFamilyHandle, byte[] key) { - remove(key, key.length, columnFamilyHandle.nativeHandle_); - } - - /** - * Append a blob of arbitrary size to the records in this batch. The blob will - * be stored in the transaction log but not in any other file. In particular, - * it will not be persisted to the SST files. When iterating over this - * WriteBatch, WriteBatch::Handler::LogData will be called with the contents - * of the blob as it is encountered. Blobs, puts, deletes, and merges will be - * encountered in the same order in thich they were inserted. The blob will - * NOT consume sequence number(s) and will NOT increase the count of the batch - * - * Example application: add timestamps to the transaction log for use in - * replication. - * - * @param blob binary object to be inserted - */ - public void putLogData(byte[] blob) { - putLogData(blob, blob.length); - } - /** * Support for iterating over the contents of a batch. * @@ -149,36 +53,22 @@ public class WriteBatch extends RocksObject { iterate(handler.nativeHandle_); } - /** - * Clear all updates buffered in this batch - */ - public native void clear(); - - /** - * Delete the c++ side pointer. - */ - @Override protected void disposeInternal() { - assert(isInitialized()); - disposeInternal(nativeHandle_); - } + @Override final native void disposeInternal(long handle); + @Override final native int count0(); + @Override final native void put(byte[] key, int keyLen, byte[] value, int valueLen); + @Override final native void put(byte[] key, int keyLen, byte[] value, int valueLen, + long cfHandle); + @Override final native void merge(byte[] key, int keyLen, byte[] value, int valueLen); + @Override final native void merge(byte[] key, int keyLen, byte[] value, int valueLen, + long cfHandle); + @Override final native void remove(byte[] key, int keyLen); + @Override final native void remove(byte[] key, int keyLen, long cfHandle); + @Override final native void putLogData(byte[] blob, int blobLen); + @Override final native void clear0(); private native void newWriteBatch(int reserved_bytes); - private native void put(byte[] key, int keyLen, - byte[] value, int valueLen); - private native void put(byte[] key, int keyLen, - byte[] value, int valueLen, - long cfHandle); - private native void merge(byte[] key, int keyLen, - byte[] value, int valueLen); - private native void merge(byte[] key, int keyLen, - byte[] value, int valueLen, - long cfHandle); - private native void remove(byte[] key, int keyLen); - private native void remove(byte[] key, int keyLen, - long cfHandle); - private native void putLogData(byte[] blob, int blobLen); private native void iterate(long handlerHandle) throws RocksDBException; - private native void disposeInternal(long handle); + /** * Handler callback for iterating over the contents of a batch. diff --git a/java/org/rocksdb/WriteBatchInterface.java b/java/org/rocksdb/WriteBatchInterface.java new file mode 100644 index 000000000..4eaf1ad9d --- /dev/null +++ b/java/org/rocksdb/WriteBatchInterface.java @@ -0,0 +1,98 @@ +// Copyright (c) 2014, 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; + +/** + *Defines the interface for a Write Batch which + * holds a collection of updates to apply atomically to a DB.
+ */ +public interface WriteBatchInterface { + + /** + * Returns the number of updates in the batch. + * + * @return number of items in WriteBatch + */ + public int count(); + + /** + *Store the mapping "key->value" in the database.
+ * + * @param key the specified key to be inserted. + * @param value the value associated with the specified key. + */ + public void put(byte[] key, byte[] value); + + /** + *Store the mapping "key->value" within given column + * family.
+ * + * @param columnFamilyHandle {@link org.rocksdb.ColumnFamilyHandle} + * instance + * @param key the specified key to be inserted. + * @param value the value associated with the specified key. + */ + public void put(ColumnFamilyHandle columnFamilyHandle, + byte[] key, byte[] value); + + /** + *Merge "value" with the existing value of "key" in the database. + * "key->merge(existing, value)"
+ * + * @param key the specified key to be merged. + * @param value the value to be merged with the current value for + * the specified key. + */ + public void merge(byte[] key, byte[] value); + + /** + *Merge "value" with the existing value of "key" in given column family. + * "key->merge(existing, value)"
+ * + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param key the specified key to be merged. + * @param value the value to be merged with the current value for + * the specified key. + */ + public void merge(ColumnFamilyHandle columnFamilyHandle, + byte[] key, byte[] value); + + /** + *If the database contains a mapping for "key", erase it. Else do nothing.
+ * + * @param key Key to delete within database + */ + public void remove(byte[] key); + + /** + *If column family contains a mapping for "key", erase it. Else do nothing.
+ * + * @param columnFamilyHandle {@link ColumnFamilyHandle} instance + * @param key Key to delete within database + */ + public void remove(ColumnFamilyHandle columnFamilyHandle, byte[] key); + + /** + * Append a blob of arbitrary size to the records in this batch. The blob will + * be stored in the transaction log but not in any other file. In particular, + * it will not be persisted to the SST files. When iterating over this + * WriteBatch, WriteBatch::Handler::LogData will be called with the contents + * of the blob as it is encountered. Blobs, puts, deletes, and merges will be + * encountered in the same order in thich they were inserted. The blob will + * NOT consume sequence number(s) and will NOT increase the count of the batch + * + * Example application: add timestamps to the transaction log for use in + * replication. + * + * @param blob binary object to be inserted + */ + public void putLogData(byte[] blob); + + /** + * Clear all updates buffered in this batch + */ + public void clear(); +} diff --git a/java/org/rocksdb/WriteBatchWithIndex.java b/java/org/rocksdb/WriteBatchWithIndex.java new file mode 100644 index 000000000..5204146c4 --- /dev/null +++ b/java/org/rocksdb/WriteBatchWithIndex.java @@ -0,0 +1,149 @@ +// Copyright (c) 2014, 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; + +/** + * Similar to {@link org.rocksdb.WriteBatch} but with a binary searchable + * index built for all the keys inserted. + * + * Calling put, merge, remove or putLogData calls the same function + * as with {@link org.rocksdb.WriteBatch} whilst also building an index. + * + * A user can call {@link org.rocksdb.WriteBatchWithIndex#newIterator() }to create an iterator + * over the write batch or + * {@link org.rocksdb.WriteBatchWithIndex#newIteratorWithBase(org.rocksdb.RocksIterator)} to + * get an iterator for the database with Read-Your-Own-Writes like capability + */ +public class WriteBatchWithIndex extends AbstractWriteBatch { + /** + * Creates a WriteBatchWithIndex where no bytes + * are reserved up-front, bytewise comparison is + * used for fallback key comparisons, + * and duplicate keys operations are retained + */ + public WriteBatchWithIndex() { + super(); + newWriteBatchWithIndex(); + } + + + /** + * Creates a WriteBatchWithIndex where no bytes + * are reserved up-front, bytewise comparison is + * used for fallback key comparisons, and duplicate key + * assignment is determined by the constructor argument + * + * @param overwriteKey if true, overwrite the key in the index when + * inserting a duplicate key, in this way an iterator will never + * show two entries with the same key. + */ + public WriteBatchWithIndex(boolean overwriteKey) { + super(); + newWriteBatchWithIndex(overwriteKey); + } + + /** + * Creates a WriteBatchWithIndex + * + * @param fallbackIndexComparator We fallback to this comparator + * to compare keys within a column family if we cannot determine + * the column family and so look up it's comparator. + * + * @param reservedBytes reserved bytes in underlying WriteBatch + * + * @param overwriteKey if true, overwrite the key in the index when + * inserting a duplicate key, in this way an iterator will never + * show two entries with the same key. + */ + public WriteBatchWithIndex(AbstractComparator fallbackIndexComparator, int reservedBytes, + boolean overwriteKey) { + super(); + newWriteBatchWithIndex(fallbackIndexComparator.nativeHandle_, reservedBytes, overwriteKey); + } + + /** + * Create an iterator of a column family. User can call + * {@link org.rocksdb.RocksIteratorInterface#seek(byte[])} to + * search to the next entry of or after a key. Keys will be iterated in the + * order given by index_comparator. For multiple updates on the same key, + * each update will be returned as a separate entry, in the order of update + * time. + * + * @param columnFamilyHandle The column family to iterate over + * @return An iterator for the Write Batch contents, restricted to the column family + */ + public WBWIRocksIterator newIterator(ColumnFamilyHandle columnFamilyHandle) { + return new WBWIRocksIterator(this, iterator1(columnFamilyHandle.nativeHandle_)); + } + + /** + * Create an iterator of the default column family. User can call + * {@link org.rocksdb.RocksIteratorInterface#seek(byte[])} to + * search to the next entry of or after a key. Keys will be iterated in the + * order given by index_comparator. For multiple updates on the same key, + * each update will be returned as a separate entry, in the order of update + * time. + * + * @return An iterator for the Write Batch contents + */ + public WBWIRocksIterator newIterator() { + return new WBWIRocksIterator(this, iterator0()); + } + + /** + * Provides Read-Your-Own-Writes like functionality by + * creating a new Iterator that will use {@link org.rocksdb.WBWIRocksIterator} + * as a delta and baseIterator as a base + * + * @param columnFamilyHandle The column family to iterate over + * @param baseIterator The base iterator, e.g. {@link org.rocksdb.RocksDB#newIterator()} + * @return An iterator which shows a view comprised of both the database point-in-time + * from baseIterator and modifications made in this write batch. + */ + public RocksIterator newIteratorWithBase(ColumnFamilyHandle columnFamilyHandle, + RocksIterator baseIterator) { + RocksIterator iterator = new RocksIterator( + baseIterator.parent_, + iteratorWithBase(columnFamilyHandle.nativeHandle_, baseIterator.nativeHandle_)); + //when the iterator is deleted it will also delete the baseIterator + baseIterator.disOwnNativeHandle(); + return iterator; + } + + /** + * Provides Read-Your-Own-Writes like functionality by + * creating a new Iterator that will use {@link org.rocksdb.WBWIRocksIterator} + * as a delta and baseIterator as a base. Operates on the default column family. + * + * @param baseIterator The base iterator, e.g. {@link org.rocksdb.RocksDB#newIterator()} + * @return An iterator which shows a view comprised of both the database point-in-time + * from baseIterator and modifications made in this write batch. + */ + public RocksIterator newIteratorWithBase(RocksIterator baseIterator) { + return newIteratorWithBase(baseIterator.parent_.getDefaultColumnFamily(), baseIterator); + } + + @Override final native void disposeInternal(long handle); + @Override final native int count0(); + @Override final native void put(byte[] key, int keyLen, byte[] value, int valueLen); + @Override final native void put(byte[] key, int keyLen, byte[] value, int valueLen, + long cfHandle); + @Override final native void merge(byte[] key, int keyLen, byte[] value, int valueLen); + @Override final native void merge(byte[] key, int keyLen, byte[] value, int valueLen, + long cfHandle); + @Override final native void remove(byte[] key, int keyLen); + @Override final native void remove(byte[] key, int keyLen, long cfHandle); + @Override final native void putLogData(byte[] blob, int blobLen); + @Override final native void clear0(); + + private native void newWriteBatchWithIndex(); + private native void newWriteBatchWithIndex(boolean overwriteKey); + private native void newWriteBatchWithIndex(long fallbackIndexComparatorHandle, int reservedBytes, + boolean overwriteKey); + private native long iterator0(); + private native long iterator1(long cfHandle); + private native long iteratorWithBase(long baseIteratorHandle, long cfHandle); +} diff --git a/java/org/rocksdb/test/ColumnFamilyTest.java b/java/org/rocksdb/test/ColumnFamilyTest.java index 703ed296f..fb95e8010 100644 --- a/java/org/rocksdb/test/ColumnFamilyTest.java +++ b/java/org/rocksdb/test/ColumnFamilyTest.java @@ -56,6 +56,40 @@ public class ColumnFamilyTest { } } + @Test + public void defaultColumnFamily() throws RocksDBException { + RocksDB db = null; + Options options = null; + try { + options = new Options(); + options.setCreateIfMissing(true); + + DBOptions dbOptions = new DBOptions(); + dbOptions.setCreateIfMissing(true); + + db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath()); + ColumnFamilyHandle cfh = db.getDefaultColumnFamily(); + assertThat(cfh).isNotNull(); + + final byte[] key = "key".getBytes(); + final byte[] value = "value".getBytes(); + + db.put(cfh, key, value); + + final byte[] actualValue = db.get(cfh, key); + + assertThat(cfh).isNotNull(); + assertThat(actualValue).isEqualTo(value); + } finally { + if (db != null) { + db.close(); + } + if (options != null) { + options.dispose(); + } + } + } + @Test public void createColumnFamily() throws RocksDBException { RocksDB db = null; diff --git a/java/org/rocksdb/test/WriteBatchWithIndexTest.java b/java/org/rocksdb/test/WriteBatchWithIndexTest.java new file mode 100644 index 000000000..de2b637ff --- /dev/null +++ b/java/org/rocksdb/test/WriteBatchWithIndexTest.java @@ -0,0 +1,247 @@ +// Copyright (c) 2014, 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. +// +// Copyright (c) 2011 The LevelDB Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. See the AUTHORS file for names of contributors. + +package org.rocksdb.test; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.rocksdb.WriteBatchWithIndex; +import org.rocksdb.DirectSlice; +import org.rocksdb.Options; +import org.rocksdb.RocksDB; +import org.rocksdb.RocksDBException; +import org.rocksdb.RocksIterator; +import org.rocksdb.WriteOptions; +import org.rocksdb.WBWIRocksIterator; + +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.Deque; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class WriteBatchWithIndexTest { + + @ClassRule + public static final RocksMemoryResource rocksMemoryResource = + new RocksMemoryResource(); + + @Rule + public TemporaryFolder dbFolder = new TemporaryFolder(); + + @Test + public void readYourOwnWrites() throws RocksDBException { + RocksDB db = null; + Options options = null; + try { + options = new Options(); + // Setup options + options.setCreateIfMissing(true); + db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath()); + + final byte[] k1 = "key1".getBytes(); + final byte[] v1 = "value1".getBytes(); + final byte[] k2 = "key2".getBytes(); + final byte[] v2 = "value2".getBytes(); + + db.put(k1, v1); + db.put(k2, v2); + + final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); + + RocksIterator base = null; + RocksIterator it = null; + try { + base = db.newIterator(); + it = wbwi.newIteratorWithBase(base); + + it.seek(k1); + assertThat(it.isValid()).isTrue(); + assertThat(it.key()).isEqualTo(k1); + assertThat(it.value()).isEqualTo(v1); + + it.seek(k2); + assertThat(it.isValid()).isTrue(); + assertThat(it.key()).isEqualTo(k2); + assertThat(it.value()).isEqualTo(v2); + + //put data to the write batch and make sure we can read it. + final byte[] k3 = "key3".getBytes(); + final byte[] v3 = "value3".getBytes(); + wbwi.put(k3, v3); + it.seek(k3); + assertThat(it.isValid()).isTrue(); + assertThat(it.key()).isEqualTo(k3); + assertThat(it.value()).isEqualTo(v3); + + //update k2 in the write batch and check the value + final byte[] v2Other = "otherValue2".getBytes(); + wbwi.put(k2, v2Other); + it.seek(k2); + assertThat(it.isValid()).isTrue(); + assertThat(it.key()).isEqualTo(k2); + assertThat(it.value()).isEqualTo(v2Other); + + //remove k1 and make sure we can read back the write + wbwi.remove(k1); + it.seek(k1); + assertThat(it.key()).isNotEqualTo(k1); + + //reinsert k1 and make sure we see the new value + final byte[] v1Other = "otherValue1".getBytes(); + wbwi.put(k1, v1Other); + it.seek(k1); + assertThat(it.isValid()).isTrue(); + assertThat(it.key()).isEqualTo(k1); + assertThat(it.value()).isEqualTo(v1Other); + } finally { + if (it != null) { + it.dispose(); + } + if (base != null) { + base.dispose(); + } + } + + } finally { + if (db != null) { + db.close(); + } + if (options != null) { + options.dispose(); + } + } + } + + @Test + public void write_writeBatchWithIndex() throws RocksDBException { + RocksDB db = null; + Options options = null; + try { + options = new Options(); + // Setup options + options.setCreateIfMissing(true); + db = RocksDB.open(options, dbFolder.getRoot().getAbsolutePath()); + + final byte[] k1 = "key1".getBytes(); + final byte[] v1 = "value1".getBytes(); + final byte[] k2 = "key2".getBytes(); + final byte[] v2 = "value2".getBytes(); + + WriteBatchWithIndex wbwi = null; + + try { + wbwi = new WriteBatchWithIndex(); + + + wbwi.put(k1, v1); + wbwi.put(k2, v2); + + db.write(new WriteOptions(), wbwi); + } finally { + if(wbwi != null) { + wbwi.dispose(); + } + } + + assertThat(db.get(k1)).isEqualTo(v1); + assertThat(db.get(k2)).isEqualTo(v2); + + } finally { + if (db != null) { + db.close(); + } + if (options != null) { + options.dispose(); + } + } + } + + @Test + public void iterator() throws RocksDBException { + final WriteBatchWithIndex wbwi = new WriteBatchWithIndex(true); + + final String k1 = "key1"; + final String v1 = "value1"; + final String k2 = "key2"; + final String v2 = "value2"; + final String k3 = "key3"; + final String v3 = "value3"; + final byte[] k1b = k1.getBytes(); + final byte[] v1b = v1.getBytes(); + final byte[] k2b = k2.getBytes(); + final byte[] v2b = v2.getBytes(); + final byte[] k3b = k3.getBytes(); + final byte[] v3b = v3.getBytes(); + + //add put records + wbwi.put(k1b, v1b); + wbwi.put(k2b, v2b); + wbwi.put(k3b, v3b); + + //add a deletion record + final String k4 = "key4"; + final byte[] k4b = k4.getBytes(); + wbwi.remove(k4b); + + WBWIRocksIterator.WriteEntry[] expected = { + new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.PUT, + new DirectSlice(k1), new DirectSlice(v1)), + new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.PUT, + new DirectSlice(k2), new DirectSlice(v2)), + new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.PUT, + new DirectSlice(k3), new DirectSlice(v3)), + new WBWIRocksIterator.WriteEntry(WBWIRocksIterator.WriteType.DELETE, + new DirectSlice(k4), DirectSlice.NONE) + }; + + WBWIRocksIterator it = null; + try { + it = wbwi.newIterator(); + + //direct access - seek to key offsets + final int[] testOffsets = {2, 0, 1, 3}; + + for(int i = 0; i < testOffsets.length; i++) { + final int testOffset = testOffsets[i]; + final byte[] key = toArray(expected[testOffset].getKey().data()); + + it.seek(key); + assertThat(it.isValid()).isTrue(); + assertThat(it.entry()).isEqualTo(expected[testOffset]); + } + + //forward iterative access + int i = 0; + for(it.seekToFirst(); it.isValid(); it.next()) { + assertThat(it.entry()).isEqualTo(expected[i++]); + } + + //reverse iterative access + i = expected.length - 1; + for(it.seekToLast(); it.isValid(); it.prev()) { + assertThat(it.entry()).isEqualTo(expected[i--]); + } + + } finally { + if(it != null) { + it.dispose(); + } + } + } + + private byte[] toArray(final ByteBuffer buf) { + final byte[] ary = new byte[buf.remaining()]; + buf.get(ary); + return ary; + } +} diff --git a/java/rocksjni/iterator.cc b/java/rocksjni/iterator.cc index c7667a018..e9eb0bb37 100644 --- a/java/rocksjni/iterator.cc +++ b/java/rocksjni/iterator.cc @@ -14,6 +14,17 @@ #include "rocksjni/portal.h" #include "rocksdb/iterator.h" +/* + * Class: org_rocksdb_RocksIterator + * Method: disposeInternal + * Signature: (J)V + */ +void Java_org_rocksdb_RocksIterator_disposeInternal( + JNIEnv* env, jobject jobj, jlong handle) { + auto it = reinterpret_cast