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