1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
6 package org
.rocksdb
.util
;
8 import org
.junit
.ClassRule
;
10 import org
.junit
.Test
;
11 import org
.junit
.rules
.TemporaryFolder
;
14 import java
.io
.IOException
;
15 import java
.nio
.ByteBuffer
;
16 import java
.nio
.file
.*;
19 import static java
.nio
.charset
.StandardCharsets
.UTF_8
;
20 import static org
.junit
.Assert
.*;
21 import static org
.rocksdb
.util
.ByteUtil
.bytes
;
24 * This is a direct port of various C++
25 * tests from db/comparator_db_test.cc
26 * and some code to adapt it to RocksJava
28 public class BytewiseComparatorTest
{
31 public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE
=
32 new RocksNativeLibraryResource();
35 public TemporaryFolder dbFolder
= new TemporaryFolder();
37 private List
<String
> source_strings
= Arrays
.asList("b", "d", "f", "h", "j", "l");
38 private List
<String
> interleaving_strings
= Arrays
.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m");
41 * Open the database using the C++ BytewiseComparatorImpl
42 * and test the results against our Java BytewiseComparator
45 public void java_vs_cpp_bytewiseComparator()
46 throws IOException
, RocksDBException
{
47 for(int rand_seed
= 301; rand_seed
< 306; rand_seed
++) {
49 FileSystems
.getDefault().getPath(dbFolder
.newFolder().getAbsolutePath());
50 try(final RocksDB db
= openDatabase(dbDir
,
51 BuiltinComparator
.BYTEWISE_COMPARATOR
)) {
53 final Random rnd
= new Random(rand_seed
);
54 try(final ComparatorOptions copt2
= new ComparatorOptions()
55 .setUseDirectBuffer(false);
56 final AbstractComparator comparator2
= new BytewiseComparator(copt2
)) {
57 final java
.util
.Comparator
<String
> jComparator
= toJavaComparator(comparator2
);
58 doRandomIterationTest(
70 * Open the database using the Java BytewiseComparator
71 * and test the results against another Java BytewiseComparator
74 public void java_vs_java_bytewiseComparator()
75 throws IOException
, RocksDBException
{
76 for(int rand_seed
= 301; rand_seed
< 306; rand_seed
++) {
78 FileSystems
.getDefault().getPath(dbFolder
.newFolder().getAbsolutePath());
79 try(final ComparatorOptions copt
= new ComparatorOptions()
80 .setUseDirectBuffer(false);
81 final AbstractComparator comparator
= new BytewiseComparator(copt
);
82 final RocksDB db
= openDatabase(dbDir
, comparator
)) {
84 final Random rnd
= new Random(rand_seed
);
85 try(final ComparatorOptions copt2
= new ComparatorOptions()
86 .setUseDirectBuffer(false);
87 final AbstractComparator comparator2
= new BytewiseComparator(copt2
)) {
88 final java
.util
.Comparator
<String
> jComparator
= toJavaComparator(comparator2
);
89 doRandomIterationTest(
101 * Open the database using the C++ BytewiseComparatorImpl
102 * and test the results against our Java DirectBytewiseComparator
105 public void java_vs_cpp_directBytewiseComparator()
106 throws IOException
, RocksDBException
{
107 for(int rand_seed
= 301; rand_seed
< 306; rand_seed
++) {
109 FileSystems
.getDefault().getPath(dbFolder
.newFolder().getAbsolutePath());
110 try(final RocksDB db
= openDatabase(dbDir
,
111 BuiltinComparator
.BYTEWISE_COMPARATOR
)) {
113 final Random rnd
= new Random(rand_seed
);
114 try(final ComparatorOptions copt2
= new ComparatorOptions()
115 .setUseDirectBuffer(true);
116 final AbstractComparator comparator2
= new BytewiseComparator(copt2
)) {
117 final java
.util
.Comparator
<String
> jComparator
= toJavaComparator(comparator2
);
118 doRandomIterationTest(
130 * Open the database using the Java DirectBytewiseComparator
131 * and test the results against another Java DirectBytewiseComparator
134 public void java_vs_java_directBytewiseComparator()
135 throws IOException
, RocksDBException
{
136 for(int rand_seed
= 301; rand_seed
< 306; rand_seed
++) {
138 FileSystems
.getDefault().getPath(dbFolder
.newFolder().getAbsolutePath());
139 try (final ComparatorOptions copt
= new ComparatorOptions()
140 .setUseDirectBuffer(true);
141 final AbstractComparator comparator
= new BytewiseComparator(copt
);
142 final RocksDB db
= openDatabase(dbDir
, comparator
)) {
144 final Random rnd
= new Random(rand_seed
);
145 try(final ComparatorOptions copt2
= new ComparatorOptions()
146 .setUseDirectBuffer(true);
147 final AbstractComparator comparator2
= new BytewiseComparator(copt2
)) {
148 final java
.util
.Comparator
<String
> jComparator
= toJavaComparator(comparator2
);
149 doRandomIterationTest(
161 * Open the database using the C++ ReverseBytewiseComparatorImpl
162 * and test the results against our Java ReverseBytewiseComparator
165 public void java_vs_cpp_reverseBytewiseComparator()
166 throws IOException
, RocksDBException
{
167 for(int rand_seed
= 301; rand_seed
< 306; rand_seed
++) {
169 FileSystems
.getDefault().getPath(dbFolder
.newFolder().getAbsolutePath());
170 try(final RocksDB db
= openDatabase(dbDir
,
171 BuiltinComparator
.REVERSE_BYTEWISE_COMPARATOR
)) {
173 final Random rnd
= new Random(rand_seed
);
174 try(final ComparatorOptions copt2
= new ComparatorOptions()
175 .setUseDirectBuffer(false);
176 final AbstractComparator comparator2
= new ReverseBytewiseComparator(copt2
)) {
177 final java
.util
.Comparator
<String
> jComparator
= toJavaComparator(comparator2
);
178 doRandomIterationTest(
190 * Open the database using the Java ReverseBytewiseComparator
191 * and test the results against another Java ReverseBytewiseComparator
194 public void java_vs_java_reverseBytewiseComparator()
195 throws IOException
, RocksDBException
{
196 for(int rand_seed
= 301; rand_seed
< 306; rand_seed
++) {
198 FileSystems
.getDefault().getPath(dbFolder
.newFolder().getAbsolutePath());
199 try (final ComparatorOptions copt
= new ComparatorOptions()
200 .setUseDirectBuffer(false);
201 final AbstractComparator comparator
= new ReverseBytewiseComparator(copt
);
202 final RocksDB db
= openDatabase(dbDir
, comparator
)) {
204 final Random rnd
= new Random(rand_seed
);
205 try(final ComparatorOptions copt2
= new ComparatorOptions()
206 .setUseDirectBuffer(false);
207 final AbstractComparator comparator2
= new ReverseBytewiseComparator(copt2
)) {
208 final java
.util
.Comparator
<String
> jComparator
= toJavaComparator(comparator2
);
209 doRandomIterationTest(
220 private void doRandomIterationTest(
221 final RocksDB db
, final java
.util
.Comparator
<String
> javaComparator
,
223 final int num_writes
, final int num_iter_ops
,
224 final int num_trigger_flush
) throws RocksDBException
{
226 final TreeMap
<String
, String
> map
= new TreeMap
<>(javaComparator
);
228 try (final FlushOptions flushOptions
= new FlushOptions();
229 final WriteOptions writeOptions
= new WriteOptions()) {
230 for (int i
= 0; i
< num_writes
; i
++) {
231 if (num_trigger_flush
> 0 && i
!= 0 && i
% num_trigger_flush
== 0) {
232 db
.flush(flushOptions
);
235 final int type
= rnd
.nextInt(2);
236 final int index
= rnd
.nextInt(source_strings
.size());
237 final String key
= source_strings
.get(index
);
242 db
.put(writeOptions
, bytes(key
), bytes(key
));
246 if (map
.containsKey(key
)) {
249 db
.delete(writeOptions
, bytes(key
));
253 fail("Should not be able to generate random outside range 1..2");
258 try (final ReadOptions readOptions
= new ReadOptions();
259 final RocksIterator iter
= db
.newIterator(readOptions
)) {
260 final KVIter
<String
, String
> result_iter
= new KVIter
<>(map
);
262 boolean is_valid
= false;
263 for (int i
= 0; i
< num_iter_ops
; i
++) {
264 // Random walk and make sure iter and result_iter returns the
265 // same key and value
266 final int type
= rnd
.nextInt(8);
272 result_iter
.seekToFirst();
277 result_iter
.seekToLast();
280 // Seek to random (existing or non-existing) key
281 final int key_idx
= rnd
.nextInt(interleaving_strings
.size());
282 final String key
= interleaving_strings
.get(key_idx
);
283 iter
.seek(bytes(key
));
284 result_iter
.seek(bytes(key
));
288 // SeekForPrev to random (existing or non-existing) key
289 final int key_idx
= rnd
.nextInt(interleaving_strings
.size());
290 final String key
= interleaving_strings
.get(key_idx
);
291 iter
.seekForPrev(bytes(key
));
292 result_iter
.seekForPrev(bytes(key
));
316 result_iter
.refresh();
318 result_iter
.seekToFirst();
322 final int key_idx
= rnd
.nextInt(source_strings
.size());
323 final String key
= source_strings
.get(key_idx
);
324 final byte[] result
= db
.get(readOptions
, bytes(key
));
325 if (!map
.containsKey(key
)) {
328 assertArrayEquals(bytes(map
.get(key
)), result
);
334 assertEquals(result_iter
.isValid(), iter
.isValid());
336 is_valid
= iter
.isValid();
339 assertArrayEquals(bytes(result_iter
.key()), iter
.key());
341 //note that calling value on a non-valid iterator from the Java API
342 //results in a SIGSEGV
343 assertArrayEquals(bytes(result_iter
.value()), iter
.value());
350 * Open the database using a C++ Comparator
352 private RocksDB
openDatabase(
353 final Path dbDir
, final BuiltinComparator cppComparator
)
354 throws IOException
, RocksDBException
{
355 final Options options
= new Options()
356 .setCreateIfMissing(true)
357 .setComparator(cppComparator
);
358 return RocksDB
.open(options
, dbDir
.toAbsolutePath().toString());
362 * Open the database using a Java Comparator
364 private RocksDB
openDatabase(
366 final AbstractComparator javaComparator
)
367 throws IOException
, RocksDBException
{
368 final Options options
= new Options()
369 .setCreateIfMissing(true)
370 .setComparator(javaComparator
);
371 return RocksDB
.open(options
, dbDir
.toAbsolutePath().toString());
374 private java
.util
.Comparator
<String
> toJavaComparator(
375 final AbstractComparator rocksComparator
) {
376 return new java
.util
.Comparator
<String
>() {
378 public int compare(final String s1
, final String s2
) {
379 final ByteBuffer bufS1
;
380 final ByteBuffer bufS2
;
381 if (rocksComparator
.usingDirectBuffers()) {
382 bufS1
= ByteBuffer
.allocateDirect(s1
.length());
383 bufS2
= ByteBuffer
.allocateDirect(s2
.length());
385 bufS1
= ByteBuffer
.allocate(s1
.length());
386 bufS2
= ByteBuffer
.allocate(s2
.length());
388 bufS1
.put(bytes(s1
));
390 bufS2
.put(bytes(s2
));
392 return rocksComparator
.compare(bufS1
, bufS2
);
397 private static class KVIter
<K
, V
> implements RocksIteratorInterface
{
399 private final List
<Map
.Entry
<K
, V
>> entries
;
400 private final java
.util
.Comparator
<?
super K
> comparator
;
401 private int offset
= -1;
403 private int lastPrefixMatchIdx
= -1;
404 private int lastPrefixMatch
= 0;
406 public KVIter(final TreeMap
<K
, V
> map
) {
407 this.entries
= new ArrayList
<>();
408 entries
.addAll(map
.entrySet());
409 this.comparator
= map
.comparator();
414 public boolean isValid() {
415 return offset
> -1 && offset
< entries
.size();
419 public void seekToFirst() {
424 public void seekToLast() {
425 offset
= entries
.size() - 1;
428 @SuppressWarnings("unchecked")
430 public void seek(final byte[] target
) {
431 for(offset
= 0; offset
< entries
.size(); offset
++) {
432 if(comparator
.compare(entries
.get(offset
).getKey(),
433 (K
)new String(target
, UTF_8
)) >= 0) {
439 @SuppressWarnings("unchecked")
441 public void seekForPrev(final byte[] target
) {
442 for(offset
= entries
.size()-1; offset
>= 0; offset
--) {
443 if(comparator
.compare(entries
.get(offset
).getKey(),
444 (K
)new String(target
, UTF_8
)) <= 0) {
451 * Is `a` a prefix of `b`
453 * @return The length of the matching prefix, or 0 if it is not a prefix
455 private int isPrefix(final byte[] a
, final byte[] b
) {
456 if(b
.length
>= a
.length
) {
457 for(int i
= 0; i
< a
.length
; i
++) {
470 if(offset
< entries
.size()) {
483 public void refresh() throws RocksDBException
{
488 public void status() throws RocksDBException
{
489 if(offset
< 0 || offset
>= entries
.size()) {
490 throw new RocksDBException("Index out of bounds. Size is: " +
491 entries
.size() + ", offset is: " + offset
);
495 @SuppressWarnings("unchecked")
498 if(entries
.isEmpty()) {
500 } else if(offset
== -1){
501 return entries
.get(0).getKey();
502 } else if(offset
== entries
.size()) {
503 return entries
.get(offset
- 1).getKey();
508 return entries
.get(offset
).getKey();
512 @SuppressWarnings("unchecked")
517 return entries
.get(offset
).getValue();
522 public void seek(ByteBuffer target
) {
523 throw new IllegalAccessError("Not implemented");
527 public void seekForPrev(ByteBuffer target
) {
528 throw new IllegalAccessError("Not implemented");