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).
5 
6 package org.rocksdb.util;
7 
8 import org.junit.BeforeClass;
9 import org.junit.ClassRule;
10 import org.junit.Rule;
11 import org.junit.Test;
12 import org.junit.rules.TemporaryFolder;
13 import org.junit.runner.RunWith;
14 import org.junit.runners.Parameterized;
15 import org.junit.runners.Parameterized.Parameter;
16 import org.junit.runners.Parameterized.Parameters;
17 import org.rocksdb.*;
18 
19 import java.nio.ByteBuffer;
20 import java.nio.file.FileSystems;
21 import java.nio.file.Path;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.Random;
26 
27 import static java.nio.charset.StandardCharsets.UTF_8;
28 import static org.assertj.core.api.Assertions.assertThat;
29 
30 /**
31  * Similar to {@link IntComparatorTest}, but uses
32  * {@link ReverseBytewiseComparator} which ensures the correct reverse
33  * ordering of positive integers.
34  */
35 @RunWith(Parameterized.class)
36 public class ReverseBytewiseComparatorIntTest {
37 
38   // test with 500 random positive integer keys
39   private static final int TOTAL_KEYS = 500;
40   private static final byte[][] keys = new byte[TOTAL_KEYS][4];
41 
42   @BeforeClass
prepareKeys()43   public static void prepareKeys() {
44     final ByteBuffer buf = ByteBuffer.allocate(4);
45     final Random random = new Random();
46     for (int i = 0; i < TOTAL_KEYS; i++) {
47       final int ri = random.nextInt() & Integer.MAX_VALUE;  // the & ensures positive integer
48       buf.putInt(ri);
49       buf.flip();
50       final byte[] key = buf.array();
51 
52       // does key already exist (avoid duplicates)
53       if (keyExists(key, i)) {
54         i--; // loop round and generate a different key
55       } else {
56         System.arraycopy(key, 0, keys[i], 0, 4);
57       }
58     }
59   }
60 
keyExists(final byte[] key, final int limit)61   private static boolean keyExists(final byte[] key, final int limit) {
62     for (int j = 0; j < limit; j++) {
63       if (Arrays.equals(key, keys[j])) {
64         return true;
65       }
66     }
67     return false;
68   }
69 
70   @Parameters(name = "{0}")
parameters()71   public static Iterable<Object[]> parameters() {
72     return Arrays.asList(new Object[][] {
73         { "non-direct_reused64_mutex", false, 64, ReusedSynchronisationType.MUTEX },
74         { "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.MUTEX },
75         { "non-direct_reused64_adaptive-mutex", false, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX },
76         { "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.ADAPTIVE_MUTEX },
77         { "non-direct_reused64_adaptive-mutex", false, 64, ReusedSynchronisationType.THREAD_LOCAL },
78         { "direct_reused64_adaptive-mutex", true, 64, ReusedSynchronisationType.THREAD_LOCAL },
79         { "non-direct_noreuse", false, -1, null },
80         { "direct_noreuse", true, -1, null }
81     });
82   }
83 
84   @Parameter(0)
85   public String name;
86 
87   @Parameter(1)
88   public boolean useDirectBuffer;
89 
90   @Parameter(2)
91   public int maxReusedBufferSize;
92 
93   @Parameter(3)
94   public ReusedSynchronisationType reusedSynchronisationType;
95 
96   @ClassRule
97   public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE =
98       new RocksNativeLibraryResource();
99 
100   @Rule
101   public TemporaryFolder dbFolder = new TemporaryFolder();
102 
103 
104   @Test
javaComparatorDefaultCf()105   public void javaComparatorDefaultCf() throws RocksDBException {
106     try (final ComparatorOptions options = new ComparatorOptions()
107         .setUseDirectBuffer(useDirectBuffer)
108         .setMaxReusedBufferSize(maxReusedBufferSize)
109         // if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used
110         .setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType);
111         final ReverseBytewiseComparator comparator =
112             new ReverseBytewiseComparator(options)) {
113 
114       // test the round-tripability of keys written and read with the Comparator
115       testRoundtrip(FileSystems.getDefault().getPath(
116           dbFolder.getRoot().getAbsolutePath()), comparator);
117     }
118   }
119 
120   @Test
javaComparatorNamedCf()121   public void javaComparatorNamedCf() throws RocksDBException {
122     try (final ComparatorOptions options = new ComparatorOptions()
123         .setUseDirectBuffer(useDirectBuffer)
124         .setMaxReusedBufferSize(maxReusedBufferSize)
125         // if reusedSynchronisationType == null we assume that maxReusedBufferSize <= 0 and so we just set ADAPTIVE_MUTEX, even though it won't be used
126         .setReusedSynchronisationType(reusedSynchronisationType == null ? ReusedSynchronisationType.ADAPTIVE_MUTEX : reusedSynchronisationType);
127       final ReverseBytewiseComparator comparator
128           = new ReverseBytewiseComparator(options)) {
129 
130       // test the round-tripability of keys written and read with the Comparator
131       testRoundtripCf(FileSystems.getDefault().getPath(
132           dbFolder.getRoot().getAbsolutePath()), comparator);
133     }
134   }
135 
136   /**
137    * Test which stores random keys into the database
138    * using an {@link IntComparator}
139    * it then checks that these keys are read back in
140    * ascending order
141    *
142    * @param db_path A path where we can store database
143    *                files temporarily
144    *
145    * @param comparator the comparator
146    *
147    * @throws RocksDBException if a database error happens.
148    */
testRoundtrip(final Path db_path, final AbstractComparator comparator)149   private void testRoundtrip(final Path db_path,
150       final AbstractComparator comparator) throws RocksDBException {
151     try (final Options opt = new Options()
152              .setCreateIfMissing(true)
153              .setComparator(comparator)) {
154 
155       // store TOTAL_KEYS into the db
156       try (final RocksDB db = RocksDB.open(opt, db_path.toString())) {
157         for (int i = 0; i < TOTAL_KEYS; i++) {
158               db.put(keys[i], "value".getBytes(UTF_8));
159         }
160       }
161 
162       // re-open db and read from start to end
163       // integer keys should be in descending
164       // order
165       final ByteBuffer key = ByteBuffer.allocate(4);
166       try (final RocksDB db = RocksDB.open(opt, db_path.toString());
167            final RocksIterator it = db.newIterator()) {
168         it.seekToFirst();
169         int lastKey = Integer.MAX_VALUE;
170         int count = 0;
171         for (it.seekToFirst(); it.isValid(); it.next()) {
172           key.put(it.key());
173           key.flip();
174           final int thisKey = key.getInt();
175           key.clear();
176           assertThat(thisKey).isLessThan(lastKey);
177           lastKey = thisKey;
178           count++;
179         }
180         assertThat(count).isEqualTo(TOTAL_KEYS);
181       }
182     }
183   }
184 
185   /**
186    * Test which stores random keys into a column family
187    * in the database
188    * using an {@link IntComparator}
189    * it then checks that these keys are read back in
190    * ascending order
191    *
192    * @param db_path A path where we can store database
193    *                files temporarily
194    *
195    * @param comparator the comparator
196    *
197    * @throws RocksDBException if a database error happens.
198    */
testRoundtripCf(final Path db_path, final AbstractComparator comparator)199   private void testRoundtripCf(final Path db_path,
200       final AbstractComparator comparator) throws RocksDBException {
201 
202     final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
203         new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY),
204         new ColumnFamilyDescriptor("new_cf".getBytes(),
205             new ColumnFamilyOptions()
206                 .setComparator(comparator))
207     );
208 
209     final List<ColumnFamilyHandle> cfHandles = new ArrayList<>();
210 
211     try (final DBOptions opt = new DBOptions()
212         .setCreateIfMissing(true)
213         .setCreateMissingColumnFamilies(true)) {
214 
215       try (final RocksDB db = RocksDB.open(opt, db_path.toString(),
216           cfDescriptors, cfHandles)) {
217         try {
218           assertThat(cfDescriptors.size()).isEqualTo(2);
219           assertThat(cfHandles.size()).isEqualTo(2);
220 
221           for (int i = 0; i < TOTAL_KEYS; i++) {
222             db.put(cfHandles.get(1), keys[i], "value".getBytes(UTF_8));
223           }
224         } finally {
225           for (final ColumnFamilyHandle cfHandle : cfHandles) {
226             cfHandle.close();
227           }
228           cfHandles.clear();
229         }
230       }
231 
232       // re-open db and read from start to end
233       // integer keys should be in descending
234       // order
235       final ByteBuffer key = ByteBuffer.allocate(4);
236       try (final RocksDB db = RocksDB.open(opt, db_path.toString(),
237           cfDescriptors, cfHandles);
238            final RocksIterator it = db.newIterator(cfHandles.get(1))) {
239         try {
240           assertThat(cfDescriptors.size()).isEqualTo(2);
241           assertThat(cfHandles.size()).isEqualTo(2);
242 
243           it.seekToFirst();
244           int lastKey = Integer.MAX_VALUE;
245           int count = 0;
246           for (it.seekToFirst(); it.isValid(); it.next()) {
247             key.put(it.key());
248             key.flip();
249             final int thisKey = key.getInt();
250             key.clear();
251             assertThat(thisKey).isLessThan(lastKey);
252             lastKey = thisKey;
253             count++;
254           }
255 
256           assertThat(count).isEqualTo(TOTAL_KEYS);
257 
258         } finally {
259           for (final ColumnFamilyHandle cfHandle : cfHandles) {
260             cfHandle.close();
261           }
262           cfHandles.clear();
263           for (final ColumnFamilyDescriptor cfDescriptor : cfDescriptors) {
264             cfDescriptor.getOptions().close();
265           }
266         }
267       }
268     }
269   }
270 }
271