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 // This file implements the "bridge" between Java and C++
7 // for ROCKSDB_NAMESPACE::TransactionDB.
8 
9 #include <jni.h>
10 #include <functional>
11 #include <memory>
12 #include <utility>
13 
14 #include "include/org_rocksdb_TransactionDB.h"
15 
16 #include "rocksdb/options.h"
17 #include "rocksdb/utilities/transaction.h"
18 #include "rocksdb/utilities/transaction_db.h"
19 
20 #include "rocksjni/portal.h"
21 
22 /*
23  * Class:     org_rocksdb_TransactionDB
24  * Method:    open
25  * Signature: (JJLjava/lang/String;)J
26  */
27 jlong Java_org_rocksdb_TransactionDB_open__JJLjava_lang_String_2(
28     JNIEnv* env, jclass, jlong joptions_handle,
29     jlong jtxn_db_options_handle, jstring jdb_path) {
30   auto* options =
31       reinterpret_cast<ROCKSDB_NAMESPACE::Options*>(joptions_handle);
32   auto* txn_db_options =
33       reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDBOptions*>(
34           jtxn_db_options_handle);
35   ROCKSDB_NAMESPACE::TransactionDB* tdb = nullptr;
36   const char* db_path = env->GetStringUTFChars(jdb_path, nullptr);
37   if (db_path == nullptr) {
38     // exception thrown: OutOfMemoryError
39     return 0;
40   }
41   ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::TransactionDB::Open(
42       *options, *txn_db_options, db_path, &tdb);
43   env->ReleaseStringUTFChars(jdb_path, db_path);
44 
45   if (s.ok()) {
46     return reinterpret_cast<jlong>(tdb);
47   } else {
48     ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
49     return 0;
50   }
51 }
52 
53 /*
54  * Class:     org_rocksdb_TransactionDB
55  * Method:    open
56  * Signature: (JJLjava/lang/String;[[B[J)[J
57  */
58 jlongArray Java_org_rocksdb_TransactionDB_open__JJLjava_lang_String_2_3_3B_3J(
59     JNIEnv* env, jclass, jlong jdb_options_handle,
60     jlong jtxn_db_options_handle, jstring jdb_path, jobjectArray jcolumn_names,
61     jlongArray jcolumn_options_handles) {
62   const char* db_path = env->GetStringUTFChars(jdb_path, nullptr);
63   if (db_path == nullptr) {
64     // exception thrown: OutOfMemoryError
65     return nullptr;
66   }
67 
68   const jsize len_cols = env->GetArrayLength(jcolumn_names);
69   if (env->EnsureLocalCapacity(len_cols) != 0) {
70     // out of memory
71     env->ReleaseStringUTFChars(jdb_path, db_path);
72     return nullptr;
73   }
74 
75   jlong* jco = env->GetLongArrayElements(jcolumn_options_handles, nullptr);
76   if (jco == nullptr) {
77     // exception thrown: OutOfMemoryError
78     env->ReleaseStringUTFChars(jdb_path, db_path);
79     return nullptr;
80   }
81   std::vector<ROCKSDB_NAMESPACE::ColumnFamilyDescriptor> column_families;
82   for (int i = 0; i < len_cols; i++) {
83     const jobject jcn = env->GetObjectArrayElement(jcolumn_names, i);
84     if (env->ExceptionCheck()) {
85       // exception thrown: ArrayIndexOutOfBoundsException
86       env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT);
87       env->ReleaseStringUTFChars(jdb_path, db_path);
88       return nullptr;
89     }
90     const jbyteArray jcn_ba = reinterpret_cast<jbyteArray>(jcn);
91     jbyte* jcf_name = env->GetByteArrayElements(jcn_ba, nullptr);
92     if (jcf_name == nullptr) {
93       // exception thrown: OutOfMemoryError
94       env->DeleteLocalRef(jcn);
95       env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT);
96       env->ReleaseStringUTFChars(jdb_path, db_path);
97       return nullptr;
98     }
99 
100     const int jcf_name_len = env->GetArrayLength(jcn_ba);
TargetOptions()101     if (env->EnsureLocalCapacity(jcf_name_len) != 0) {
102       // out of memory
103       env->ReleaseByteArrayElements(jcn_ba, jcf_name, JNI_ABORT);
104       env->DeleteLocalRef(jcn);
105       env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT);
106       env->ReleaseStringUTFChars(jdb_path, db_path);
107       return nullptr;
108     }
109     const std::string cf_name(reinterpret_cast<char*>(jcf_name), jcf_name_len);
110     const ROCKSDB_NAMESPACE::ColumnFamilyOptions* cf_options =
111         reinterpret_cast<ROCKSDB_NAMESPACE::ColumnFamilyOptions*>(jco[i]);
112     column_families.push_back(
113         ROCKSDB_NAMESPACE::ColumnFamilyDescriptor(cf_name, *cf_options));
114 
115     env->ReleaseByteArrayElements(jcn_ba, jcf_name, JNI_ABORT);
116     env->DeleteLocalRef(jcn);
117   }
118   env->ReleaseLongArrayElements(jcolumn_options_handles, jco, JNI_ABORT);
119 
120   auto* db_options =
121       reinterpret_cast<ROCKSDB_NAMESPACE::DBOptions*>(jdb_options_handle);
122   auto* txn_db_options =
123       reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDBOptions*>(
124           jtxn_db_options_handle);
125   std::vector<ROCKSDB_NAMESPACE::ColumnFamilyHandle*> handles;
126   ROCKSDB_NAMESPACE::TransactionDB* tdb = nullptr;
127   const ROCKSDB_NAMESPACE::Status s = ROCKSDB_NAMESPACE::TransactionDB::Open(
128       *db_options, *txn_db_options, db_path, column_families, &handles, &tdb);
129 
130   // check if open operation was successful
131   if (s.ok()) {
132     const jsize resultsLen = 1 + len_cols;  // db handle + column family handles
133     std::unique_ptr<jlong[]> results =
134         std::unique_ptr<jlong[]>(new jlong[resultsLen]);
135     results[0] = reinterpret_cast<jlong>(tdb);
136     for (int i = 1; i <= len_cols; i++) {
137       results[i] = reinterpret_cast<jlong>(handles[i - 1]);
138     }
139 
140     jlongArray jresults = env->NewLongArray(resultsLen);
141     if (jresults == nullptr) {
142       // exception thrown: OutOfMemoryError
143       return nullptr;
144     }
145     env->SetLongArrayRegion(jresults, 0, resultsLen, results.get());
146     if (env->ExceptionCheck()) {
147       // exception thrown: ArrayIndexOutOfBoundsException
148       env->DeleteLocalRef(jresults);
149       return nullptr;
150     }
151     return jresults;
152   } else {
153     ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
154     return nullptr;
155   }
156 }
157 
158 /*
159  * Class:     org_rocksdb_TransactionDB
160  * Method:    disposeInternal
161  * Signature: (J)V
162  */
163 void Java_org_rocksdb_TransactionDB_disposeInternal(
164     JNIEnv*, jobject, jlong jhandle) {
165   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
166   assert(txn_db != nullptr);
167   delete txn_db;
168 }
169 
170 /*
171  * Class:     org_rocksdb_TransactionDB
172  * Method:    closeDatabase
173  * Signature: (J)V
174  */
175 void Java_org_rocksdb_TransactionDB_closeDatabase(
176     JNIEnv* env, jclass, jlong jhandle) {
177   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
178   assert(txn_db != nullptr);
179   ROCKSDB_NAMESPACE::Status s = txn_db->Close();
180   ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
181 }
182 
183 /*
184  * Class:     org_rocksdb_TransactionDB
185  * Method:    beginTransaction
186  * Signature: (JJ)J
187  */
188 jlong Java_org_rocksdb_TransactionDB_beginTransaction__JJ(
189     JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle) {
190   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
191   auto* write_options =
192       reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
193   ROCKSDB_NAMESPACE::Transaction* txn =
194       txn_db->BeginTransaction(*write_options);
195   return reinterpret_cast<jlong>(txn);
196 }
197 
198 /*
199  * Class:     org_rocksdb_TransactionDB
200  * Method:    beginTransaction
201  * Signature: (JJJ)J
202  */
203 jlong Java_org_rocksdb_TransactionDB_beginTransaction__JJJ(
204     JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle,
205     jlong jtxn_options_handle) {
206   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
207   auto* write_options =
208       reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
209   auto* txn_options = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionOptions*>(
210       jtxn_options_handle);
211   ROCKSDB_NAMESPACE::Transaction* txn =
212       txn_db->BeginTransaction(*write_options, *txn_options);
213   return reinterpret_cast<jlong>(txn);
214 }
215 
216 /*
217  * Class:     org_rocksdb_TransactionDB
218  * Method:    beginTransaction_withOld
219  * Signature: (JJJ)J
220  */
221 jlong Java_org_rocksdb_TransactionDB_beginTransaction_1withOld__JJJ(
222     JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle,
223     jlong jold_txn_handle) {
224   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
225   auto* write_options =
226       reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
227   auto* old_txn =
228       reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jold_txn_handle);
229   ROCKSDB_NAMESPACE::TransactionOptions txn_options;
230   ROCKSDB_NAMESPACE::Transaction* txn =
231       txn_db->BeginTransaction(*write_options, txn_options, old_txn);
232 
233   // RocksJava relies on the assumption that
234   // we do not allocate a new Transaction object
235   // when providing an old_txn
236   assert(txn == old_txn);
237 
238   return reinterpret_cast<jlong>(txn);
239 }
240 
241 /*
242  * Class:     org_rocksdb_TransactionDB
243  * Method:    beginTransaction_withOld
244  * Signature: (JJJJ)J
245  */
246 jlong Java_org_rocksdb_TransactionDB_beginTransaction_1withOld__JJJJ(
247     JNIEnv*, jobject, jlong jhandle, jlong jwrite_options_handle,
248     jlong jtxn_options_handle, jlong jold_txn_handle) {
249   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
250   auto* write_options =
251       reinterpret_cast<ROCKSDB_NAMESPACE::WriteOptions*>(jwrite_options_handle);
252   auto* txn_options = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionOptions*>(
253       jtxn_options_handle);
254   auto* old_txn =
255       reinterpret_cast<ROCKSDB_NAMESPACE::Transaction*>(jold_txn_handle);
256   ROCKSDB_NAMESPACE::Transaction* txn =
257       txn_db->BeginTransaction(*write_options, *txn_options, old_txn);
258 
259   // RocksJava relies on the assumption that
260   // we do not allocate a new Transaction object
261   // when providing an old_txn
262   assert(txn == old_txn);
263 
264   return reinterpret_cast<jlong>(txn);
265 }
266 
267 /*
268  * Class:     org_rocksdb_TransactionDB
269  * Method:    getTransactionByName
270  * Signature: (JLjava/lang/String;)J
271  */
272 jlong Java_org_rocksdb_TransactionDB_getTransactionByName(
273     JNIEnv* env, jobject, jlong jhandle, jstring jname) {
274   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
275   const char* name = env->GetStringUTFChars(jname, nullptr);
276   if (name == nullptr) {
277     // exception thrown: OutOfMemoryError
278     return 0;
279   }
280   ROCKSDB_NAMESPACE::Transaction* txn = txn_db->GetTransactionByName(name);
281   env->ReleaseStringUTFChars(jname, name);
282   return reinterpret_cast<jlong>(txn);
283 }
284 
285 /*
286  * Class:     org_rocksdb_TransactionDB
287  * Method:    getAllPreparedTransactions
288  * Signature: (J)[J
289  */
290 jlongArray Java_org_rocksdb_TransactionDB_getAllPreparedTransactions(
291     JNIEnv* env, jobject, jlong jhandle) {
292   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
293   std::vector<ROCKSDB_NAMESPACE::Transaction*> txns;
294   txn_db->GetAllPreparedTransactions(&txns);
295 
296   const size_t size = txns.size();
297   assert(size < UINT32_MAX);  // does it fit in a jint?
298 
299   const jsize len = static_cast<jsize>(size);
300   std::vector<jlong> tmp(len);
301   for (jsize i = 0; i < len; ++i) {
302     tmp[i] = reinterpret_cast<jlong>(txns[i]);
303   }
304 
305   jlongArray jtxns = env->NewLongArray(len);
306   if (jtxns == nullptr) {
307     // exception thrown: OutOfMemoryError
308     return nullptr;
309   }
310   env->SetLongArrayRegion(jtxns, 0, len, tmp.data());
311   if (env->ExceptionCheck()) {
312     // exception thrown: ArrayIndexOutOfBoundsException
313     env->DeleteLocalRef(jtxns);
314     return nullptr;
315   }
316 
317   return jtxns;
318 }
319 
320 /*
321  * Class:     org_rocksdb_TransactionDB
322  * Method:    getLockStatusData
323  * Signature: (J)Ljava/util/Map;
324  */
325 jobject Java_org_rocksdb_TransactionDB_getLockStatusData(
326     JNIEnv* env, jobject, jlong jhandle) {
327   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
328   const std::unordered_multimap<uint32_t, ROCKSDB_NAMESPACE::KeyLockInfo>
329       lock_status_data = txn_db->GetLockStatusData();
330   const jobject jlock_status_data = ROCKSDB_NAMESPACE::HashMapJni::construct(
331       env, static_cast<uint32_t>(lock_status_data.size()));
332   if (jlock_status_data == nullptr) {
333     // exception occurred
334     return nullptr;
335   }
336 
337   const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV<
338       const int32_t, const ROCKSDB_NAMESPACE::KeyLockInfo, jobject, jobject>
339       fn_map_kv =
340           [env](const std::pair<const int32_t,
341                                 const ROCKSDB_NAMESPACE::KeyLockInfo>& pair) {
342             const jobject jlong_column_family_id =
343                 ROCKSDB_NAMESPACE::LongJni::valueOf(env, pair.first);
344             if (jlong_column_family_id == nullptr) {
345               // an error occurred
346               return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
347             }
348             const jobject jkey_lock_info =
349                 ROCKSDB_NAMESPACE::KeyLockInfoJni::construct(env, pair.second);
350             if (jkey_lock_info == nullptr) {
351               // an error occurred
352               return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
353             }
354             return std::unique_ptr<std::pair<jobject, jobject>>(
355                 new std::pair<jobject, jobject>(jlong_column_family_id,
356                                                 jkey_lock_info));
357           };
358 
359   if (!ROCKSDB_NAMESPACE::HashMapJni::putAll(
360           env, jlock_status_data, lock_status_data.begin(),
361           lock_status_data.end(), fn_map_kv)) {
362     // exception occcurred
363     return nullptr;
364   }
365 
366   return jlock_status_data;
367 }
368 
369 /*
370  * Class:     org_rocksdb_TransactionDB
371  * Method:    getDeadlockInfoBuffer
372  * Signature: (J)[Lorg/rocksdb/TransactionDB/DeadlockPath;
373  */
374 jobjectArray Java_org_rocksdb_TransactionDB_getDeadlockInfoBuffer(
375     JNIEnv* env, jobject jobj, jlong jhandle) {
376   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
377   const std::vector<ROCKSDB_NAMESPACE::DeadlockPath> deadlock_info_buffer =
378       txn_db->GetDeadlockInfoBuffer();
379 
380   const jsize deadlock_info_buffer_len =
381       static_cast<jsize>(deadlock_info_buffer.size());
382   jobjectArray jdeadlock_info_buffer = env->NewObjectArray(
383       deadlock_info_buffer_len,
384       ROCKSDB_NAMESPACE::DeadlockPathJni::getJClass(env), nullptr);
385   if (jdeadlock_info_buffer == nullptr) {
386     // exception thrown: OutOfMemoryError
387     return nullptr;
388   }
389   jsize jdeadlock_info_buffer_offset = 0;
390 
391   auto buf_end = deadlock_info_buffer.end();
392   for (auto buf_it = deadlock_info_buffer.begin(); buf_it != buf_end;
393        ++buf_it) {
394     const ROCKSDB_NAMESPACE::DeadlockPath deadlock_path = *buf_it;
395     const std::vector<ROCKSDB_NAMESPACE::DeadlockInfo> deadlock_infos =
396         deadlock_path.path;
397     const jsize deadlock_infos_len =
398         static_cast<jsize>(deadlock_info_buffer.size());
399     jobjectArray jdeadlock_infos = env->NewObjectArray(
400         deadlock_infos_len, ROCKSDB_NAMESPACE::DeadlockInfoJni::getJClass(env),
401         nullptr);
402     if (jdeadlock_infos == nullptr) {
403       // exception thrown: OutOfMemoryError
404       env->DeleteLocalRef(jdeadlock_info_buffer);
405       return nullptr;
406     }
407     jsize jdeadlock_infos_offset = 0;
408 
409     auto infos_end = deadlock_infos.end();
410     for (auto infos_it = deadlock_infos.begin(); infos_it != infos_end;
411          ++infos_it) {
412       const ROCKSDB_NAMESPACE::DeadlockInfo deadlock_info = *infos_it;
413       const jobject jdeadlock_info =
414           ROCKSDB_NAMESPACE::TransactionDBJni::newDeadlockInfo(
415               env, jobj, deadlock_info.m_txn_id, deadlock_info.m_cf_id,
416               deadlock_info.m_waiting_key, deadlock_info.m_exclusive);
417       if (jdeadlock_info == nullptr) {
418         // exception occcurred
419         env->DeleteLocalRef(jdeadlock_info_buffer);
420         return nullptr;
421       }
422       env->SetObjectArrayElement(jdeadlock_infos, jdeadlock_infos_offset++,
423                                  jdeadlock_info);
424       if (env->ExceptionCheck()) {
425         // exception thrown: ArrayIndexOutOfBoundsException or
426         // ArrayStoreException
427         env->DeleteLocalRef(jdeadlock_info);
428         env->DeleteLocalRef(jdeadlock_info_buffer);
429         return nullptr;
430       }
431     }
432 
433     const jobject jdeadlock_path =
434         ROCKSDB_NAMESPACE::DeadlockPathJni::construct(
435             env, jdeadlock_infos, deadlock_path.limit_exceeded);
436     if (jdeadlock_path == nullptr) {
437       // exception occcurred
438       env->DeleteLocalRef(jdeadlock_info_buffer);
439       return nullptr;
440     }
441     env->SetObjectArrayElement(jdeadlock_info_buffer,
442                                jdeadlock_info_buffer_offset++, jdeadlock_path);
443     if (env->ExceptionCheck()) {
444       // exception thrown: ArrayIndexOutOfBoundsException or ArrayStoreException
445       env->DeleteLocalRef(jdeadlock_path);
446       env->DeleteLocalRef(jdeadlock_info_buffer);
447       return nullptr;
448     }
449   }
450 
451   return jdeadlock_info_buffer;
452 }
453 
454 /*
455  * Class:     org_rocksdb_TransactionDB
456  * Method:    setDeadlockInfoBufferSize
457  * Signature: (JI)V
458  */
459 void Java_org_rocksdb_TransactionDB_setDeadlockInfoBufferSize(
460     JNIEnv*, jobject, jlong jhandle, jint jdeadlock_info_buffer_size) {
461   auto* txn_db = reinterpret_cast<ROCKSDB_NAMESPACE::TransactionDB*>(jhandle);
462   txn_db->SetDeadlockInfoBufferSize(jdeadlock_info_buffer_size);
463 }
464