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