@nxtedition/rocksdb 5.2.34 → 5.2.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/binding.cc CHANGED
@@ -30,6 +30,23 @@ class NullLogger : public rocksdb::Logger {
30
30
  struct Database;
31
31
  struct Iterator;
32
32
 
33
+ #define NAPI_STATUS_RETURN(call) \
34
+ { \
35
+ const auto status = (call); \
36
+ if (status != napi_ok) { \
37
+ return status; \
38
+ } \
39
+ }
40
+
41
+ #define NAPI_PENDING_EXCEPTION() \
42
+ { \
43
+ bool result; \
44
+ NAPI_STATUS_THROWS(napi_is_exception_pending(env, &result)); \
45
+ if (result) { \
46
+ return nullptr; \
47
+ } \
48
+ }
49
+
33
50
  #define NAPI_DB_CONTEXT() \
34
51
  Database* database = nullptr; \
35
52
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
@@ -60,21 +77,15 @@ static bool IsObject(napi_env env, napi_value value) {
60
77
  return type == napi_object;
61
78
  }
62
79
 
63
- static napi_value CreateError(napi_env env, const std::string_view& str) {
64
- napi_value msg;
65
- napi_create_string_utf8(env, str.data(), str.size(), &msg);
66
- napi_value error;
67
- napi_create_error(env, nullptr, msg, &error);
68
- return error;
69
- }
70
-
71
- static napi_value CreateCodeError(napi_env env, const std::string_view& code, const std::string_view& msg) {
72
- napi_value codeValue;
73
- napi_create_string_utf8(env, code.data(), code.size(), &codeValue);
80
+ static napi_value CreateError(napi_env env, const std::optional<std::string_view>& code, const std::string_view& msg) {
81
+ napi_value codeValue = nullptr;
82
+ if (code) {
83
+ NAPI_STATUS_THROWS(napi_create_string_utf8(env, code->data(), code->size(), &codeValue));
84
+ }
74
85
  napi_value msgValue;
75
- napi_create_string_utf8(env, msg.data(), msg.size(), &msgValue);
86
+ NAPI_STATUS_THROWS(napi_create_string_utf8(env, msg.data(), msg.size(), &msgValue));
76
87
  napi_value error;
77
- napi_create_error(env, codeValue, msgValue, &error);
88
+ NAPI_STATUS_THROWS(napi_create_error(env, codeValue, msgValue, &error));
78
89
  return error;
79
90
  }
80
91
 
@@ -85,12 +96,12 @@ static bool HasProperty(napi_env env, napi_value obj, const std::string_view& ke
85
96
  }
86
97
 
87
98
  static napi_value GetProperty(napi_env env, napi_value obj, const std::string_view& key) {
88
- napi_value value;
99
+ napi_value value = nullptr;
89
100
  napi_get_named_property(env, obj, key.data(), &value);
90
101
  return value;
91
102
  }
92
103
 
93
- static bool BooleanProperty(napi_env env, napi_value obj, const std::string_view& key, bool defaultValue) {
104
+ static std::optional<bool> BooleanProperty(napi_env env, napi_value obj, const std::string_view& key) {
94
105
  if (HasProperty(env, obj, key.data())) {
95
106
  const auto value = GetProperty(env, obj, key.data());
96
107
  bool result;
@@ -98,7 +109,7 @@ static bool BooleanProperty(napi_env env, napi_value obj, const std::string_view
98
109
  return result;
99
110
  }
100
111
 
101
- return defaultValue;
112
+ return {};
102
113
  }
103
114
 
104
115
  static bool EncodingIsBuffer(napi_env env, napi_value obj, const std::string_view& option) {
@@ -114,7 +125,7 @@ static bool EncodingIsBuffer(napi_env env, napi_value obj, const std::string_vie
114
125
  return false;
115
126
  }
116
127
 
117
- static uint32_t Uint32Property(napi_env env, napi_value obj, const std::string_view& key, uint32_t defaultValue) {
128
+ static std::optional<uint32_t> Uint32Property(napi_env env, napi_value obj, const std::string_view& key) {
118
129
  if (HasProperty(env, obj, key.data())) {
119
130
  const auto value = GetProperty(env, obj, key.data());
120
131
  uint32_t result;
@@ -122,10 +133,10 @@ static uint32_t Uint32Property(napi_env env, napi_value obj, const std::string_v
122
133
  return result;
123
134
  }
124
135
 
125
- return defaultValue;
136
+ return {};
126
137
  }
127
138
 
128
- static int Int32Property(napi_env env, napi_value obj, const std::string_view& key, int defaultValue) {
139
+ static std::optional<int> Int32Property(napi_env env, napi_value obj, const std::string_view& key) {
129
140
  if (HasProperty(env, obj, key.data())) {
130
141
  const auto value = GetProperty(env, obj, key.data());
131
142
  int result;
@@ -133,7 +144,7 @@ static int Int32Property(napi_env env, napi_value obj, const std::string_view& k
133
144
  return result;
134
145
  }
135
146
 
136
- return defaultValue;
147
+ return {};
137
148
  }
138
149
 
139
150
  static std::string ToString(napi_env env, napi_value from, const std::string& defaultValue = "") {
@@ -153,42 +164,19 @@ static std::string ToString(napi_env env, napi_value from, const std::string& de
153
164
  return defaultValue;
154
165
  }
155
166
 
156
- static std::string StringProperty(napi_env env,
157
- napi_value obj,
158
- const std::string_view& key,
159
- const std::string& defaultValue = "") {
160
- if (HasProperty(env, obj, key)) {
161
- napi_value value = GetProperty(env, obj, key);
162
- if (IsString(env, value)) {
163
- return ToString(env, value);
164
- }
165
- }
166
-
167
- return defaultValue;
168
- }
169
-
170
- static std::optional<std::string> RangeOption(napi_env env, napi_value opts, const std::string_view& name) {
167
+ static std::optional<std::string> StringProperty(napi_env env, napi_value opts, const std::string_view& name) {
171
168
  if (HasProperty(env, opts, name)) {
172
169
  const auto value = GetProperty(env, opts, name);
173
170
  return ToString(env, value);
174
171
  }
175
-
176
172
  return {};
177
173
  }
178
174
 
179
175
  static napi_status CallFunction(napi_env env, napi_value callback, const int argc, napi_value* argv) {
180
- napi_status status;
181
176
  napi_value global;
182
177
 
183
- status = napi_get_global(env, &global);
184
- if (status != napi_ok) {
185
- return status;
186
- }
187
-
188
- status = napi_call_function(env, global, callback, argc, argv, nullptr);
189
- if (status != napi_ok) {
190
- return status;
191
- }
178
+ NAPI_STATUS_RETURN(napi_get_global(env, &global));
179
+ NAPI_STATUS_RETURN(napi_call_function(env, global, callback, argc, argv, nullptr));
192
180
 
193
181
  return napi_ok;
194
182
  }
@@ -201,28 +189,45 @@ static napi_value ToError(napi_env env, const rocksdb::Status& status) {
201
189
  const auto msg = status.ToString();
202
190
 
203
191
  if (status.IsNotFound()) {
204
- return CreateCodeError(env, "LEVEL_NOT_FOUND", msg);
192
+ return CreateError(env, "LEVEL_NOT_FOUND", msg);
205
193
  } else if (status.IsCorruption()) {
206
- return CreateCodeError(env, "LEVEL_CORRUPTION", msg);
194
+ return CreateError(env, "LEVEL_CORRUPTION", msg);
207
195
  } else if (status.IsIOError()) {
208
196
  if (msg.find("IO error: lock ") != std::string::npos) { // env_posix.cc
209
- return CreateCodeError(env, "LEVEL_LOCKED", msg);
197
+ return CreateError(env, "LEVEL_LOCKED", msg);
210
198
  } else if (msg.find("IO error: LockFile ") != std::string::npos) { // env_win.cc
211
- return CreateCodeError(env, "LEVEL_LOCKED", msg);
199
+ return CreateError(env, "LEVEL_LOCKED", msg);
212
200
  } else if (msg.find("IO error: While lock file") != std::string::npos) { // env_mac.cc
213
- return CreateCodeError(env, "LEVEL_LOCKED", msg);
201
+ return CreateError(env, "LEVEL_LOCKED", msg);
214
202
  } else {
215
- return CreateCodeError(env, "LEVEL_IO_ERROR", msg);
203
+ return CreateError(env, "LEVEL_IO_ERROR", msg);
216
204
  }
217
205
  }
218
206
 
219
- return CreateError(env, msg);
207
+ return CreateError(env, {}, msg);
220
208
  }
221
209
 
222
210
  template <typename T>
223
- napi_status Convert(napi_env env, const T& s, bool asBuffer, napi_value& result) {
211
+ static void Finalize(napi_env env, void* data, void* hint) {
212
+ if (hint) {
213
+ delete reinterpret_cast<T*>(hint);
214
+ }
215
+ }
216
+
217
+ napi_status Convert(napi_env env, std::string s, bool asBuffer, napi_value& result) {
218
+ if (asBuffer) {
219
+ auto ptr = new std::string(std::move(s));
220
+ return napi_create_external_buffer(env, ptr->size(), ptr->data(), Finalize<std::string>, ptr, &result);
221
+ } else {
222
+ return napi_create_string_utf8(env, s.data(), s.size(), &result);
223
+ }
224
+ }
225
+
226
+ napi_status Convert(napi_env env, rocksdb::PinnableSlice s, bool asBuffer, napi_value& result) {
224
227
  if (asBuffer) {
225
- return napi_create_buffer_copy(env, s.size(), s.data(), nullptr, &result);
228
+ auto ptr = new rocksdb::PinnableSlice(std::move(s));
229
+ return napi_create_external_buffer(env, ptr->size(), const_cast<char*>(ptr->data()), Finalize<rocksdb::PinnableSlice>, ptr,
230
+ &result);
226
231
  } else {
227
232
  return napi_create_string_utf8(env, s.data(), s.size(), &result);
228
233
  }
@@ -231,7 +236,7 @@ napi_status Convert(napi_env env, const T& s, bool asBuffer, napi_value& result)
231
236
  struct NapiSlice : public rocksdb::Slice {
232
237
  NapiSlice(napi_env env, napi_value from) {
233
238
  if (IsString(env, from)) {
234
- napi_get_value_string_utf8(env, from, nullptr, 0, &size_);
239
+ NAPI_STATUS_THROWS_VOID(napi_get_value_string_utf8(env, from, nullptr, 0, &size_));
235
240
  char* data;
236
241
  if (size_ + 1 < stack_.size()) {
237
242
  data = stack_.data();
@@ -240,17 +245,17 @@ struct NapiSlice : public rocksdb::Slice {
240
245
  data = heap_.get();
241
246
  }
242
247
  data[size_] = 0;
243
- napi_get_value_string_utf8(env, from, data, size_ + 1, &size_);
248
+ NAPI_STATUS_THROWS_VOID(napi_get_value_string_utf8(env, from, data, size_ + 1, &size_));
244
249
  data_ = data;
245
250
  } else if (IsBuffer(env, from)) {
246
251
  void* data;
247
- napi_get_buffer_info(env, from, &data, &size_);
252
+ NAPI_STATUS_THROWS_VOID(napi_get_buffer_info(env, from, &data, &size_));
248
253
  data_ = static_cast<char*>(data);
249
254
  }
250
255
  }
251
256
 
252
257
  std::unique_ptr<char[]> heap_;
253
- std::array<char, 8192> stack_;
258
+ std::array<char, 1024> stack_;
254
259
  };
255
260
 
256
261
  /**
@@ -263,13 +268,12 @@ struct NapiSlice : public rocksdb::Slice {
263
268
  * - Destroy (main thread): do cleanup regardless of success
264
269
  */
265
270
  struct Worker {
266
- Worker(napi_env env, Database* database, napi_value callback, const std::string& resourceName)
267
- : database_(database) {
271
+ Worker(napi_env env, Database* database, napi_value callback, const std::string& resourceName) : database_(database) {
268
272
  NAPI_STATUS_THROWS_VOID(napi_create_reference(env, callback, 1, &callbackRef_));
269
273
  napi_value asyncResourceName;
270
274
  NAPI_STATUS_THROWS_VOID(napi_create_string_utf8(env, resourceName.data(), resourceName.size(), &asyncResourceName));
271
- NAPI_STATUS_THROWS_VOID(napi_create_async_work(env, callback, asyncResourceName, Worker::Execute,
272
- Worker::Complete, this, &asyncWork_));
275
+ NAPI_STATUS_THROWS_VOID(
276
+ napi_create_async_work(env, callback, asyncResourceName, Worker::Execute, Worker::Complete, this, &asyncWork_));
273
277
  }
274
278
 
275
279
  virtual ~Worker() {}
@@ -282,6 +286,8 @@ struct Worker {
282
286
  static void Complete(napi_env env, napi_status status, void* data) {
283
287
  auto self = reinterpret_cast<Worker*>(data);
284
288
 
289
+ // TODO (fix): napi status handling...
290
+
285
291
  napi_value callback;
286
292
  napi_get_reference_value(env, self->callbackRef_, &callback);
287
293
 
@@ -301,13 +307,15 @@ struct Worker {
301
307
 
302
308
  virtual rocksdb::Status Execute(Database& database) = 0;
303
309
 
304
- virtual void OnOk(napi_env env, napi_value callback) {
310
+ virtual napi_status OnOk(napi_env env, napi_value callback) {
305
311
  napi_value argv;
306
- napi_get_null(env, &argv);
307
- CallFunction(env, callback, 1, &argv);
312
+ NAPI_STATUS_RETURN(napi_get_null(env, &argv));
313
+ return CallFunction(env, callback, 1, &argv);
308
314
  }
309
315
 
310
- virtual void OnError(napi_env env, napi_value callback, napi_value err) { CallFunction(env, callback, 1, &err); }
316
+ virtual napi_status OnError(napi_env env, napi_value callback, napi_value err) {
317
+ return CallFunction(env, callback, 1, &err);
318
+ }
311
319
 
312
320
  virtual void Destroy(napi_env env) {}
313
321
 
@@ -322,20 +330,6 @@ struct Worker {
322
330
  };
323
331
 
324
332
  struct Database {
325
- rocksdb::Status Open(const rocksdb::Options& options, const bool readOnly, const char* location) {
326
- if (readOnly) {
327
- rocksdb::DB* db = nullptr;
328
- const auto status = rocksdb::DB::OpenForReadOnly(options, location, &db);
329
- db_.reset(db);
330
- return status;
331
- } else {
332
- rocksdb::DB* db = nullptr;
333
- const auto status = rocksdb::DB::Open(options, location, &db);
334
- db_.reset(db);
335
- return status;
336
- }
337
- }
338
-
339
333
  void AttachIterator(napi_env env, Iterator* iterator) {
340
334
  iterators_.insert(iterator);
341
335
  IncrementPriorityWork(env);
@@ -346,10 +340,10 @@ struct Database {
346
340
  DecrementPriorityWork(env);
347
341
  }
348
342
 
349
- void IncrementPriorityWork(napi_env env) { napi_reference_ref(env, prioritRef_, &priorityWork_); }
343
+ void IncrementPriorityWork(napi_env env) { napi_reference_ref(env, priorityRef_, &priorityWork_); }
350
344
 
351
345
  void DecrementPriorityWork(napi_env env) {
352
- napi_reference_unref(env, prioritRef_, &priorityWork_);
346
+ napi_reference_unref(env, priorityRef_, &priorityWork_);
353
347
 
354
348
  if (priorityWork_ == 0 && pendingCloseWorker_) {
355
349
  pendingCloseWorker_->Queue(env);
@@ -362,7 +356,7 @@ struct Database {
362
356
  std::unique_ptr<rocksdb::DB> db_;
363
357
  Worker* pendingCloseWorker_;
364
358
  std::set<Iterator*> iterators_;
365
- napi_ref prioritRef_;
359
+ napi_ref priorityRef_;
366
360
 
367
361
  private:
368
362
  uint32_t priorityWork_ = 0;
@@ -378,33 +372,40 @@ struct BaseIterator {
378
372
  const int limit,
379
373
  const bool fillCache)
380
374
  : database_(database),
381
- lt_(!lte ? lt : *lte + '\0'),
382
- gte_(gte ? gte : (gt ? std::optional<std::string>(*gt + '\0') : std::nullopt)),
383
375
  snapshot_(database_->db_->GetSnapshot(),
384
376
  [this](const rocksdb::Snapshot* ptr) { database_->db_->ReleaseSnapshot(ptr); }),
385
- iterator_(database->db_->NewIterator([&] {
386
- rocksdb::ReadOptions options;
387
- if (lt_) {
388
- upper_bound_ = rocksdb::Slice(lt_->data(), lt_->size());
389
- options.iterate_upper_bound = &upper_bound_;
390
- }
391
- if (gte_) {
392
- lower_bound_ = rocksdb::Slice(gte_->data(), gte_->size());
393
- options.iterate_lower_bound = &lower_bound_;
394
- }
395
- options.fill_cache = fillCache;
396
- options.snapshot = snapshot_.get();
397
- return options;
398
- }())),
399
377
  reverse_(reverse),
400
- limit_(limit) {}
378
+ limit_(limit),
379
+ fillCache_(fillCache) {
380
+ if (lte) {
381
+ upper_bound_ = rocksdb::PinnableSlice();
382
+ *upper_bound_->GetSelf() = std::move(*lte) + '\0';
383
+ upper_bound_->PinSelf();
384
+ } else if (lt) {
385
+ upper_bound_ = rocksdb::PinnableSlice();
386
+ *upper_bound_->GetSelf() = std::move(*lt);
387
+ upper_bound_->PinSelf();
388
+ }
389
+
390
+ if (gte) {
391
+ lower_bound_ = rocksdb::PinnableSlice();
392
+ *lower_bound_->GetSelf() = std::move(*gte);
393
+ lower_bound_->PinSelf();
394
+ } else if (gt) {
395
+ lower_bound_ = rocksdb::PinnableSlice();
396
+ *lower_bound_->GetSelf() = std::move(*gt) + '\0';
397
+ lower_bound_->PinSelf();
398
+ }
399
+ }
401
400
 
402
401
  virtual ~BaseIterator() { assert(!iterator_); }
403
402
 
404
- bool DidSeek() const { return didSeek_; }
403
+ bool DidSeek() const { return iterator_ != nullptr; }
405
404
 
406
405
  void SeekToRange() {
407
- didSeek_ = true;
406
+ if (!iterator_) {
407
+ Init();
408
+ }
408
409
 
409
410
  if (reverse_) {
410
411
  iterator_->SeekToLast();
@@ -413,10 +414,12 @@ struct BaseIterator {
413
414
  }
414
415
  }
415
416
 
416
- void Seek(const std::string& target) {
417
- didSeek_ = true;
417
+ void Seek(const rocksdb::Slice& target) {
418
+ if (!iterator_) {
419
+ Init();
420
+ }
418
421
 
419
- if ((lt_ && target.compare(*lt_) >= 0) || (gte_ && target.compare(*gte_) < 0)) {
422
+ if ((upper_bound_ && target.compare(*upper_bound_) >= 0) || (lower_bound_ && target.compare(*lower_bound_) < 0)) {
420
423
  // TODO (fix): Why is this required? Seek should handle it?
421
424
  // https://github.com/facebook/rocksdb/issues/9904
422
425
  iterator_->SeekToLast();
@@ -455,16 +458,28 @@ struct BaseIterator {
455
458
  Database* database_;
456
459
 
457
460
  private:
458
- const std::optional<std::string> lt_;
459
- const std::optional<std::string> gte_;
460
- rocksdb::Slice lower_bound_;
461
- rocksdb::Slice upper_bound_;
461
+ void Init() {
462
+ rocksdb::ReadOptions options;
463
+ if (upper_bound_) {
464
+ options.iterate_upper_bound = &*upper_bound_;
465
+ }
466
+ if (lower_bound_) {
467
+ options.iterate_lower_bound = &*lower_bound_;
468
+ }
469
+ options.fill_cache = fillCache_;
470
+ options.snapshot = snapshot_.get();
471
+
472
+ iterator_.reset(database_->db_->NewIterator(options));
473
+ }
474
+
475
+ std::optional<rocksdb::PinnableSlice> lower_bound_;
476
+ std::optional<rocksdb::PinnableSlice> upper_bound_;
462
477
  std::shared_ptr<const rocksdb::Snapshot> snapshot_;
463
478
  std::unique_ptr<rocksdb::Iterator> iterator_;
464
- bool didSeek_ = false;
465
479
  const bool reverse_;
466
480
  const int limit_;
467
481
  int count_ = 0;
482
+ const bool fillCache_;
468
483
  };
469
484
 
470
485
  struct Iterator final : public BaseIterator {
@@ -486,9 +501,7 @@ struct Iterator final : public BaseIterator {
486
501
  values_(values),
487
502
  keyAsBuffer_(keyAsBuffer),
488
503
  valueAsBuffer_(valueAsBuffer),
489
- highWaterMarkBytes_(highWaterMarkBytes),
490
- first_(true),
491
- ref_(nullptr) {}
504
+ highWaterMarkBytes_(highWaterMarkBytes) {}
492
505
 
493
506
  void Attach(napi_env env, napi_value context) {
494
507
  napi_create_reference(env, context, 1, &ref_);
@@ -507,10 +520,10 @@ struct Iterator final : public BaseIterator {
507
520
  const bool keyAsBuffer_;
508
521
  const bool valueAsBuffer_;
509
522
  const uint32_t highWaterMarkBytes_;
510
- bool first_;
523
+ bool first_ = true;
511
524
 
512
525
  private:
513
- napi_ref ref_;
526
+ napi_ref ref_ = nullptr;
514
527
  };
515
528
 
516
529
  /**
@@ -543,8 +556,8 @@ static void FinalizeDatabase(napi_env env, void* data, void* hint) {
543
556
  if (data) {
544
557
  auto database = reinterpret_cast<Database*>(data);
545
558
  napi_remove_env_cleanup_hook(env, env_cleanup_hook, database);
546
- if (database->prioritRef_)
547
- napi_delete_reference(env, database->prioritRef_);
559
+ if (database->priorityRef_)
560
+ napi_delete_reference(env, database->priorityRef_);
548
561
  delete database;
549
562
  }
550
563
  }
@@ -556,7 +569,7 @@ NAPI_METHOD(db_init) {
556
569
  napi_value result;
557
570
  NAPI_STATUS_THROWS(napi_create_external(env, database, FinalizeDatabase, nullptr, &result));
558
571
 
559
- NAPI_STATUS_THROWS(napi_create_reference(env, result, 0, &database->prioritRef_));
572
+ NAPI_STATUS_THROWS(napi_create_reference(env, result, 0, &database->priorityRef_));
560
573
 
561
574
  return result;
562
575
  }
@@ -566,71 +579,21 @@ struct OpenWorker final : public Worker {
566
579
  Database* database,
567
580
  napi_value callback,
568
581
  const std::string& location,
569
- const bool createIfMissing,
570
- const bool errorIfExists,
571
- const bool compression,
572
- const uint32_t writeBufferSize,
573
- const uint32_t blockSize,
574
- const uint32_t maxOpenFiles,
575
- const uint32_t blockRestartInterval,
576
- const uint32_t maxFileSize,
577
- const uint32_t cacheSize,
578
- const std::string& infoLogLevel,
582
+ rocksdb::Options options,
579
583
  const bool readOnly)
580
- : Worker(env, database, callback, "leveldown.db.open"), readOnly_(readOnly), location_(location) {
581
- options_.create_if_missing = createIfMissing;
582
- options_.error_if_exists = errorIfExists;
583
- options_.compression = compression ? rocksdb::kSnappyCompression : rocksdb::kNoCompression;
584
- options_.write_buffer_size = writeBufferSize;
585
- options_.max_open_files = maxOpenFiles;
586
- options_.max_log_file_size = maxFileSize;
587
- options_.use_adaptive_mutex = true;
588
-
589
- if (infoLogLevel.size() > 0) {
590
- rocksdb::InfoLogLevel lvl = {};
591
-
592
- if (infoLogLevel == "debug")
593
- lvl = rocksdb::InfoLogLevel::DEBUG_LEVEL;
594
- else if (infoLogLevel == "info")
595
- lvl = rocksdb::InfoLogLevel::INFO_LEVEL;
596
- else if (infoLogLevel == "warn")
597
- lvl = rocksdb::InfoLogLevel::WARN_LEVEL;
598
- else if (infoLogLevel == "error")
599
- lvl = rocksdb::InfoLogLevel::ERROR_LEVEL;
600
- else if (infoLogLevel == "fatal")
601
- lvl = rocksdb::InfoLogLevel::FATAL_LEVEL;
602
- else if (infoLogLevel == "header")
603
- lvl = rocksdb::InfoLogLevel::HEADER_LEVEL;
604
- else
605
- napi_throw_error(env, nullptr, "invalid log level");
584
+ : Worker(env, database, callback, "leveldown.db.open"),
585
+ options_(options),
586
+ readOnly_(readOnly),
587
+ location_(location) {}
606
588
 
607
- options_.info_log_level = lvl;
608
- } else {
609
- // In some places RocksDB checks this option to see if it should prepare
610
- // debug information (ahead of logging), so set it to the highest level.
611
- options_.info_log_level = rocksdb::InfoLogLevel::HEADER_LEVEL;
612
- options_.info_log.reset(new NullLogger());
613
- }
614
-
615
- rocksdb::BlockBasedTableOptions tableOptions;
616
-
617
- if (cacheSize) {
618
- tableOptions.block_cache = rocksdb::NewLRUCache(cacheSize);
619
- } else {
620
- tableOptions.no_block_cache = true;
621
- }
622
-
623
- tableOptions.block_size = blockSize;
624
- tableOptions.block_restart_interval = blockRestartInterval;
625
- tableOptions.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10));
626
- tableOptions.format_version = 5;
627
- tableOptions.checksum = rocksdb::kxxHash64;
628
-
629
- options_.table_factory.reset(rocksdb::NewBlockBasedTableFactory(tableOptions));
589
+ rocksdb::Status Execute(Database& database) override {
590
+ rocksdb::DB* db;
591
+ const auto status = readOnly_ ? rocksdb::DB::OpenForReadOnly(options_, location_, &db)
592
+ : rocksdb::DB::Open(options_, location_, &db);
593
+ database.db_.reset(db);
594
+ return status;
630
595
  }
631
596
 
632
- rocksdb::Status Execute(Database& database) override { return database.Open(options_, readOnly_, location_.c_str()); }
633
-
634
597
  rocksdb::Options options_;
635
598
  const bool readOnly_;
636
599
  const std::string location_;
@@ -640,27 +603,68 @@ NAPI_METHOD(db_open) {
640
603
  NAPI_ARGV(4);
641
604
  NAPI_DB_CONTEXT();
642
605
 
606
+ rocksdb::Options options;
607
+
608
+ options.IncreaseParallelism(Uint32Property(env, argv[2], "parallelism").value_or(4));
609
+
643
610
  const auto location = ToString(env, argv[1]);
644
- const auto options = argv[2];
645
- const auto createIfMissing = BooleanProperty(env, options, "createIfMissing", true);
646
- const auto errorIfExists = BooleanProperty(env, options, "errorIfExists", false);
647
- const auto compression = BooleanProperty(env, options, "compression", true);
648
- const auto readOnly = BooleanProperty(env, options, "readOnly", false);
611
+ options.create_if_missing = BooleanProperty(env, argv[2], "createIfMissing").value_or(true);
612
+ options.error_if_exists = BooleanProperty(env, argv[2], "errorIfExists").value_or(false);
613
+ options.compression = BooleanProperty(env, argv[2], "compression").value_or((true)) ? rocksdb::kSnappyCompression
614
+ : rocksdb::kNoCompression;
615
+ options.use_adaptive_mutex = true;
616
+
617
+ const auto infoLogLevel = StringProperty(env, argv[2], "infoLogLevel").value_or("");
618
+ if (infoLogLevel.size() > 0) {
619
+ rocksdb::InfoLogLevel lvl = {};
620
+
621
+ if (infoLogLevel == "debug")
622
+ lvl = rocksdb::InfoLogLevel::DEBUG_LEVEL;
623
+ else if (infoLogLevel == "info")
624
+ lvl = rocksdb::InfoLogLevel::INFO_LEVEL;
625
+ else if (infoLogLevel == "warn")
626
+ lvl = rocksdb::InfoLogLevel::WARN_LEVEL;
627
+ else if (infoLogLevel == "error")
628
+ lvl = rocksdb::InfoLogLevel::ERROR_LEVEL;
629
+ else if (infoLogLevel == "fatal")
630
+ lvl = rocksdb::InfoLogLevel::FATAL_LEVEL;
631
+ else if (infoLogLevel == "header")
632
+ lvl = rocksdb::InfoLogLevel::HEADER_LEVEL;
633
+ else
634
+ napi_throw_error(env, nullptr, "invalid log level");
635
+
636
+ options.info_log_level = lvl;
637
+ } else {
638
+ // In some places RocksDB checks this option to see if it should prepare
639
+ // debug information (ahead of logging), so set it to the highest level.
640
+ options.info_log_level = rocksdb::InfoLogLevel::HEADER_LEVEL;
641
+ options.info_log.reset(new NullLogger());
642
+ }
649
643
 
650
- const auto infoLogLevel = StringProperty(env, options, "infoLogLevel");
644
+ const auto readOnly = BooleanProperty(env, argv[2], "readOnly").value_or(false);
645
+ const auto cacheSize = Uint32Property(env, argv[2], "cacheSize").value_or(8 << 20);
651
646
 
652
- const auto cacheSize = Uint32Property(env, options, "cacheSize", 8 << 20);
653
- const auto writeBufferSize = Uint32Property(env, options, "writeBufferSize", 4 << 20);
654
- const auto blockSize = Uint32Property(env, options, "blockSize", 4096);
655
- const auto maxOpenFiles = Uint32Property(env, options, "maxOpenFiles", 1000);
656
- const auto blockRestartInterval = Uint32Property(env, options, "blockRestartInterval", 16);
657
- const auto maxFileSize = Uint32Property(env, options, "maxFileSize", 2 << 20);
647
+ rocksdb::BlockBasedTableOptions tableOptions;
648
+
649
+ if (cacheSize) {
650
+ tableOptions.block_cache = rocksdb::NewLRUCache(cacheSize);
651
+ } else {
652
+ tableOptions.no_block_cache = true;
653
+ }
654
+
655
+ tableOptions.block_size = Uint32Property(env, argv[2], "blockSize").value_or(4096);
656
+ tableOptions.block_restart_interval = Uint32Property(env, argv[2], "blockRestartInterval").value_or(16);
657
+ tableOptions.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10));
658
+ tableOptions.format_version = 5;
659
+ tableOptions.checksum = rocksdb::kxxHash64;
660
+
661
+ options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(tableOptions));
658
662
 
659
663
  const auto callback = argv[3];
660
664
 
661
- auto worker =
662
- new OpenWorker(env, database, callback, location, createIfMissing, errorIfExists, compression, writeBufferSize,
663
- blockSize, maxOpenFiles, blockRestartInterval, maxFileSize, cacheSize, infoLogLevel, readOnly);
665
+ NAPI_PENDING_EXCEPTION();
666
+
667
+ auto worker = new OpenWorker(env, database, callback, location, options, readOnly);
664
668
  worker->Queue(env);
665
669
 
666
670
  return 0;
@@ -698,11 +702,13 @@ NAPI_METHOD(db_put) {
698
702
  NAPI_ARGV(4);
699
703
  NAPI_DB_CONTEXT();
700
704
 
701
- const auto key = ToString(env, argv[1]);
702
- const auto value = ToString(env, argv[2]);
705
+ const auto key = NapiSlice(env, argv[1]);
706
+ const auto val = NapiSlice(env, argv[2]);
707
+
708
+ NAPI_PENDING_EXCEPTION();
703
709
 
704
710
  rocksdb::WriteOptions options;
705
- return ToError(env, database->db_->Put(options, key, value));
711
+ return ToError(env, database->db_->Put(options, key, val));
706
712
  }
707
713
 
708
714
  struct GetWorker final : public Worker {
@@ -726,17 +732,19 @@ struct GetWorker final : public Worker {
726
732
  options.fill_cache = fillCache_;
727
733
  options.snapshot = snapshot_.get();
728
734
 
729
- auto status = database.db_->Get(options, database.db_->DefaultColumnFamily(), key_, &value_);
735
+ auto status = database.db_->Get(options, key_, &value_);
736
+
737
+ key_.clear();
730
738
  snapshot_ = nullptr;
731
739
 
732
740
  return status;
733
741
  }
734
742
 
735
- void OnOk(napi_env env, napi_value callback) override {
743
+ napi_status OnOk(napi_env env, napi_value callback) override {
736
744
  napi_value argv[2];
737
- napi_get_null(env, &argv[0]);
738
- Convert(env, std::move(value_), asBuffer_, argv[1]);
739
- CallFunction(env, callback, 2, argv);
745
+ NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
746
+ NAPI_STATUS_RETURN(Convert(env, std::move(value_), asBuffer_, argv[1]));
747
+ return CallFunction(env, callback, 2, argv);
740
748
  }
741
749
 
742
750
  void Destroy(napi_env env) override {
@@ -745,8 +753,8 @@ struct GetWorker final : public Worker {
745
753
  }
746
754
 
747
755
  private:
748
- const std::string key_;
749
- rocksdb::PinnableSlice value_;
756
+ std::string key_;
757
+ std::string value_;
750
758
  const bool asBuffer_;
751
759
  const bool fillCache_;
752
760
  std::shared_ptr<const rocksdb::Snapshot> snapshot_;
@@ -759,9 +767,11 @@ NAPI_METHOD(db_get) {
759
767
  const auto key = ToString(env, argv[1]);
760
768
  const auto options = argv[2];
761
769
  const auto asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
762
- const auto fillCache = BooleanProperty(env, options, "fillCache", true);
770
+ const auto fillCache = BooleanProperty(env, options, "fillCache").value_or(true);
763
771
  const auto callback = argv[3];
764
772
 
773
+ NAPI_PENDING_EXCEPTION();
774
+
765
775
  auto worker = new GetWorker(env, database, callback, key, asBuffer, fillCache);
766
776
  worker->Queue(env);
767
777
 
@@ -789,10 +799,24 @@ struct GetManyWorker final : public Worker {
789
799
  options.fill_cache = fillCache_;
790
800
  options.snapshot = snapshot_.get();
791
801
 
792
- status_ = database.db_->MultiGet(options, std::vector<rocksdb::Slice>(keys_.begin(), keys_.end()), &values_);
802
+ const auto numKeys = keys_.size();
803
+
804
+ std::vector<rocksdb::Slice> keys;
805
+ keys.reserve(keys_.size());
806
+ for (const auto& key : keys_) {
807
+ keys.emplace_back(key);
808
+ }
809
+
810
+ statuses_.resize(numKeys);
811
+ values_.resize(numKeys);
812
+
813
+ database.db_->MultiGet(options, database.db_->DefaultColumnFamily(), numKeys, keys.data(), values_.data(),
814
+ statuses_.data());
815
+
816
+ keys_.clear();
793
817
  snapshot_ = nullptr;
794
818
 
795
- for (auto status : status_) {
819
+ for (auto status : statuses_) {
796
820
  if (!status.ok() && !status.IsNotFound()) {
797
821
  return status;
798
822
  }
@@ -801,26 +825,29 @@ struct GetManyWorker final : public Worker {
801
825
  return rocksdb::Status::OK();
802
826
  }
803
827
 
804
- void OnOk(napi_env env, napi_value callback) override {
828
+ napi_status OnOk(napi_env env, napi_value callback) override {
805
829
  const auto size = values_.size();
806
830
 
807
831
  napi_value array;
808
- napi_create_array_with_length(env, size, &array);
832
+ NAPI_STATUS_RETURN(napi_create_array_with_length(env, size, &array));
809
833
 
810
834
  for (size_t idx = 0; idx < size; idx++) {
811
835
  napi_value element;
812
- if (status_[idx].ok()) {
813
- Convert(env, values_[idx], valueAsBuffer_, element);
836
+ if (statuses_[idx].ok()) {
837
+ NAPI_STATUS_RETURN(Convert(env, std::move(values_[idx]), valueAsBuffer_, element));
814
838
  } else {
815
- napi_get_undefined(env, &element);
839
+ NAPI_STATUS_RETURN(napi_get_undefined(env, &element));
816
840
  }
817
- napi_set_element(env, array, static_cast<uint32_t>(idx), element);
841
+ NAPI_STATUS_RETURN(napi_set_element(env, array, static_cast<uint32_t>(idx), element));
818
842
  }
819
843
 
844
+ values_.clear();
845
+ statuses_.clear();
846
+
820
847
  napi_value argv[2];
821
- napi_get_null(env, &argv[0]);
848
+ NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
822
849
  argv[1] = array;
823
- CallFunction(env, callback, 2, argv);
850
+ return CallFunction(env, callback, 2, argv);
824
851
  }
825
852
 
826
853
  void Destroy(napi_env env) override {
@@ -829,9 +856,9 @@ struct GetManyWorker final : public Worker {
829
856
  }
830
857
 
831
858
  private:
832
- const std::vector<std::string> keys_;
833
- std::vector<std::string> values_;
834
- std::vector<rocksdb::Status> status_;
859
+ std::vector<std::string> keys_;
860
+ std::vector<rocksdb::PinnableSlice> values_;
861
+ std::vector<rocksdb::Status> statuses_;
835
862
  const bool valueAsBuffer_;
836
863
  const bool fillCache_;
837
864
  std::shared_ptr<const rocksdb::Snapshot> snapshot_;
@@ -858,9 +885,11 @@ NAPI_METHOD(db_get_many) {
858
885
 
859
886
  const auto options = argv[2];
860
887
  const bool asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
861
- const bool fillCache = BooleanProperty(env, options, "fillCache", true);
888
+ const bool fillCache = BooleanProperty(env, options, "fillCache").value_or(true);
862
889
  const auto callback = argv[3];
863
890
 
891
+ NAPI_PENDING_EXCEPTION();
892
+
864
893
  auto worker = new GetManyWorker(env, database, keys, callback, asBuffer, fillCache);
865
894
  worker->Queue(env);
866
895
 
@@ -871,7 +900,9 @@ NAPI_METHOD(db_del) {
871
900
  NAPI_ARGV(3);
872
901
  NAPI_DB_CONTEXT();
873
902
 
874
- const auto key = ToString(env, argv[1]);
903
+ const auto key = NapiSlice(env, argv[1]);
904
+
905
+ NAPI_PENDING_EXCEPTION();
875
906
 
876
907
  rocksdb::WriteOptions options;
877
908
  return ToError(env, database->db_->Delete(options, key));
@@ -881,13 +912,13 @@ NAPI_METHOD(db_clear) {
881
912
  NAPI_ARGV(2);
882
913
  NAPI_DB_CONTEXT();
883
914
 
884
- const auto reverse = BooleanProperty(env, argv[1], "reverse", false);
885
- const auto limit = Int32Property(env, argv[1], "limit", -1);
915
+ const auto reverse = BooleanProperty(env, argv[1], "reverse").value_or(false);
916
+ const auto limit = Int32Property(env, argv[1], "limit").value_or(-1);
886
917
 
887
- const auto lt = RangeOption(env, argv[1], "lt");
888
- const auto lte = RangeOption(env, argv[1], "lte");
889
- const auto gt = RangeOption(env, argv[1], "gt");
890
- const auto gte = RangeOption(env, argv[1], "gte");
918
+ const auto lt = StringProperty(env, argv[1], "lt");
919
+ const auto lte = StringProperty(env, argv[1], "lte");
920
+ const auto gt = StringProperty(env, argv[1], "gt");
921
+ const auto gte = StringProperty(env, argv[1], "gte");
891
922
 
892
923
  // TODO (perf): Use DeleteRange.
893
924
 
@@ -934,51 +965,47 @@ NAPI_METHOD(db_get_property) {
934
965
  NAPI_ARGV(2);
935
966
  NAPI_DB_CONTEXT();
936
967
 
937
- const auto property = ToString(env, argv[1]);
968
+ const auto property = NapiSlice(env, argv[1]);
969
+
970
+ NAPI_PENDING_EXCEPTION();
938
971
 
939
972
  std::string value;
940
973
  database->db_->GetProperty(property, &value);
941
974
 
942
975
  napi_value result;
943
- napi_create_string_utf8(env, value.data(), value.size(), &result);
976
+ NAPI_STATUS_THROWS(napi_create_string_utf8(env, value.data(), value.size(), &result));
944
977
 
945
978
  return result;
946
979
  }
947
980
 
948
- static void FinalizeIterator(napi_env env, void* data, void* hint) {
949
- if (data) {
950
- delete reinterpret_cast<Iterator*>(data);
951
- }
952
- }
953
-
954
981
  NAPI_METHOD(iterator_init) {
955
982
  NAPI_ARGV(2);
956
983
  NAPI_DB_CONTEXT();
957
984
 
958
985
  const auto options = argv[1];
959
- const auto reverse = BooleanProperty(env, options, "reverse", false);
960
- const auto keys = BooleanProperty(env, options, "keys", true);
961
- const auto values = BooleanProperty(env, options, "values", true);
962
- const auto fillCache = BooleanProperty(env, options, "fillCache", false);
986
+ const auto reverse = BooleanProperty(env, options, "reverse").value_or(false);
987
+ const auto keys = BooleanProperty(env, options, "keys").value_or(true);
988
+ const auto values = BooleanProperty(env, options, "values").value_or(true);
989
+ const auto fillCache = BooleanProperty(env, options, "fillCache").value_or(false);
963
990
  const bool keyAsBuffer = EncodingIsBuffer(env, options, "keyEncoding");
964
991
  const bool valueAsBuffer = EncodingIsBuffer(env, options, "valueEncoding");
965
- const auto limit = Int32Property(env, options, "limit", -1);
966
- const auto highWaterMarkBytes = Uint32Property(env, options, "highWaterMarkBytes", 16 * 1024);
992
+ const auto limit = Int32Property(env, options, "limit").value_or(-1);
993
+ const auto highWaterMarkBytes = Uint32Property(env, options, "highWaterMarkBytes").value_or(16 * 1024);
967
994
 
968
- const auto lt = RangeOption(env, options, "lt");
969
- const auto lte = RangeOption(env, options, "lte");
970
- const auto gt = RangeOption(env, options, "gt");
971
- const auto gte = RangeOption(env, options, "gte");
995
+ const auto lt = StringProperty(env, options, "lt");
996
+ const auto lte = StringProperty(env, options, "lte");
997
+ const auto gt = StringProperty(env, options, "gt");
998
+ const auto gte = StringProperty(env, options, "gte");
972
999
 
973
- auto iterator = new Iterator(database, reverse, keys, values, limit, lt, lte, gt, gte, fillCache, keyAsBuffer,
974
- valueAsBuffer, highWaterMarkBytes);
975
- napi_value result;
1000
+ auto iterator = std::make_unique<Iterator>(database, reverse, keys, values, limit, lt, lte, gt, gte, fillCache,
1001
+ keyAsBuffer, valueAsBuffer, highWaterMarkBytes);
976
1002
 
977
- NAPI_STATUS_THROWS(napi_create_external(env, iterator, FinalizeIterator, nullptr, &result));
1003
+ napi_value result;
1004
+ NAPI_STATUS_THROWS(napi_create_external(env, iterator.get(), Finalize<Iterator>, iterator.get(), &result));
978
1005
 
979
1006
  // Prevent GC of JS object before the iterator is closed (explicitly or on
980
1007
  // db close) and keep track of non-closed iterators to end them on db close.
981
- iterator->Attach(env, result);
1008
+ iterator.release()->Attach(env, result);
982
1009
 
983
1010
  return result;
984
1011
  }
@@ -987,39 +1014,22 @@ NAPI_METHOD(iterator_seek) {
987
1014
  NAPI_ARGV(2);
988
1015
  NAPI_ITERATOR_CONTEXT();
989
1016
 
990
- const auto target = ToString(env, argv[1]);
1017
+ const auto target = NapiSlice(env, argv[1]);
1018
+
1019
+ NAPI_PENDING_EXCEPTION();
1020
+
991
1021
  iterator->first_ = true;
992
1022
  iterator->Seek(target);
993
1023
 
994
1024
  return 0;
995
1025
  }
996
1026
 
997
- struct CloseIteratorWorker final : public Worker {
998
- CloseIteratorWorker(napi_env env, Iterator* iterator, napi_value callback)
999
- : Worker(env, iterator->database_, callback, "leveldown.iterator.end"), iterator_(iterator) {}
1000
-
1001
- rocksdb::Status Execute(Database& database) override {
1002
- iterator_->Close();
1003
- return rocksdb::Status::OK();
1004
- }
1005
-
1006
- void Destroy(napi_env env) override {
1007
- iterator_->Detach(env);
1008
- Worker::Destroy(env);
1009
- }
1010
-
1011
- private:
1012
- Iterator* iterator_;
1013
- };
1014
-
1015
1027
  NAPI_METHOD(iterator_close) {
1016
- NAPI_ARGV(2);
1028
+ NAPI_ARGV(1);
1017
1029
  NAPI_ITERATOR_CONTEXT();
1018
1030
 
1019
- const auto callback = argv[1];
1020
-
1021
- auto worker = new CloseIteratorWorker(env, iterator, callback);
1022
- worker->Queue(env);
1031
+ iterator->Detach(env);
1032
+ iterator->Close();
1023
1033
 
1024
1034
  return 0;
1025
1035
  }
@@ -1033,9 +1043,6 @@ struct NextWorker final : public Worker {
1033
1043
  iterator_->SeekToRange();
1034
1044
  }
1035
1045
 
1036
- // Limit the size of the cache to prevent starving the event loop
1037
- // in JS-land while we're recursively calling process.nextTick().
1038
-
1039
1046
  cache_.reserve(size_ * 2);
1040
1047
  size_t bytesRead = 0;
1041
1048
 
@@ -1075,29 +1082,29 @@ struct NextWorker final : public Worker {
1075
1082
  return iterator_->Status();
1076
1083
  }
1077
1084
 
1078
- void OnOk(napi_env env, napi_value callback) override {
1085
+ napi_status OnOk(napi_env env, napi_value callback) override {
1079
1086
  const auto size = cache_.size();
1080
1087
  napi_value result;
1081
- napi_create_array_with_length(env, size, &result);
1088
+ NAPI_STATUS_RETURN(napi_create_array_with_length(env, size, &result));
1082
1089
 
1083
1090
  for (size_t idx = 0; idx < cache_.size(); idx += 2) {
1084
1091
  napi_value key;
1085
1092
  napi_value val;
1086
1093
 
1087
- Convert(env, cache_[idx + 0], iterator_->keyAsBuffer_, key);
1088
- Convert(env, cache_[idx + 1], iterator_->valueAsBuffer_, val);
1094
+ NAPI_STATUS_RETURN(Convert(env, std::move(cache_[idx + 0]), iterator_->keyAsBuffer_, key));
1095
+ NAPI_STATUS_RETURN(Convert(env, std::move(cache_[idx + 1]), iterator_->valueAsBuffer_, val));
1089
1096
 
1090
- napi_set_element(env, result, static_cast<int>(idx + 0), key);
1091
- napi_set_element(env, result, static_cast<int>(idx + 1), val);
1097
+ NAPI_STATUS_RETURN(napi_set_element(env, result, static_cast<int>(idx + 0), key));
1098
+ NAPI_STATUS_RETURN(napi_set_element(env, result, static_cast<int>(idx + 1), val));
1092
1099
  }
1093
1100
 
1094
1101
  cache_.clear();
1095
1102
 
1096
1103
  napi_value argv[3];
1097
- napi_get_null(env, &argv[0]);
1104
+ NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
1098
1105
  argv[1] = result;
1099
- napi_get_boolean(env, !finished_, &argv[2]);
1100
- CallFunction(env, callback, 3, argv);
1106
+ NAPI_STATUS_RETURN(napi_get_boolean(env, !finished_, &argv[2]));
1107
+ return CallFunction(env, callback, 3, argv);
1101
1108
  }
1102
1109
 
1103
1110
  private:
@@ -1113,8 +1120,6 @@ NAPI_METHOD(iterator_nextv) {
1113
1120
 
1114
1121
  uint32_t size;
1115
1122
  NAPI_STATUS_THROWS(napi_get_value_uint32(env, argv[1], &size));
1116
- if (size == 0)
1117
- size = 1;
1118
1123
 
1119
1124
  const auto callback = argv[2];
1120
1125
 
@@ -1145,21 +1150,17 @@ NAPI_METHOD(batch_do) {
1145
1150
  const auto type = StringProperty(env, element, "type");
1146
1151
 
1147
1152
  if (type == "del") {
1148
- if (!HasProperty(env, element, "key"))
1149
- continue;
1150
-
1151
1153
  const auto key = NapiSlice(env, GetProperty(env, element, "key"));
1152
1154
 
1155
+ NAPI_PENDING_EXCEPTION();
1156
+
1153
1157
  batch.Delete(key);
1154
1158
  } else if (type == "put") {
1155
- if (!HasProperty(env, element, "key"))
1156
- continue;
1157
- if (!HasProperty(env, element, "value"))
1158
- continue;
1159
-
1160
1159
  const auto key = NapiSlice(env, GetProperty(env, element, "key"));
1161
1160
  const auto value = NapiSlice(env, GetProperty(env, element, "value"));
1162
1161
 
1162
+ NAPI_PENDING_EXCEPTION();
1163
+
1163
1164
  batch.Put(key, value);
1164
1165
  }
1165
1166
  }
@@ -1168,12 +1169,6 @@ NAPI_METHOD(batch_do) {
1168
1169
  return ToError(env, database->db_->Write(options, &batch));
1169
1170
  }
1170
1171
 
1171
- static void FinalizeBatch(napi_env env, void* data, void* hint) {
1172
- if (data) {
1173
- delete reinterpret_cast<rocksdb::WriteBatch*>(data);
1174
- }
1175
- }
1176
-
1177
1172
  NAPI_METHOD(batch_init) {
1178
1173
  NAPI_ARGV(1);
1179
1174
  NAPI_DB_CONTEXT();
@@ -1181,7 +1176,7 @@ NAPI_METHOD(batch_init) {
1181
1176
  auto batch = new rocksdb::WriteBatch();
1182
1177
 
1183
1178
  napi_value result;
1184
- NAPI_STATUS_THROWS(napi_create_external(env, batch, FinalizeBatch, nullptr, &result));
1179
+ NAPI_STATUS_THROWS(napi_create_external(env, batch, Finalize<rocksdb::WriteBatch>, batch, &result));
1185
1180
  return result;
1186
1181
  }
1187
1182
 
@@ -1190,9 +1185,11 @@ NAPI_METHOD(batch_put) {
1190
1185
  NAPI_BATCH_CONTEXT();
1191
1186
 
1192
1187
  const auto key = NapiSlice(env, argv[1]);
1193
- const auto value = NapiSlice(env, argv[2]);
1188
+ const auto val = NapiSlice(env, argv[2]);
1194
1189
 
1195
- batch->Put(key, value);
1190
+ NAPI_PENDING_EXCEPTION();
1191
+
1192
+ batch->Put(key, val);
1196
1193
 
1197
1194
  return 0;
1198
1195
  }
@@ -1203,6 +1200,8 @@ NAPI_METHOD(batch_del) {
1203
1200
 
1204
1201
  const auto key = NapiSlice(env, argv[1]);
1205
1202
 
1203
+ NAPI_PENDING_EXCEPTION();
1204
+
1206
1205
  batch->Delete(key);
1207
1206
 
1208
1207
  return 0;