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; 7 8 import java.util.ArrayList; 9 import java.util.HashMap; 10 import java.util.List; 11 import java.util.Map; 12 13 /** 14 * Database with Transaction support 15 */ 16 public class TransactionDB extends RocksDB 17 implements TransactionalDB<TransactionOptions> { 18 19 private TransactionDBOptions transactionDbOptions_; 20 21 /** 22 * Private constructor. 23 * 24 * @param nativeHandle The native handle of the C++ TransactionDB object 25 */ TransactionDB(final long nativeHandle)26 private TransactionDB(final long nativeHandle) { 27 super(nativeHandle); 28 } 29 30 /** 31 * Open a TransactionDB, similar to {@link RocksDB#open(Options, String)}. 32 * 33 * @param options {@link org.rocksdb.Options} instance. 34 * @param transactionDbOptions {@link org.rocksdb.TransactionDBOptions} 35 * instance. 36 * @param path the path to the rocksdb. 37 * 38 * @return a {@link TransactionDB} instance on success, null if the specified 39 * {@link TransactionDB} can not be opened. 40 * 41 * @throws RocksDBException if an error occurs whilst opening the database. 42 */ open(final Options options, final TransactionDBOptions transactionDbOptions, final String path)43 public static TransactionDB open(final Options options, 44 final TransactionDBOptions transactionDbOptions, final String path) 45 throws RocksDBException { 46 final TransactionDB tdb = new TransactionDB(open(options.nativeHandle_, 47 transactionDbOptions.nativeHandle_, path)); 48 49 // when non-default Options is used, keeping an Options reference 50 // in RocksDB can prevent Java to GC during the life-time of 51 // the currently-created RocksDB. 52 tdb.storeOptionsInstance(options); 53 tdb.storeTransactionDbOptions(transactionDbOptions); 54 55 return tdb; 56 } 57 58 /** 59 * Open a TransactionDB, similar to 60 * {@link RocksDB#open(DBOptions, String, List, List)}. 61 * 62 * @param dbOptions {@link org.rocksdb.DBOptions} instance. 63 * @param transactionDbOptions {@link org.rocksdb.TransactionDBOptions} 64 * instance. 65 * @param path the path to the rocksdb. 66 * @param columnFamilyDescriptors list of column family descriptors 67 * @param columnFamilyHandles will be filled with ColumnFamilyHandle instances 68 * 69 * @return a {@link TransactionDB} instance on success, null if the specified 70 * {@link TransactionDB} can not be opened. 71 * 72 * @throws RocksDBException if an error occurs whilst opening the database. 73 */ open(final DBOptions dbOptions, final TransactionDBOptions transactionDbOptions, final String path, final List<ColumnFamilyDescriptor> columnFamilyDescriptors, final List<ColumnFamilyHandle> columnFamilyHandles)74 public static TransactionDB open(final DBOptions dbOptions, 75 final TransactionDBOptions transactionDbOptions, 76 final String path, 77 final List<ColumnFamilyDescriptor> columnFamilyDescriptors, 78 final List<ColumnFamilyHandle> columnFamilyHandles) 79 throws RocksDBException { 80 81 final byte[][] cfNames = new byte[columnFamilyDescriptors.size()][]; 82 final long[] cfOptionHandles = new long[columnFamilyDescriptors.size()]; 83 for (int i = 0; i < columnFamilyDescriptors.size(); i++) { 84 final ColumnFamilyDescriptor cfDescriptor = columnFamilyDescriptors 85 .get(i); 86 cfNames[i] = cfDescriptor.getName(); 87 cfOptionHandles[i] = cfDescriptor.getOptions().nativeHandle_; 88 } 89 90 final long[] handles = open(dbOptions.nativeHandle_, 91 transactionDbOptions.nativeHandle_, path, cfNames, cfOptionHandles); 92 final TransactionDB tdb = new TransactionDB(handles[0]); 93 94 // when non-default Options is used, keeping an Options reference 95 // in RocksDB can prevent Java to GC during the life-time of 96 // the currently-created RocksDB. 97 tdb.storeOptionsInstance(dbOptions); 98 tdb.storeTransactionDbOptions(transactionDbOptions); 99 100 for (int i = 1; i < handles.length; i++) { 101 columnFamilyHandles.add(new ColumnFamilyHandle(tdb, handles[i])); 102 } 103 104 return tdb; 105 } 106 107 /** 108 * This is similar to {@link #close()} except that it 109 * throws an exception if any error occurs. 110 * 111 * This will not fsync the WAL files. 112 * If syncing is required, the caller must first call {@link #syncWal()} 113 * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch 114 * with {@link WriteOptions#setSync(boolean)} set to true. 115 * 116 * See also {@link #close()}. 117 * 118 * @throws RocksDBException if an error occurs whilst closing. 119 */ closeE()120 public void closeE() throws RocksDBException { 121 if (owningHandle_.compareAndSet(true, false)) { 122 try { 123 closeDatabase(nativeHandle_); 124 } finally { 125 disposeInternal(); 126 } 127 } 128 } 129 130 /** 131 * This is similar to {@link #closeE()} except that it 132 * silently ignores any errors. 133 * 134 * This will not fsync the WAL files. 135 * If syncing is required, the caller must first call {@link #syncWal()} 136 * or {@link #write(WriteOptions, WriteBatch)} using an empty write batch 137 * with {@link WriteOptions#setSync(boolean)} set to true. 138 * 139 * See also {@link #close()}. 140 */ 141 @Override close()142 public void close() { 143 if (owningHandle_.compareAndSet(true, false)) { 144 try { 145 closeDatabase(nativeHandle_); 146 } catch (final RocksDBException e) { 147 // silently ignore the error report 148 } finally { 149 disposeInternal(); 150 } 151 } 152 } 153 154 @Override beginTransaction(final WriteOptions writeOptions)155 public Transaction beginTransaction(final WriteOptions writeOptions) { 156 return new Transaction(this, beginTransaction(nativeHandle_, 157 writeOptions.nativeHandle_)); 158 } 159 160 @Override beginTransaction(final WriteOptions writeOptions, final TransactionOptions transactionOptions)161 public Transaction beginTransaction(final WriteOptions writeOptions, 162 final TransactionOptions transactionOptions) { 163 return new Transaction(this, beginTransaction(nativeHandle_, 164 writeOptions.nativeHandle_, transactionOptions.nativeHandle_)); 165 } 166 167 // TODO(AR) consider having beingTransaction(... oldTransaction) set a 168 // reference count inside Transaction, so that we can always call 169 // Transaction#close but the object is only disposed when there are as many 170 // closes as beginTransaction. Makes the try-with-resources paradigm easier for 171 // java developers 172 173 @Override beginTransaction(final WriteOptions writeOptions, final Transaction oldTransaction)174 public Transaction beginTransaction(final WriteOptions writeOptions, 175 final Transaction oldTransaction) { 176 final long jtxnHandle = beginTransaction_withOld(nativeHandle_, 177 writeOptions.nativeHandle_, oldTransaction.nativeHandle_); 178 179 // RocksJava relies on the assumption that 180 // we do not allocate a new Transaction object 181 // when providing an old_txn 182 assert(jtxnHandle == oldTransaction.nativeHandle_); 183 184 return oldTransaction; 185 } 186 187 @Override beginTransaction(final WriteOptions writeOptions, final TransactionOptions transactionOptions, final Transaction oldTransaction)188 public Transaction beginTransaction(final WriteOptions writeOptions, 189 final TransactionOptions transactionOptions, 190 final Transaction oldTransaction) { 191 final long jtxn_handle = beginTransaction_withOld(nativeHandle_, 192 writeOptions.nativeHandle_, transactionOptions.nativeHandle_, 193 oldTransaction.nativeHandle_); 194 195 // RocksJava relies on the assumption that 196 // we do not allocate a new Transaction object 197 // when providing an old_txn 198 assert(jtxn_handle == oldTransaction.nativeHandle_); 199 200 return oldTransaction; 201 } 202 getTransactionByName(final String transactionName)203 public Transaction getTransactionByName(final String transactionName) { 204 final long jtxnHandle = getTransactionByName(nativeHandle_, transactionName); 205 if(jtxnHandle == 0) { 206 return null; 207 } 208 209 final Transaction txn = new Transaction(this, jtxnHandle); 210 211 // this instance doesn't own the underlying C++ object 212 txn.disOwnNativeHandle(); 213 214 return txn; 215 } 216 getAllPreparedTransactions()217 public List<Transaction> getAllPreparedTransactions() { 218 final long[] jtxnHandles = getAllPreparedTransactions(nativeHandle_); 219 220 final List<Transaction> txns = new ArrayList<>(); 221 for(final long jtxnHandle : jtxnHandles) { 222 final Transaction txn = new Transaction(this, jtxnHandle); 223 224 // this instance doesn't own the underlying C++ object 225 txn.disOwnNativeHandle(); 226 227 txns.add(txn); 228 } 229 return txns; 230 } 231 232 public static class KeyLockInfo { 233 private final String key; 234 private final long[] transactionIDs; 235 private final boolean exclusive; 236 KeyLockInfo(final String key, final long transactionIDs[], final boolean exclusive)237 public KeyLockInfo(final String key, final long transactionIDs[], 238 final boolean exclusive) { 239 this.key = key; 240 this.transactionIDs = transactionIDs; 241 this.exclusive = exclusive; 242 } 243 244 /** 245 * Get the key. 246 * 247 * @return the key 248 */ getKey()249 public String getKey() { 250 return key; 251 } 252 253 /** 254 * Get the Transaction IDs. 255 * 256 * @return the Transaction IDs. 257 */ getTransactionIDs()258 public long[] getTransactionIDs() { 259 return transactionIDs; 260 } 261 262 /** 263 * Get the Lock status. 264 * 265 * @return true if the lock is exclusive, false if the lock is shared. 266 */ isExclusive()267 public boolean isExclusive() { 268 return exclusive; 269 } 270 } 271 272 /** 273 * Returns map of all locks held. 274 * 275 * @return a map of all the locks held. 276 */ getLockStatusData()277 public Map<Long, KeyLockInfo> getLockStatusData() { 278 return getLockStatusData(nativeHandle_); 279 } 280 281 /** 282 * Called from C++ native method {@link #getDeadlockInfoBuffer(long)} 283 * to construct a DeadlockInfo object. 284 * 285 * @param transactionID The transaction id 286 * @param columnFamilyId The id of the {@link ColumnFamilyHandle} 287 * @param waitingKey the key that we are waiting on 288 * @param exclusive true if the lock is exclusive, false if the lock is shared 289 * 290 * @return The waiting transactions 291 */ newDeadlockInfo( final long transactionID, final long columnFamilyId, final String waitingKey, final boolean exclusive)292 private DeadlockInfo newDeadlockInfo( 293 final long transactionID, final long columnFamilyId, 294 final String waitingKey, final boolean exclusive) { 295 return new DeadlockInfo(transactionID, columnFamilyId, 296 waitingKey, exclusive); 297 } 298 299 public static class DeadlockInfo { 300 private final long transactionID; 301 private final long columnFamilyId; 302 private final String waitingKey; 303 private final boolean exclusive; 304 DeadlockInfo(final long transactionID, final long columnFamilyId, final String waitingKey, final boolean exclusive)305 private DeadlockInfo(final long transactionID, final long columnFamilyId, 306 final String waitingKey, final boolean exclusive) { 307 this.transactionID = transactionID; 308 this.columnFamilyId = columnFamilyId; 309 this.waitingKey = waitingKey; 310 this.exclusive = exclusive; 311 } 312 313 /** 314 * Get the Transaction ID. 315 * 316 * @return the transaction ID 317 */ getTransactionID()318 public long getTransactionID() { 319 return transactionID; 320 } 321 322 /** 323 * Get the Column Family ID. 324 * 325 * @return The column family ID 326 */ getColumnFamilyId()327 public long getColumnFamilyId() { 328 return columnFamilyId; 329 } 330 331 /** 332 * Get the key that we are waiting on. 333 * 334 * @return the key that we are waiting on 335 */ getWaitingKey()336 public String getWaitingKey() { 337 return waitingKey; 338 } 339 340 /** 341 * Get the Lock status. 342 * 343 * @return true if the lock is exclusive, false if the lock is shared. 344 */ isExclusive()345 public boolean isExclusive() { 346 return exclusive; 347 } 348 } 349 350 public static class DeadlockPath { 351 final DeadlockInfo[] path; 352 final boolean limitExceeded; 353 DeadlockPath(final DeadlockInfo[] path, final boolean limitExceeded)354 public DeadlockPath(final DeadlockInfo[] path, final boolean limitExceeded) { 355 this.path = path; 356 this.limitExceeded = limitExceeded; 357 } 358 isEmpty()359 public boolean isEmpty() { 360 return path.length == 0 && !limitExceeded; 361 } 362 } 363 getDeadlockInfoBuffer()364 public DeadlockPath[] getDeadlockInfoBuffer() { 365 return getDeadlockInfoBuffer(nativeHandle_); 366 } 367 setDeadlockInfoBufferSize(final int targetSize)368 public void setDeadlockInfoBufferSize(final int targetSize) { 369 setDeadlockInfoBufferSize(nativeHandle_, targetSize); 370 } 371 storeTransactionDbOptions( final TransactionDBOptions transactionDbOptions)372 private void storeTransactionDbOptions( 373 final TransactionDBOptions transactionDbOptions) { 374 this.transactionDbOptions_ = transactionDbOptions; 375 } 376 disposeInternal(final long handle)377 @Override protected final native void disposeInternal(final long handle); 378 open(final long optionsHandle, final long transactionDbOptionsHandle, final String path)379 private static native long open(final long optionsHandle, 380 final long transactionDbOptionsHandle, final String path) 381 throws RocksDBException; open(final long dbOptionsHandle, final long transactionDbOptionsHandle, final String path, final byte[][] columnFamilyNames, final long[] columnFamilyOptions)382 private static native long[] open(final long dbOptionsHandle, 383 final long transactionDbOptionsHandle, final String path, 384 final byte[][] columnFamilyNames, final long[] columnFamilyOptions); closeDatabase(final long handle)385 private native static void closeDatabase(final long handle) 386 throws RocksDBException; beginTransaction(final long handle, final long writeOptionsHandle)387 private native long beginTransaction(final long handle, 388 final long writeOptionsHandle); beginTransaction(final long handle, final long writeOptionsHandle, final long transactionOptionsHandle)389 private native long beginTransaction(final long handle, 390 final long writeOptionsHandle, final long transactionOptionsHandle); beginTransaction_withOld(final long handle, final long writeOptionsHandle, final long oldTransactionHandle)391 private native long beginTransaction_withOld(final long handle, 392 final long writeOptionsHandle, final long oldTransactionHandle); beginTransaction_withOld(final long handle, final long writeOptionsHandle, final long transactionOptionsHandle, final long oldTransactionHandle)393 private native long beginTransaction_withOld(final long handle, 394 final long writeOptionsHandle, final long transactionOptionsHandle, 395 final long oldTransactionHandle); getTransactionByName(final long handle, final String name)396 private native long getTransactionByName(final long handle, 397 final String name); getAllPreparedTransactions(final long handle)398 private native long[] getAllPreparedTransactions(final long handle); getLockStatusData( final long handle)399 private native Map<Long, KeyLockInfo> getLockStatusData( 400 final long handle); getDeadlockInfoBuffer(final long handle)401 private native DeadlockPath[] getDeadlockInfoBuffer(final long handle); setDeadlockInfoBufferSize(final long handle, final int targetSize)402 private native void setDeadlockInfoBufferSize(final long handle, 403 final int targetSize); 404 } 405