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