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