@nxtedition/rocksdb 5.2.3 → 5.2.10

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
@@ -18,6 +18,9 @@ namespace leveldb = rocksdb;
18
18
 
19
19
  #include <set>
20
20
  #include <optional>
21
+ #include <memory>
22
+ #include <string>
23
+ #include <string_view>
21
24
  #include <vector>
22
25
 
23
26
  class NullLogger : public rocksdb::Logger {
@@ -61,30 +64,40 @@ static bool IsObject (napi_env env, napi_value value) {
61
64
  return type == napi_object;
62
65
  }
63
66
 
64
- static napi_value CreateError (napi_env env, const std::string& str) {
67
+ static napi_value CreateError (napi_env env, const std::string_view& str) {
65
68
  napi_value msg;
66
- napi_create_string_utf8(env, str.c_str(), str.size(), &msg);
69
+ napi_create_string_utf8(env, str.data(), str.size(), &msg);
67
70
  napi_value error;
68
71
  napi_create_error(env, nullptr, msg, &error);
69
72
  return error;
70
73
  }
71
74
 
72
- static bool HasProperty (napi_env env, napi_value obj, const char* key) {
75
+ static napi_value CreateCodeError (napi_env env, const std::string_view& code, const std::string_view& msg) {
76
+ napi_value codeValue;
77
+ napi_create_string_utf8(env, code.data(), code.size(), &codeValue);
78
+ napi_value msgValue;
79
+ napi_create_string_utf8(env, msg.data(), msg.size(), &msgValue);
80
+ napi_value error;
81
+ napi_create_error(env, codeValue, msgValue, &error);
82
+ return error;
83
+ }
84
+
85
+ static bool HasProperty (napi_env env, napi_value obj, const std::string_view& key) {
73
86
  bool has = false;
74
- napi_has_named_property(env, obj, key, &has);
87
+ napi_has_named_property(env, obj, key.data(), &has);
75
88
  return has;
76
89
  }
77
90
 
78
- static napi_value GetProperty (napi_env env, napi_value obj, const char* key) {
91
+ static napi_value GetProperty (napi_env env, napi_value obj, const std::string_view& key) {
79
92
  napi_value value;
80
- napi_get_named_property(env, obj, key, &value);
93
+ napi_get_named_property(env, obj, key.data(), &value);
81
94
  return value;
82
95
  }
83
96
 
84
- static bool BooleanProperty (napi_env env, napi_value obj, const char* key,
97
+ static bool BooleanProperty (napi_env env, napi_value obj, const std::string_view& key,
85
98
  bool defaultValue) {
86
- if (HasProperty(env, obj, key)) {
87
- const auto value = GetProperty(env, obj, key);
99
+ if (HasProperty(env, obj, key.data())) {
100
+ const auto value = GetProperty(env, obj, key.data());
88
101
  bool result;
89
102
  napi_get_value_bool(env, value, &result);
90
103
  return result;
@@ -93,10 +106,23 @@ static bool BooleanProperty (napi_env env, napi_value obj, const char* key,
93
106
  return defaultValue;
94
107
  }
95
108
 
96
- static uint32_t Uint32Property (napi_env env, napi_value obj, const char* key,
109
+ static bool EncodingIsBuffer (napi_env env, napi_value obj, const std::string_view& option) {
110
+ napi_value value;
111
+ size_t size;
112
+
113
+ if (napi_get_named_property(env, obj, option.data(), &value) == napi_ok &&
114
+ napi_get_value_string_utf8(env, value, NULL, 0, &size) == napi_ok) {
115
+ // Value is either "buffer" or "utf8" so we can tell them apart just by size
116
+ return size == 6;
117
+ }
118
+
119
+ return false;
120
+ }
121
+
122
+ static uint32_t Uint32Property (napi_env env, napi_value obj, const std::string_view& key,
97
123
  uint32_t defaultValue) {
98
- if (HasProperty(env, obj, key)) {
99
- const auto value = GetProperty(env, obj, key);
124
+ if (HasProperty(env, obj, key.data())) {
125
+ const auto value = GetProperty(env, obj, key.data());
100
126
  uint32_t result;
101
127
  napi_get_value_uint32(env, value, &result);
102
128
  return result;
@@ -105,10 +131,10 @@ static uint32_t Uint32Property (napi_env env, napi_value obj, const char* key,
105
131
  return defaultValue;
106
132
  }
107
133
 
108
- static int Int32Property (napi_env env, napi_value obj, const char* key,
134
+ static int Int32Property (napi_env env, napi_value obj, const std::string_view& key,
109
135
  int defaultValue) {
110
- if (HasProperty(env, obj, key)) {
111
- const auto value = GetProperty(env, obj, key);
136
+ if (HasProperty(env, obj, key.data())) {
137
+ const auto value = GetProperty(env, obj, key.data());
112
138
  int result;
113
139
  napi_get_value_int32(env, value, &result);
114
140
  return result;
@@ -117,7 +143,7 @@ static int Int32Property (napi_env env, napi_value obj, const char* key,
117
143
  return defaultValue;
118
144
  }
119
145
 
120
- static std::string ToString (napi_env env, napi_value from) {
146
+ static std::optional<std::string> ToString (napi_env env, napi_value from) {
121
147
  if (IsString(env, from)) {
122
148
  size_t length = 0;
123
149
  napi_get_value_string_utf8(env, from, nullptr, 0, &length);
@@ -134,11 +160,11 @@ static std::string ToString (napi_env env, napi_value from) {
134
160
  return {};
135
161
  }
136
162
 
137
- static std::string StringProperty (napi_env env, napi_value obj, const char* key) {
163
+ static std::string StringProperty (napi_env env, napi_value obj, const std::string_view& key) {
138
164
  if (HasProperty(env, obj, key)) {
139
165
  napi_value value = GetProperty(env, obj, key);
140
166
  if (IsString(env, value)) {
141
- return ToString(env, value);
167
+ return ToString(env, value).value_or(std::string());
142
168
  }
143
169
  }
144
170
 
@@ -158,13 +184,10 @@ static size_t StringOrBufferLength (napi_env env, napi_value value) {
158
184
  return size;
159
185
  }
160
186
 
161
- static std::optional<std::string> RangeOption (napi_env env, napi_value opts, const char* name) {
187
+ static std::optional<std::string> RangeOption (napi_env env, napi_value opts, const std::string& name) {
162
188
  if (HasProperty(env, opts, name)) {
163
189
  const auto value = GetProperty(env, opts, name);
164
-
165
- if (StringOrBufferLength(env, value) > 0) {
166
- return ToString(env, value);
167
- }
190
+ return ToString(env, value);
168
191
  }
169
192
 
170
193
  return {};
@@ -182,7 +205,7 @@ static std::vector<std::string> KeyArray (napi_env env, napi_value arr) {
182
205
 
183
206
  if (napi_get_element(env, arr, i, &element) == napi_ok &&
184
207
  StringOrBufferLength(env, element) > 0) {
185
- result.push_back(ToString(env, element));
208
+ result.push_back(ToString(env, element).value_or(std::string()));
186
209
  }
187
210
  }
188
211
  }
@@ -199,7 +222,8 @@ static napi_status CallFunction (napi_env env,
199
222
  return napi_call_function(env, global, callback, argc, argv, nullptr);
200
223
  }
201
224
 
202
- void Convert (napi_env env, const std::optional<std::string>& s, bool asBuffer, napi_value& result) {
225
+ template <typename T>
226
+ void Convert (napi_env env, const std::optional<T>& s, bool asBuffer, napi_value& result) {
203
227
  if (!s) {
204
228
  napi_get_undefined(env, &result);
205
229
  } else if (asBuffer) {
@@ -209,6 +233,15 @@ void Convert (napi_env env, const std::optional<std::string>& s, bool asBuffer,
209
233
  }
210
234
  }
211
235
 
236
+ template <typename T>
237
+ void Convert (napi_env env, const T& s, bool asBuffer, napi_value& result) {
238
+ if (asBuffer) {
239
+ napi_create_buffer_copy(env, s.size(), s.data(), nullptr, &result);
240
+ } else {
241
+ napi_create_string_utf8(env, s.data(), s.size(), &result);
242
+ }
243
+ }
244
+
212
245
  /**
213
246
  * Base worker class. Handles the async work. Derived classes can override the
214
247
  * following virtual methods (listed in the order in which they're called):
@@ -247,8 +280,9 @@ struct BaseWorker {
247
280
  self->DoExecute();
248
281
  }
249
282
 
250
- void SetStatus (const leveldb::Status& status) {
283
+ bool SetStatus (const leveldb::Status& status) {
251
284
  status_ = status;
285
+ return status.ok();
252
286
  }
253
287
 
254
288
  virtual void DoExecute () = 0;
@@ -278,7 +312,28 @@ struct BaseWorker {
278
312
  }
279
313
 
280
314
  virtual void HandleErrorCallback (napi_env env, napi_value callback) {
281
- auto argv = CreateError(env, status_.ToString());
315
+ napi_value argv;
316
+
317
+ const auto msg = status_.ToString();
318
+
319
+ if (status_.IsNotFound()) {
320
+ argv = CreateCodeError(env, "LEVEL_NOT_FOUND", msg);
321
+ } else if (status_.IsCorruption()) {
322
+ argv = CreateCodeError(env, "LEVEL_CORRUPTION", msg);
323
+ } else if (status_.IsIOError()) {
324
+ if (msg.find("IO error: lock ") != std::string::npos) { // env_posix.cc
325
+ argv = CreateCodeError(env, "LEVEL_LOCKED", msg);
326
+ } else if (msg.find("IO error: LockFile ") != std::string::npos) { // env_win.cc
327
+ argv = CreateCodeError(env, "LEVEL_LOCKED", msg);
328
+ } else if (msg.find("IO error: While lock file") != std::string::npos) { // env_mac.cc
329
+ argv = CreateCodeError(env, "LEVEL_LOCKED", msg);
330
+ } else {
331
+ argv = CreateCodeError(env, "LEVEL_IO_ERROR", msg);
332
+ }
333
+ } else {
334
+ argv = CreateError(env, msg);
335
+ }
336
+
282
337
  CallFunction(env, callback, 1, &argv);
283
338
  }
284
339
 
@@ -333,18 +388,18 @@ struct Database {
333
388
  leveldb::Status Put (const leveldb::WriteOptions& options,
334
389
  const std::string& key,
335
390
  const std::string& value) {
336
- return db_->Put(options, key, value);
391
+ return db_->Put(options, db_->DefaultColumnFamily(), key, value);
337
392
  }
338
393
 
339
394
  leveldb::Status Get (const leveldb::ReadOptions& options,
340
395
  const std::string& key,
341
- std::string& value) {
342
- return db_->Get(options, key, &value);
396
+ rocksdb::PinnableSlice& value) {
397
+ return db_->Get(options, db_->DefaultColumnFamily(), key, &value);
343
398
  }
344
399
 
345
400
  leveldb::Status Del (const leveldb::WriteOptions& options,
346
401
  const std::string& key) {
347
- return db_->Delete(options, key);
402
+ return db_->Delete(options, db_->DefaultColumnFamily(), key);
348
403
  }
349
404
 
350
405
  leveldb::Status WriteBatch (const leveldb::WriteOptions& options,
@@ -571,10 +626,19 @@ struct BaseIterator {
571
626
  }
572
627
 
573
628
  bool OutOfRange (const leveldb::Slice& target) const {
574
- return ((lt_ && target.compare(*lt_) >= 0) ||
575
- (lte_ && target.compare(*lte_) > 0) ||
576
- (gt_ && target.compare(*gt_) <= 0) ||
577
- (gte_ && target.compare(*gte_) < 0));
629
+ if (lte_) {
630
+ if (target.compare(*lte_) > 0) return true;
631
+ } else if (lt_) {
632
+ if (target.compare(*lt_) >= 0) return true;
633
+ }
634
+
635
+ if (gte_) {
636
+ if (target.compare(*gte_) < 0) return true;
637
+ } else if (gt_) {
638
+ if (target.compare(*gt_) <= 0) return true;
639
+ }
640
+
641
+ return false;
578
642
  }
579
643
 
580
644
  Database* database_;
@@ -605,14 +669,14 @@ struct Iterator final : public BaseIterator {
605
669
  const bool fillCache,
606
670
  const bool keyAsBuffer,
607
671
  const bool valueAsBuffer,
608
- const uint32_t highWaterMark)
672
+ const uint32_t highWaterMarkBytes)
609
673
  : BaseIterator(database, reverse, lt, lte, gt, gte, limit, fillCache),
610
674
  keys_(keys),
611
675
  values_(values),
612
676
  keyAsBuffer_(keyAsBuffer),
613
677
  valueAsBuffer_(valueAsBuffer),
614
- highWaterMark_(highWaterMark),
615
- landed_(false),
678
+ highWaterMarkBytes_(highWaterMarkBytes),
679
+ first_(true),
616
680
  nexting_(false),
617
681
  isClosing_(false),
618
682
  closeWorker_(nullptr),
@@ -631,34 +695,34 @@ struct Iterator final : public BaseIterator {
631
695
 
632
696
  bool ReadMany (uint32_t size) {
633
697
  cache_.clear();
698
+ cache_.reserve(size * 2);
634
699
  size_t bytesRead = 0;
635
700
 
636
701
  while (true) {
637
- if (landed_) Next();
638
- if (!Valid() || !Increment()) break;
639
-
640
- if (keys_) {
641
- const auto& slice = CurrentKey();
642
- cache_.emplace_back(slice.data(), slice.size());
643
- bytesRead += slice.size();
644
- } else {
645
- cache_.emplace_back("");
646
- }
647
-
648
- if (values_) {
649
- const auto& slice = CurrentValue();
650
- cache_.emplace_back(slice.data(), slice.size());
651
- bytesRead += slice.size();
652
- } else {
653
- cache_.emplace_back("");
654
- }
702
+ if (!first_) Next();
703
+ else first_ = false;
655
704
 
656
- if (!landed_) {
657
- landed_ = true;
658
- return true;
705
+ if (!Valid() || !Increment()) break;
706
+
707
+ if (keys_ && values_) {
708
+ auto k = CurrentKey();
709
+ auto v = CurrentValue();
710
+ cache_.emplace_back(k.data(), k.size());
711
+ cache_.emplace_back(v.data(), v.size());
712
+ bytesRead += k.size() + v.size();
713
+ } else if (keys_) {
714
+ auto k = CurrentKey();
715
+ cache_.emplace_back(k.data(), k.size());
716
+ cache_.push_back({});
717
+ bytesRead += k.size();
718
+ } else if (values_) {
719
+ auto v = CurrentValue();
720
+ cache_.push_back({});
721
+ cache_.emplace_back(v.data(), v.size());
722
+ bytesRead += v.size();
659
723
  }
660
724
 
661
- if (bytesRead > highWaterMark_ || cache_.size() >= size * 2) {
725
+ if (bytesRead > highWaterMarkBytes_ || cache_.size() / 2 >= size) {
662
726
  return true;
663
727
  }
664
728
  }
@@ -670,8 +734,8 @@ struct Iterator final : public BaseIterator {
670
734
  const bool values_;
671
735
  const bool keyAsBuffer_;
672
736
  const bool valueAsBuffer_;
673
- const uint32_t highWaterMark_;
674
- bool landed_;
737
+ const uint32_t highWaterMarkBytes_;
738
+ bool first_;
675
739
  bool nexting_;
676
740
  bool isClosing_;
677
741
  BaseWorker* closeWorker_;
@@ -687,7 +751,7 @@ private:
687
751
  * the guarantee that no db operations will be in-flight at this time.
688
752
  */
689
753
  static void env_cleanup_hook (void* arg) {
690
- Database* database = (Database*)arg;
754
+ Database* database = reinterpret_cast<Database*>(arg);
691
755
 
692
756
  // Do everything that db_close() does but synchronously. We're expecting that GC
693
757
  // did not (yet) collect the database because that would be a user mistake (not
@@ -697,8 +761,8 @@ static void env_cleanup_hook (void* arg) {
697
761
  // be a safe noop if called before db_open() or after db_close().
698
762
  if (database && database->db_) {
699
763
  // TODO: does not do `napi_delete_reference(env, iterator->ref_)`. Problem?
700
- for (auto it = database->iterators_.begin(); it != database->iterators_.end(); ++it) {
701
- (*it)->Close();
764
+ for (auto it : database->iterators_) {
765
+ it->Close();
702
766
  }
703
767
 
704
768
  // Having closed the iterators (and released snapshots) we can safely close.
@@ -812,7 +876,7 @@ NAPI_METHOD(db_open) {
812
876
  NAPI_ARGV(4);
813
877
  NAPI_DB_CONTEXT();
814
878
 
815
- const auto location = ToString(env, argv[1]);
879
+ const auto location = ToString(env, argv[1]).value_or(std::string());
816
880
  const auto options = argv[2];
817
881
  const auto createIfMissing = BooleanProperty(env, options, "createIfMissing", true);
818
882
  const auto errorIfExists = BooleanProperty(env, options, "errorIfExists", false);
@@ -866,8 +930,8 @@ NAPI_METHOD(db_close) {
866
930
  napi_value noop;
867
931
  napi_create_function(env, nullptr, 0, noop_callback, nullptr, &noop);
868
932
 
869
- for (auto it = database->iterators_.begin(); it != database->iterators_.end(); ++it) {
870
- iterator_do_close(env, *it, noop);
933
+ for (auto it : database->iterators_) {
934
+ iterator_do_close(env, it, noop);
871
935
  }
872
936
 
873
937
  auto worker = new CloseWorker(env, database, callback);
@@ -881,6 +945,91 @@ NAPI_METHOD(db_close) {
881
945
  return 0;
882
946
  }
883
947
 
948
+ struct PutWorker final : public PriorityWorker {
949
+ PutWorker (napi_env env,
950
+ Database* database,
951
+ napi_value callback,
952
+ const std::string& key,
953
+ const std::string& value,
954
+ bool sync)
955
+ : PriorityWorker(env, database, callback, "classic_level.db.put"),
956
+ key_(key), value_(value), sync_(sync) {
957
+ }
958
+
959
+ void DoExecute () override {
960
+ leveldb::WriteOptions options;
961
+ options.sync = sync_;
962
+ SetStatus(database_->Put(options, key_, value_));
963
+ }
964
+
965
+ const std::string key_;
966
+ const std::string value_;
967
+ const bool sync_;
968
+ };
969
+
970
+ NAPI_METHOD(db_put) {
971
+ NAPI_ARGV(5);
972
+ NAPI_DB_CONTEXT();
973
+
974
+ const auto key = ToString(env, argv[1]).value_or(std::string());
975
+ const auto value = ToString(env, argv[2]).value_or(std::string());
976
+ const auto sync = BooleanProperty(env, argv[3], "sync", false);
977
+ const auto callback = argv[4];
978
+
979
+ auto worker = new PutWorker(env, database, callback, key, value, sync);
980
+ worker->Queue(env);
981
+
982
+ return 0;
983
+ }
984
+
985
+ struct GetWorker final : public PriorityWorker {
986
+ GetWorker (napi_env env,
987
+ Database* database,
988
+ napi_value callback,
989
+ const std::string& key,
990
+ const bool asBuffer,
991
+ const bool fillCache)
992
+ : PriorityWorker(env, database, callback, "classic_level.db.get"),
993
+ key_(key), asBuffer_(asBuffer), fillCache_(fillCache) {
994
+ }
995
+
996
+ void DoExecute () override {
997
+ leveldb::ReadOptions options;
998
+ options.fill_cache = fillCache_;
999
+ SetStatus(database_->Get(options, key_, value_));
1000
+ }
1001
+
1002
+ void HandleOKCallback (napi_env env, napi_value callback) override {
1003
+ napi_value argv[2];
1004
+ napi_get_null(env, &argv[0]);
1005
+ Convert(env, std::move(value_), asBuffer_, argv[1]);
1006
+ CallFunction(env, callback, 2, argv);
1007
+ }
1008
+
1009
+ private:
1010
+ const std::string key_;
1011
+ rocksdb::PinnableSlice value_;
1012
+ const bool asBuffer_;
1013
+ const bool fillCache_;
1014
+ };
1015
+
1016
+ NAPI_METHOD(db_get) {
1017
+ NAPI_ARGV(4);
1018
+ NAPI_DB_CONTEXT();
1019
+
1020
+ const auto key = ToString(env, argv[1]).value_or(std::string());
1021
+ const auto options = argv[2];
1022
+ const auto asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1023
+ const auto fillCache = BooleanProperty(env, options, "fillCache", true);
1024
+ const auto callback = argv[3];
1025
+
1026
+ auto worker = new GetWorker(env, database, callback, key, asBuffer, fillCache);
1027
+ worker->Queue(env);
1028
+
1029
+ return 0;
1030
+ }
1031
+
1032
+
884
1033
  struct GetManyWorker final : public PriorityWorker {
885
1034
  GetManyWorker (napi_env env,
886
1035
  Database* database,
@@ -907,8 +1056,9 @@ struct GetManyWorker final : public PriorityWorker {
907
1056
  options.snapshot = snapshot_;
908
1057
  options.fill_cache = fillCache_;
909
1058
 
1059
+ rocksdb::PinnableSlice value;
1060
+
910
1061
  for (const auto& key: keys_) {
911
- std::string value;
912
1062
  const auto status = database_->Get(options, key, value);
913
1063
 
914
1064
  if (status.ok()) {
@@ -919,6 +1069,8 @@ struct GetManyWorker final : public PriorityWorker {
919
1069
  SetStatus(status);
920
1070
  break;
921
1071
  }
1072
+
1073
+ value.Reset();
922
1074
  }
923
1075
 
924
1076
  database_->ReleaseSnapshot(snapshot_);
@@ -946,7 +1098,7 @@ struct GetManyWorker final : public PriorityWorker {
946
1098
  private:
947
1099
  const std::vector<std::string> keys_;
948
1100
  const bool valueAsBuffer_;
949
- std::vector<std::optional<std::string>> cache_;
1101
+ std::vector<std::optional<rocksdb::PinnableSlice>> cache_;
950
1102
  const bool fillCache_;
951
1103
  const leveldb::Snapshot* snapshot_;
952
1104
  };
@@ -957,7 +1109,7 @@ NAPI_METHOD(db_get_many) {
957
1109
 
958
1110
  const auto keys = KeyArray(env, argv[1]);
959
1111
  const auto options = argv[2];
960
- const bool asBuffer = BooleanProperty(env, options, "asBuffer", true);
1112
+ const bool asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
961
1113
  const bool fillCache = BooleanProperty(env, options, "fillCache", true);
962
1114
  const auto callback = argv[3];
963
1115
 
@@ -967,6 +1119,113 @@ NAPI_METHOD(db_get_many) {
967
1119
  return 0;
968
1120
  }
969
1121
 
1122
+ struct DelWorker final : public PriorityWorker {
1123
+ DelWorker (napi_env env,
1124
+ Database* database,
1125
+ napi_value callback,
1126
+ const std::string& key,
1127
+ bool sync)
1128
+ : PriorityWorker(env, database, callback, "classic_level.db.del"),
1129
+ key_(key), sync_(sync) {
1130
+ }
1131
+
1132
+ void DoExecute () override {
1133
+ leveldb::WriteOptions options;
1134
+ options.sync = sync_;
1135
+ SetStatus(database_->Del(options, key_));
1136
+ }
1137
+
1138
+ const std::string key_;
1139
+ const bool sync_;
1140
+ };
1141
+
1142
+ NAPI_METHOD(db_del) {
1143
+ NAPI_ARGV(4);
1144
+ NAPI_DB_CONTEXT();
1145
+
1146
+ const auto key = ToString(env, argv[1]).value_or(std::string());
1147
+ const auto sync = BooleanProperty(env, argv[2], "sync", false);
1148
+ const auto callback = argv[3];
1149
+
1150
+ auto worker = new DelWorker(env, database, callback, key, sync);
1151
+ worker->Queue(env);
1152
+
1153
+ return 0;
1154
+ }
1155
+
1156
+ struct ClearWorker final : public PriorityWorker {
1157
+ ClearWorker (napi_env env,
1158
+ Database* database,
1159
+ napi_value callback,
1160
+ const bool reverse,
1161
+ const int limit,
1162
+ const std::optional<std::string>& lt,
1163
+ const std::optional<std::string>& lte,
1164
+ const std::optional<std::string>& gt,
1165
+ const std::optional<std::string>& gte)
1166
+ : PriorityWorker(env, database, callback, "classic_level.db.clear"),
1167
+ iterator_(database, reverse, lt, lte, gt, gte, limit, false) {
1168
+ }
1169
+
1170
+ void DoExecute () override {
1171
+ iterator_.SeekToRange();
1172
+
1173
+ // TODO: add option
1174
+ const uint32_t hwm = 16 * 1024;
1175
+ leveldb::WriteBatch batch;
1176
+
1177
+ leveldb::WriteOptions options;
1178
+ options.sync = false;
1179
+
1180
+ while (true) {
1181
+ size_t bytesRead = 0;
1182
+
1183
+ while (bytesRead <= hwm && iterator_.Valid() && iterator_.Increment()) {
1184
+ const auto key = iterator_.CurrentKey();
1185
+ batch.Delete(key);
1186
+ bytesRead += key.size();
1187
+ iterator_.Next();
1188
+ }
1189
+
1190
+ if (!SetStatus(iterator_.Status()) || bytesRead == 0) {
1191
+ break;
1192
+ }
1193
+
1194
+ if (!SetStatus(database_->WriteBatch(options, &batch))) {
1195
+ break;
1196
+ }
1197
+
1198
+ batch.Clear();
1199
+ }
1200
+
1201
+ iterator_.Close();
1202
+ }
1203
+
1204
+ private:
1205
+ BaseIterator iterator_;
1206
+ };
1207
+
1208
+ NAPI_METHOD(db_clear) {
1209
+ NAPI_ARGV(3);
1210
+ NAPI_DB_CONTEXT();
1211
+
1212
+ napi_value options = argv[1];
1213
+ napi_value callback = argv[2];
1214
+
1215
+ const auto reverse = BooleanProperty(env, options, "reverse", false);
1216
+ const auto limit = Int32Property(env, options, "limit", -1);
1217
+
1218
+ const auto lt = RangeOption(env, options, "lt");
1219
+ const auto lte = RangeOption(env, options, "lte");
1220
+ const auto gt = RangeOption(env, options, "gt");
1221
+ const auto gte = RangeOption(env, options, "gte");
1222
+
1223
+ auto worker = new ClearWorker(env, database, callback, reverse, limit, lt, lte, gt, gte);
1224
+ worker->Queue(env);
1225
+
1226
+ return 0;
1227
+ }
1228
+
970
1229
  static void FinalizeIterator (napi_env env, void* data, void* hint) {
971
1230
  if (data) {
972
1231
  delete reinterpret_cast<Iterator*>(data);
@@ -982,10 +1241,10 @@ NAPI_METHOD(iterator_init) {
982
1241
  const auto keys = BooleanProperty(env, options, "keys", true);
983
1242
  const auto values = BooleanProperty(env, options, "values", true);
984
1243
  const auto fillCache = BooleanProperty(env, options, "fillCache", false);
985
- const auto keyAsBuffer = BooleanProperty(env, options, "keyAsBuffer", true);
986
- const auto valueAsBuffer = BooleanProperty(env, options, "valueAsBuffer", true);
1244
+ const bool keyAsBuffer = EncodingIsBuffer(env, options, "keyEncoding");
1245
+ const bool valueAsBuffer = EncodingIsBuffer(env, options, "valueEncoding");
987
1246
  const auto limit = Int32Property(env, options, "limit", -1);
988
- const auto highWaterMark = Uint32Property(env, options, "highWaterMark", 16 * 1024);
1247
+ const auto highWaterMarkBytes = Uint32Property(env, options, "highWaterMarkBytes", 16 * 1024);
989
1248
 
990
1249
  const auto lt = RangeOption(env, options, "lt");
991
1250
  const auto lte = RangeOption(env, options, "lte");
@@ -994,7 +1253,7 @@ NAPI_METHOD(iterator_init) {
994
1253
 
995
1254
  auto iterator = new Iterator(database, reverse, keys,
996
1255
  values, limit, lt, lte, gt, gte, fillCache,
997
- keyAsBuffer, valueAsBuffer, highWaterMark);
1256
+ keyAsBuffer, valueAsBuffer, highWaterMarkBytes);
998
1257
  napi_value result;
999
1258
 
1000
1259
  NAPI_STATUS_THROWS(napi_create_external(env, iterator,
@@ -1016,8 +1275,8 @@ NAPI_METHOD(iterator_seek) {
1016
1275
  napi_throw_error(env, nullptr, "iterator has closed");
1017
1276
  }
1018
1277
 
1019
- const auto target = ToString(env, argv[1]);
1020
- iterator->landed_ = false;
1278
+ const auto target = ToString(env, argv[1]).value_or(std::string());
1279
+ iterator->first_ = true;
1021
1280
  iterator->Seek(target);
1022
1281
 
1023
1282
  return 0;
@@ -1104,8 +1363,8 @@ struct NextWorker final : public BaseWorker {
1104
1363
  Convert(env, iterator_->cache_[idx + 0], iterator_->keyAsBuffer_, key);
1105
1364
  Convert(env, iterator_->cache_[idx + 1], iterator_->valueAsBuffer_, val);
1106
1365
 
1107
- napi_set_element(env, result, static_cast<int>(size - idx - 1), key);
1108
- napi_set_element(env, result, static_cast<int>(size - idx - 2), val);
1366
+ napi_set_element(env, result, static_cast<int>(idx + 0), key);
1367
+ napi_set_element(env, result, static_cast<int>(idx + 1), val);
1109
1368
  }
1110
1369
 
1111
1370
  iterator_->cache_.clear();
@@ -1146,9 +1405,8 @@ NAPI_METHOD(iterator_nextv) {
1146
1405
  const auto callback = argv[2];
1147
1406
 
1148
1407
  if (iterator->isClosing_) {
1149
- auto argv = CreateError(env, "iterator has closed");
1150
- CallFunction(env, callback, 1, &argv);
1151
-
1408
+ napi_value argv = CreateCodeError(env, "LEVEL_ITERATOR_NOT_OPEN", "Iterator is not open");
1409
+ NAPI_STATUS_THROWS(CallFunction(env, callback, 1, &argv));
1152
1410
  return 0;
1153
1411
  }
1154
1412
 
@@ -1159,6 +1417,83 @@ NAPI_METHOD(iterator_nextv) {
1159
1417
  return 0;
1160
1418
  }
1161
1419
 
1420
+ /**
1421
+ * Worker class for batch write operation.
1422
+ */
1423
+ struct BatchWorker final : public PriorityWorker {
1424
+ BatchWorker (napi_env env,
1425
+ Database* database,
1426
+ napi_value callback,
1427
+ leveldb::WriteBatch* batch,
1428
+ const bool sync,
1429
+ const bool hasData)
1430
+ : PriorityWorker(env, database, callback, "classic_level.batch.do"),
1431
+ batch_(batch), hasData_(hasData) {
1432
+ options_.sync = sync;
1433
+ }
1434
+
1435
+ ~BatchWorker () {
1436
+ delete batch_;
1437
+ }
1438
+
1439
+ void DoExecute () override {
1440
+ if (hasData_) {
1441
+ SetStatus(database_->WriteBatch(options_, batch_));
1442
+ }
1443
+ }
1444
+
1445
+ private:
1446
+ leveldb::WriteOptions options_;
1447
+ leveldb::WriteBatch* batch_;
1448
+ const bool hasData_;
1449
+ };
1450
+
1451
+ NAPI_METHOD(batch_do) {
1452
+ NAPI_ARGV(4);
1453
+ NAPI_DB_CONTEXT();
1454
+
1455
+ const auto array = argv[1];
1456
+ const auto sync = BooleanProperty(env, argv[2], "sync", false);
1457
+ const auto callback = argv[3];
1458
+
1459
+ uint32_t length;
1460
+ napi_get_array_length(env, array, &length);
1461
+
1462
+ leveldb::WriteBatch* batch = new leveldb::WriteBatch();
1463
+ bool hasData = false;
1464
+
1465
+ for (uint32_t i = 0; i < length; i++) {
1466
+ napi_value element;
1467
+ napi_get_element(env, array, i, &element);
1468
+
1469
+ if (!IsObject(env, element)) continue;
1470
+
1471
+ std::string type = StringProperty(env, element, "type");
1472
+
1473
+ if (type == "del") {
1474
+ if (!HasProperty(env, element, "key")) continue;
1475
+ const auto key = ToString(env, GetProperty(env, element, "key")).value_or(std::string());
1476
+
1477
+ batch->Delete(key);
1478
+ if (!hasData) hasData = true;
1479
+ } else if (type == "put") {
1480
+ if (!HasProperty(env, element, "key")) continue;
1481
+ if (!HasProperty(env, element, "value")) continue;
1482
+
1483
+ const auto key = ToString(env, GetProperty(env, element, "key")).value_or(std::string());
1484
+ const auto value = ToString(env, GetProperty(env, element, "value")).value_or(std::string());
1485
+
1486
+ batch->Put(key, value);
1487
+ if (!hasData) hasData = true;
1488
+ }
1489
+ }
1490
+
1491
+ auto worker = new BatchWorker(env, database, callback, batch, sync, hasData);
1492
+ worker->Queue(env);
1493
+
1494
+ return 0;
1495
+ }
1496
+
1162
1497
  static void FinalizeBatch (napi_env env, void* data, void* hint) {
1163
1498
  if (data) {
1164
1499
  delete reinterpret_cast<leveldb::WriteBatch*>(data);
@@ -1182,8 +1517,8 @@ NAPI_METHOD(batch_put) {
1182
1517
  NAPI_ARGV(3);
1183
1518
  NAPI_BATCH_CONTEXT();
1184
1519
 
1185
- const auto key = ToString(env, argv[1]);
1186
- const auto value = ToString(env, argv[2]);
1520
+ const auto key = ToString(env, argv[1]).value_or(std::string());
1521
+ const auto value = ToString(env, argv[2]).value_or(std::string());
1187
1522
 
1188
1523
  batch->Put(key, value);
1189
1524
 
@@ -1194,7 +1529,7 @@ NAPI_METHOD(batch_del) {
1194
1529
  NAPI_ARGV(2);
1195
1530
  NAPI_BATCH_CONTEXT();
1196
1531
 
1197
- const auto key = ToString(env, argv[1]);
1532
+ const auto key = ToString(env, argv[1]).value_or(std::string());
1198
1533
 
1199
1534
  batch->Delete(key);
1200
1535
 
@@ -1261,13 +1596,18 @@ NAPI_INIT() {
1261
1596
  NAPI_EXPORT_FUNCTION(db_init);
1262
1597
  NAPI_EXPORT_FUNCTION(db_open);
1263
1598
  NAPI_EXPORT_FUNCTION(db_close);
1599
+ NAPI_EXPORT_FUNCTION(db_put);
1600
+ NAPI_EXPORT_FUNCTION(db_get);
1264
1601
  NAPI_EXPORT_FUNCTION(db_get_many);
1602
+ NAPI_EXPORT_FUNCTION(db_del);
1603
+ NAPI_EXPORT_FUNCTION(db_clear);
1265
1604
 
1266
1605
  NAPI_EXPORT_FUNCTION(iterator_init);
1267
1606
  NAPI_EXPORT_FUNCTION(iterator_seek);
1268
1607
  NAPI_EXPORT_FUNCTION(iterator_close);
1269
1608
  NAPI_EXPORT_FUNCTION(iterator_nextv);
1270
1609
 
1610
+ NAPI_EXPORT_FUNCTION(batch_do);
1271
1611
  NAPI_EXPORT_FUNCTION(batch_init);
1272
1612
  NAPI_EXPORT_FUNCTION(batch_put);
1273
1613
  NAPI_EXPORT_FUNCTION(batch_del);