@nxtedition/rocksdb 7.0.63 → 7.0.64

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
@@ -12,11 +12,12 @@
12
12
  #include <rocksdb/filter_policy.h>
13
13
  #include <rocksdb/merge_operator.h>
14
14
  #include <rocksdb/options.h>
15
+ #include <rocksdb/slice.h>
15
16
  #include <rocksdb/slice_transform.h>
17
+ #include <rocksdb/status.h>
16
18
  #include <rocksdb/table.h>
17
19
  #include <rocksdb/write_batch.h>
18
20
 
19
- #include <array>
20
21
  #include <memory>
21
22
  #include <optional>
22
23
  #include <set>
@@ -25,6 +26,7 @@
25
26
  #include <vector>
26
27
 
27
28
  #include "max_rev_operator.h"
29
+ #include "util.h"
28
30
 
29
31
  class NullLogger : public rocksdb::Logger {
30
32
  public:
@@ -37,298 +39,6 @@ struct Database;
37
39
  struct Iterator;
38
40
  struct Updates;
39
41
 
40
- #define NAPI_STATUS_RETURN(call) \
41
- { \
42
- auto _status = (call); \
43
- if (_status != napi_ok) { \
44
- return _status; \
45
- } \
46
- }
47
-
48
- #define ROCKS_STATUS_RETURN(call) \
49
- { \
50
- auto _status = (call); \
51
- if (!_status.ok()) { \
52
- return _status; \
53
- } \
54
- }
55
-
56
- #define ROCKS_STATUS_THROWS(call) \
57
- { \
58
- auto _status = (call); \
59
- if (!_status.ok()) { \
60
- napi_throw(env, ToError(env, _status)); \
61
- return NULL; \
62
- } \
63
- }
64
-
65
- static napi_value CreateError(napi_env env, const std::optional<std::string_view>& code, const std::string_view& msg) {
66
- napi_value codeValue = nullptr;
67
- if (code) {
68
- NAPI_STATUS_THROWS(napi_create_string_utf8(env, code->data(), code->size(), &codeValue));
69
- }
70
- napi_value msgValue;
71
- NAPI_STATUS_THROWS(napi_create_string_utf8(env, msg.data(), msg.size(), &msgValue));
72
- napi_value error;
73
- NAPI_STATUS_THROWS(napi_create_error(env, codeValue, msgValue, &error));
74
- return error;
75
- }
76
-
77
- static bool HasProperty(napi_env env, napi_value obj, const std::string_view& key) {
78
- bool has = false;
79
- napi_has_named_property(env, obj, key.data(), &has);
80
- return has;
81
- }
82
-
83
- static napi_value GetProperty(napi_env env, napi_value obj, const std::string_view& key) {
84
- napi_value value = nullptr;
85
- napi_get_named_property(env, obj, key.data(), &value);
86
- return value;
87
- }
88
-
89
- static std::optional<bool> BooleanProperty(napi_env env, napi_value obj, const std::string_view& key) {
90
- if (HasProperty(env, obj, key.data())) {
91
- const auto value = GetProperty(env, obj, key.data());
92
- bool result;
93
- napi_get_value_bool(env, value, &result);
94
- return result;
95
- }
96
-
97
- return {};
98
- }
99
-
100
- static bool EncodingIsBuffer(napi_env env, napi_value obj, const std::string_view& option) {
101
- napi_value value;
102
- size_t size;
103
-
104
- if (napi_get_named_property(env, obj, option.data(), &value) == napi_ok &&
105
- napi_get_value_string_utf8(env, value, nullptr, 0, &size) == napi_ok) {
106
- // Value is either "buffer" or "utf8" so we can tell them apart just by size
107
- return size == 6;
108
- }
109
-
110
- return false;
111
- }
112
-
113
- static std::optional<uint32_t> Uint32Property(napi_env env, napi_value obj, const std::string_view& key) {
114
- if (HasProperty(env, obj, key.data())) {
115
- const auto value = GetProperty(env, obj, key.data());
116
- uint32_t result;
117
- napi_get_value_uint32(env, value, &result);
118
- return result;
119
- }
120
-
121
- return {};
122
- }
123
-
124
- static std::optional<int> Int32Property(napi_env env, napi_value obj, const std::string_view& key) {
125
- if (HasProperty(env, obj, key.data())) {
126
- const auto value = GetProperty(env, obj, key.data());
127
- int result;
128
- napi_get_value_int32(env, value, &result);
129
- return result;
130
- }
131
-
132
- return {};
133
- }
134
-
135
- static napi_status ToString(napi_env env, napi_value from, std::string& to) {
136
- napi_valuetype type;
137
- NAPI_STATUS_RETURN(napi_typeof(env, from, &type));
138
-
139
- if (type == napi_string) {
140
- size_t length = 0;
141
- NAPI_STATUS_RETURN(napi_get_value_string_utf8(env, from, nullptr, 0, &length));
142
- to.resize(length, '\0');
143
- NAPI_STATUS_RETURN(napi_get_value_string_utf8(env, from, &to[0], length + 1, &length));
144
- } else {
145
- bool isBuffer;
146
- NAPI_STATUS_RETURN(napi_is_buffer(env, from, &isBuffer));
147
-
148
- if (isBuffer) {
149
- char* buf = nullptr;
150
- size_t length = 0;
151
- NAPI_STATUS_RETURN(napi_get_buffer_info(env, from, reinterpret_cast<void**>(&buf), &length));
152
- to.assign(buf, length);
153
- } else {
154
- return napi_invalid_arg;
155
- }
156
- }
157
-
158
- return napi_ok;
159
- }
160
-
161
- void noop(void* arg1, void* arg2) {}
162
-
163
- // TODO (fix): Should use rocksdb::Slice since "to" cannot outlive "from".
164
- static napi_status ToString(napi_env env, napi_value from, rocksdb::PinnableSlice& to) {
165
- napi_valuetype type;
166
- NAPI_STATUS_RETURN(napi_typeof(env, from, &type));
167
-
168
- if (type == napi_string) {
169
- size_t length = 0;
170
- NAPI_STATUS_RETURN(napi_get_value_string_utf8(env, from, nullptr, 0, &length));
171
- to.GetSelf()->resize(length, '\0');
172
- NAPI_STATUS_RETURN(napi_get_value_string_utf8(env, from, &(*to.GetSelf())[0], length + 1, &length));
173
- to.PinSelf();
174
- } else {
175
- bool isBuffer;
176
- NAPI_STATUS_RETURN(napi_is_buffer(env, from, &isBuffer));
177
-
178
- if (isBuffer) {
179
- char* buf = nullptr;
180
- size_t length = 0;
181
- NAPI_STATUS_RETURN(napi_get_buffer_info(env, from, reinterpret_cast<void**>(&buf), &length));
182
-
183
- // TODO (fix): Should extend life of "from". Or "to" should be a non-pinnable slice.
184
- to.PinSlice(rocksdb::Slice(buf, length), noop, nullptr, nullptr);
185
- } else {
186
- return napi_invalid_arg;
187
- }
188
- }
189
-
190
- return napi_ok;
191
- }
192
-
193
- static std::optional<std::string> StringProperty(napi_env env, napi_value opts, const std::string_view& name) {
194
- if (HasProperty(env, opts, name)) {
195
- const auto property = GetProperty(env, opts, name);
196
- std::string value;
197
- ToString(env, property, value);
198
- return value;
199
- }
200
- return {};
201
- }
202
-
203
- static napi_status CallFunction(napi_env env, napi_value callback, const int argc, napi_value* argv) {
204
- napi_value global;
205
-
206
- NAPI_STATUS_RETURN(napi_get_global(env, &global));
207
- NAPI_STATUS_RETURN(napi_call_function(env, global, callback, argc, argv, nullptr));
208
-
209
- return napi_ok;
210
- }
211
-
212
- static napi_value ToError(napi_env env, const rocksdb::Status& status) {
213
- if (status.ok()) {
214
- return 0;
215
- }
216
-
217
- const auto msg = status.ToString();
218
-
219
- if (status.IsNotFound()) {
220
- return CreateError(env, "LEVEL_NOT_FOUND", msg);
221
- } else if (status.IsCorruption()) {
222
- return CreateError(env, "LEVEL_CORRUPTION", msg);
223
- } else if (status.IsTryAgain()) {
224
- return CreateError(env, "LEVEL_TRYAGAIN", msg);
225
- } else if (status.IsIOError()) {
226
- if (msg.find("IO error: lock ") != std::string::npos) { // env_posix.cc
227
- return CreateError(env, "LEVEL_LOCKED", msg);
228
- } else if (msg.find("IO error: LockFile ") != std::string::npos) { // env_win.cc
229
- return CreateError(env, "LEVEL_LOCKED", msg);
230
- } else if (msg.find("IO error: While lock file") != std::string::npos) { // env_mac.cc
231
- return CreateError(env, "LEVEL_LOCKED", msg);
232
- } else {
233
- return CreateError(env, "LEVEL_IO_ERROR", msg);
234
- }
235
- }
236
-
237
- return CreateError(env, {}, msg);
238
- }
239
-
240
- template <typename T>
241
- static void Finalize(napi_env env, void* data, void* hint) {
242
- if (hint) {
243
- delete reinterpret_cast<T*>(hint);
244
- }
245
- }
246
-
247
- template <typename T>
248
- napi_status Convert(napi_env env, T&& s, bool asBuffer, napi_value& result) {
249
- if (!s) {
250
- return napi_get_null(env, &result);
251
- } else if (asBuffer) {
252
- using Y = typename std::decay<decltype(*s)>::type;
253
- auto ptr = new Y(std::move(*s));
254
- return napi_create_external_buffer(env, ptr->size(), const_cast<char*>(ptr->data()), Finalize<Y>, ptr, &result);
255
- } else {
256
- return napi_create_string_utf8(env, s->data(), s->size(), &result);
257
- }
258
- }
259
-
260
- /**
261
- * Base worker class. Handles the async work. Derived classes can override the
262
- * following virtual methods (listed in the order in which they're called):
263
- *
264
- * - Execute (abstract, worker pool thread): main work
265
- * - OnOk (main thread): call JS callback on success
266
- * - OnError (main thread): call JS callback on error
267
- * - Destroy (main thread): do cleanup regardless of success
268
- */
269
- struct Worker {
270
- Worker(napi_env env, Database* database, napi_value callback, const std::string& resourceName) : database_(database) {
271
- NAPI_STATUS_THROWS_VOID(napi_create_reference(env, callback, 1, &callbackRef_));
272
- napi_value asyncResourceName;
273
- NAPI_STATUS_THROWS_VOID(napi_create_string_utf8(env, resourceName.data(), resourceName.size(), &asyncResourceName));
274
- NAPI_STATUS_THROWS_VOID(
275
- napi_create_async_work(env, callback, asyncResourceName, Worker::Execute, Worker::Complete, this, &asyncWork_));
276
- }
277
-
278
- virtual ~Worker() {}
279
-
280
- static void Execute(napi_env env, void* data) {
281
- auto self = reinterpret_cast<Worker*>(data);
282
- self->status_ = self->Execute(*self->database_);
283
- }
284
-
285
- static void Complete(napi_env env, napi_status status, void* data) {
286
- auto self = reinterpret_cast<Worker*>(data);
287
-
288
- // TODO (fix): napi status handling...
289
-
290
- napi_value callback;
291
- napi_get_reference_value(env, self->callbackRef_, &callback);
292
-
293
- if (self->status_.ok()) {
294
- self->OnOk(env, callback);
295
- } else {
296
- self->OnError(env, callback, ToError(env, self->status_));
297
- }
298
-
299
- self->Destroy(env);
300
-
301
- napi_delete_reference(env, self->callbackRef_);
302
- napi_delete_async_work(env, self->asyncWork_);
303
-
304
- delete self;
305
- }
306
-
307
- virtual rocksdb::Status Execute(Database& database) = 0;
308
-
309
- virtual napi_status OnOk(napi_env env, napi_value callback) {
310
- napi_value argv[1];
311
- NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
312
-
313
- return CallFunction(env, callback, 1, &argv[0]);
314
- }
315
-
316
- virtual napi_status OnError(napi_env env, napi_value callback, napi_value err) {
317
- return CallFunction(env, callback, 1, &err);
318
- }
319
-
320
- virtual void Destroy(napi_env env) {}
321
-
322
- void Queue(napi_env env) { napi_queue_async_work(env, asyncWork_); }
323
-
324
- Database* database_;
325
-
326
- private:
327
- napi_ref callbackRef_;
328
- napi_async_work asyncWork_;
329
- rocksdb::Status status_;
330
- };
331
-
332
42
  struct ColumnFamily {
333
43
  napi_ref ref;
334
44
  napi_value val;
@@ -336,80 +46,69 @@ struct ColumnFamily {
336
46
  rocksdb::ColumnFamilyDescriptor descriptor;
337
47
  };
338
48
 
339
- struct Database {
340
- void AttachIterator(napi_env env, Iterator* iterator) {
341
- iterators_.insert(iterator);
342
- IncrementPriorityWork(env);
343
- }
344
-
345
- void DetachIterator(napi_env env, Iterator* iterator) {
346
- iterators_.erase(iterator);
347
- DecrementPriorityWork(env);
348
- }
349
-
350
- void AttachUpdates(napi_env env, Updates* update) {
351
- updates_.insert(update);
352
- IncrementPriorityWork(env);
353
- }
49
+ struct Closable {
50
+ virtual ~Closable() {}
51
+ virtual rocksdb::Status Close() = 0;
52
+ };
354
53
 
355
- void DetachUpdates(napi_env env, Updates* update) {
356
- updates_.erase(update);
357
- DecrementPriorityWork(env);
358
- }
54
+ struct Database final {
55
+ ~Database() { assert(!db); }
359
56
 
360
- void IncrementPriorityWork(napi_env env) { napi_reference_ref(env, priorityRef_, &priorityWork_); }
57
+ rocksdb::Status Close() {
58
+ if (!db) {
59
+ return rocksdb::Status::OK();
60
+ }
361
61
 
362
- void DecrementPriorityWork(napi_env env) {
363
- napi_reference_unref(env, priorityRef_, &priorityWork_);
62
+ for (auto closable : closables) {
63
+ closable->Close();
64
+ }
65
+ closables.clear();
364
66
 
365
- if (priorityWork_ == 0 && pendingCloseWorker_) {
366
- pendingCloseWorker_->Queue(env);
367
- pendingCloseWorker_ = nullptr;
67
+ for (auto& [id, column] : columns) {
68
+ db->DestroyColumnFamilyHandle(column.handle);
368
69
  }
369
- }
70
+ columns.clear();
370
71
 
371
- bool HasPriorityWork() const { return priorityWork_ > 0; }
72
+ db->FlushWAL(true);
372
73
 
373
- std::unique_ptr<rocksdb::DB> db_;
374
- Worker* pendingCloseWorker_;
375
- std::set<Iterator*> iterators_;
376
- std::set<Updates*> updates_;
377
- std::map<int32_t, ColumnFamily> columns_;
378
- napi_ref priorityRef_;
74
+ auto db2 = std::move(db);
75
+ return db2->Close();
76
+ }
379
77
 
380
- private:
381
- uint32_t priorityWork_ = 0;
78
+ std::unique_ptr<rocksdb::DB> db;
79
+ std::set<Closable*> closables;
80
+ std::map<int32_t, ColumnFamily> columns;
382
81
  };
383
82
 
384
83
  enum BatchOp { Empty, Put, Delete, Merge, Data };
385
84
 
386
85
  struct BatchEntry {
387
86
  BatchOp op = BatchOp::Empty;
388
- std::optional<std::string> key;
389
- std::optional<std::string> val;
390
- std::optional<ColumnFamily> column;
87
+ std::optional<std::string> key = std::nullopt;
88
+ std::optional<std::string> val = std::nullopt;
89
+ std::optional<ColumnFamily> column = std::nullopt;
391
90
  };
392
91
 
393
92
  struct BatchIterator : public rocksdb::WriteBatch::Handler {
394
93
  BatchIterator(Database* database,
395
- bool keys = true,
396
- bool values = true,
397
- bool data = true,
398
- const rocksdb::ColumnFamilyHandle* column = nullptr,
399
- bool keyAsBuffer = false,
400
- bool valueAsBuffer = false)
94
+ const bool keys,
95
+ const bool values,
96
+ const bool data,
97
+ const rocksdb::ColumnFamilyHandle* column,
98
+ const Encoding keyEncoding,
99
+ const Encoding valueEncoding)
401
100
  : database_(database),
402
101
  keys_(keys),
403
102
  values_(values),
404
103
  data_(data),
405
104
  column_(column),
406
- keyAsBuffer_(keyAsBuffer),
407
- valueAsBuffer_(valueAsBuffer) {}
105
+ keyEncoding_(keyEncoding),
106
+ valueEncoding_(valueEncoding) {}
408
107
 
409
108
  napi_status Iterate(napi_env env, const rocksdb::WriteBatch& batch, napi_value* result) {
410
109
  cache_.reserve(batch.Count());
411
110
 
412
- batch.Iterate(this); // TODO (fix): Handle error?
111
+ ROCKS_STATUS_RETURN_NAPI(batch.Iterate(this));
413
112
 
414
113
  napi_value putStr;
415
114
  NAPI_STATUS_RETURN(napi_create_string_utf8(env, "put", NAPI_AUTO_LENGTH, &putStr));
@@ -444,11 +143,11 @@ struct BatchIterator : public rocksdb::WriteBatch::Handler {
444
143
  NAPI_STATUS_RETURN(napi_set_element(env, *result, n * 4 + 0, op));
445
144
 
446
145
  napi_value key;
447
- NAPI_STATUS_RETURN(Convert(env, cache_[n].key, keyAsBuffer_, key));
146
+ NAPI_STATUS_RETURN(Convert(env, cache_[n].key, keyEncoding_, key));
448
147
  NAPI_STATUS_RETURN(napi_set_element(env, *result, n * 4 + 1, key));
449
148
 
450
149
  napi_value val;
451
- NAPI_STATUS_RETURN(Convert(env, cache_[n].val, valueAsBuffer_, val));
150
+ NAPI_STATUS_RETURN(Convert(env, cache_[n].val, valueEncoding_, val));
452
151
  NAPI_STATUS_RETURN(napi_set_element(env, *result, n * 4 + 2, val));
453
152
 
454
153
  // TODO (fix)
@@ -466,9 +165,7 @@ struct BatchIterator : public rocksdb::WriteBatch::Handler {
466
165
  return rocksdb::Status::OK();
467
166
  }
468
167
 
469
- BatchEntry entry;
470
-
471
- entry.op = BatchOp::Put;
168
+ BatchEntry entry = {BatchOp::Put};
472
169
 
473
170
  if (keys_) {
474
171
  entry.key = key.ToStringView();
@@ -478,8 +175,8 @@ struct BatchIterator : public rocksdb::WriteBatch::Handler {
478
175
  entry.val = value.ToStringView();
479
176
  }
480
177
 
481
- // if (database_ && database_->columns_.find(column_family_id) != database_->columns_.end()) {
482
- // entry.column = database_->columns_[column_family_id];
178
+ // if (database_ && database_->columns.find(column_family_id) != database_->columns.end()) {
179
+ // entry.column = database_->columns[column_family_id];
483
180
  // }
484
181
 
485
182
  cache_.push_back(entry);
@@ -492,16 +189,14 @@ struct BatchIterator : public rocksdb::WriteBatch::Handler {
492
189
  return rocksdb::Status::OK();
493
190
  }
494
191
 
495
- BatchEntry entry;
496
-
497
- entry.op = BatchOp::Delete;
192
+ BatchEntry entry = {BatchOp::Delete};
498
193
 
499
194
  if (keys_) {
500
195
  entry.key = key.ToStringView();
501
196
  }
502
197
 
503
- // if (database_ && database_->columns_.find(column_family_id) != database_->columns_.end()) {
504
- // entry.column = database_->columns_[column_family_id];
198
+ // if (database_ && database_->columns.find(column_family_id) != database_->columns.end()) {
199
+ // entry.column = database_->columns[column_family_id];
505
200
  // }
506
201
 
507
202
  cache_.push_back(entry);
@@ -514,9 +209,7 @@ struct BatchIterator : public rocksdb::WriteBatch::Handler {
514
209
  return rocksdb::Status::OK();
515
210
  }
516
211
 
517
- BatchEntry entry;
518
-
519
- entry.op = BatchOp::Merge;
212
+ BatchEntry entry = {BatchOp::Merge};
520
213
 
521
214
  if (keys_) {
522
215
  entry.key = key.ToStringView();
@@ -526,8 +219,8 @@ struct BatchIterator : public rocksdb::WriteBatch::Handler {
526
219
  entry.val = value.ToStringView();
527
220
  }
528
221
 
529
- // if (database_ && database_->columns_.find(column_family_id) != database_->columns_.end()) {
530
- // entry.column = database_->columns_[column_family_id];
222
+ // if (database_ && database_->columns.find(column_family_id) != database_->columns.end()) {
223
+ // entry.column = database_->columns[column_family_id];
531
224
  // }
532
225
 
533
226
  cache_.push_back(entry);
@@ -540,9 +233,7 @@ struct BatchIterator : public rocksdb::WriteBatch::Handler {
540
233
  return;
541
234
  }
542
235
 
543
- BatchEntry entry;
544
-
545
- entry.op = BatchOp::Data;
236
+ BatchEntry entry = {BatchOp::Data};
546
237
 
547
238
  entry.val = data.ToStringView();
548
239
 
@@ -553,40 +244,47 @@ struct BatchIterator : public rocksdb::WriteBatch::Handler {
553
244
 
554
245
  private:
555
246
  Database* database_;
556
- bool keys_;
557
- bool values_;
558
- bool data_;
247
+ const bool keys_;
248
+ const bool values_;
249
+ const bool data_;
559
250
  const rocksdb::ColumnFamilyHandle* column_;
560
- bool keyAsBuffer_;
561
- bool valueAsBuffer_;
251
+ const Encoding keyEncoding_;
252
+ const Encoding valueEncoding_;
562
253
  std::vector<BatchEntry> cache_;
563
254
  };
564
255
 
565
- struct Updates : public BatchIterator {
256
+ struct Updates : public BatchIterator, public Closable {
566
257
  Updates(Database* database,
567
- int64_t seqNumber,
568
- bool keys,
569
- bool values,
570
- bool data,
258
+ const int64_t seqNumber,
259
+ const bool keys,
260
+ const bool values,
261
+ const bool data,
571
262
  const rocksdb::ColumnFamilyHandle* column,
572
- bool keyAsBuffer,
573
- bool valueAsBuffer)
574
- : BatchIterator(database, keys, values, data, column, keyAsBuffer, valueAsBuffer),
263
+ const Encoding keyEncoding,
264
+ const Encoding valueEncoding)
265
+ : BatchIterator(database, keys, values, data, column, keyEncoding, valueEncoding),
575
266
  database_(database),
576
267
  start_(seqNumber) {}
577
268
 
578
- void Close() { iterator_.reset(); }
269
+ virtual ~Updates() { assert(!iterator_); }
270
+
271
+ rocksdb::Status Close() override {
272
+ iterator_.reset();
273
+ return rocksdb::Status::OK();
274
+ }
579
275
 
580
- void Attach(napi_env env, napi_value context) {
581
- napi_create_reference(env, context, 1, &ref_);
582
- database_->AttachUpdates(env, this);
276
+ napi_status Attach(napi_env env, napi_value context) {
277
+ NAPI_STATUS_RETURN(napi_create_reference(env, context, 1, &ref_));
278
+ database_->closables.insert(this);
279
+ return napi_ok;
583
280
  }
584
281
 
585
- void Detach(napi_env env) {
586
- database_->DetachUpdates(env, this);
282
+ napi_status Detach(napi_env env) {
283
+ database_->closables.erase(this);
587
284
  if (ref_) {
588
- napi_delete_reference(env, ref_);
285
+ NAPI_STATUS_RETURN(napi_delete_reference(env, ref_));
589
286
  }
287
+ return napi_ok;
590
288
  }
591
289
 
592
290
  Database* database_;
@@ -597,7 +295,7 @@ struct Updates : public BatchIterator {
597
295
  napi_ref ref_ = nullptr;
598
296
  };
599
297
 
600
- struct BaseIterator {
298
+ struct BaseIterator : public Closable {
601
299
  BaseIterator(Database* database,
602
300
  rocksdb::ColumnFamilyHandle* column,
603
301
  const bool reverse,
@@ -670,9 +368,10 @@ struct BaseIterator {
670
368
  }
671
369
  }
672
370
 
673
- void Close() {
371
+ rocksdb::Status Close() override {
674
372
  snapshot_.reset();
675
373
  iterator_.reset();
374
+ return rocksdb::Status::OK();
676
375
  }
677
376
 
678
377
  bool Valid() const {
@@ -727,7 +426,7 @@ struct BaseIterator {
727
426
  readOptions.async_io = true;
728
427
  readOptions.adaptive_readahead = true;
729
428
 
730
- iterator_.reset(database_->db_->NewIterator(readOptions, column_));
429
+ iterator_.reset(database_->db->NewIterator(readOptions, column_));
731
430
  }
732
431
 
733
432
  int count_ = 0;
@@ -751,33 +450,35 @@ struct Iterator final : public BaseIterator {
751
450
  const std::optional<std::string>& gt,
752
451
  const std::optional<std::string>& gte,
753
452
  const bool fillCache,
754
- const bool keyAsBuffer,
755
- const bool valueAsBuffer,
453
+ const Encoding keyEncoding,
454
+ const Encoding valueEncoding,
756
455
  const size_t highWaterMarkBytes,
757
456
  std::shared_ptr<const rocksdb::Snapshot> snapshot)
758
457
  : BaseIterator(database, column, reverse, lt, lte, gt, gte, limit, fillCache, snapshot),
759
458
  keys_(keys),
760
459
  values_(values),
761
- keyAsBuffer_(keyAsBuffer),
762
- valueAsBuffer_(valueAsBuffer),
460
+ keyEncoding_(keyEncoding),
461
+ valueEncoding_(valueEncoding),
763
462
  highWaterMarkBytes_(highWaterMarkBytes) {}
764
463
 
765
- void Attach(napi_env env, napi_value context) {
766
- napi_create_reference(env, context, 1, &ref_);
767
- database_->AttachIterator(env, this);
464
+ napi_status Attach(napi_env env, napi_value context) {
465
+ NAPI_STATUS_RETURN(napi_create_reference(env, context, 1, &ref_));
466
+ database_->closables.insert(this);
467
+ return napi_ok;
768
468
  }
769
469
 
770
- void Detach(napi_env env) {
771
- database_->DetachIterator(env, this);
470
+ napi_status Detach(napi_env env) {
471
+ database_->closables.erase(this);
772
472
  if (ref_) {
773
- napi_delete_reference(env, ref_);
473
+ NAPI_STATUS_RETURN(napi_delete_reference(env, ref_));
774
474
  }
475
+ return napi_ok;
775
476
  }
776
477
 
777
478
  const bool keys_;
778
479
  const bool values_;
779
- const bool keyAsBuffer_;
780
- const bool valueAsBuffer_;
480
+ const Encoding keyEncoding_;
481
+ const Encoding valueEncoding_;
781
482
  const size_t highWaterMarkBytes_;
782
483
  bool first_ = true;
783
484
 
@@ -785,152 +486,20 @@ struct Iterator final : public BaseIterator {
785
486
  napi_ref ref_ = nullptr;
786
487
  };
787
488
 
788
- static napi_status GetColumnFamily(Database* database,
789
- napi_env env,
790
- napi_value options,
791
- rocksdb::ColumnFamilyHandle** column) {
792
- bool hasColumn = false;
793
- NAPI_STATUS_RETURN(napi_has_named_property(env, options, "column", &hasColumn));
794
-
795
- if (hasColumn) {
796
- napi_value value = nullptr;
797
- NAPI_STATUS_RETURN(napi_get_named_property(env, options, "column", &value));
798
-
799
- bool nully = false;
800
-
801
- napi_value nullVal;
802
- NAPI_STATUS_RETURN(napi_get_null(env, &nullVal));
803
- NAPI_STATUS_RETURN(napi_strict_equals(env, nullVal, value, &nully));
804
- if (nully) {
805
- *column = nullptr;
806
- return napi_ok;
807
- }
808
-
809
- napi_value undefinedVal;
810
- NAPI_STATUS_RETURN(napi_get_undefined(env, &undefinedVal));
811
- NAPI_STATUS_RETURN(napi_strict_equals(env, undefinedVal, value, &nully));
812
- if (nully) {
813
- *column = nullptr;
814
- return napi_ok;
815
- }
816
-
817
- NAPI_STATUS_RETURN(napi_get_value_external(env, value, reinterpret_cast<void**>(column)));
818
- } else if (database) {
819
- *column = database->db_->DefaultColumnFamily();
820
- } else {
821
- *column = nullptr;
822
- }
823
-
824
- return napi_ok;
825
- }
826
-
827
- template <typename State,
828
- typename ExecuteType = std::function<rocksdb::Status(State& state, Database& database)>,
829
- typename OnOkType = std::function<napi_status(State& state, napi_env env, napi_value callback)>>
830
- struct GenericWorker final : public Worker {
831
- template <typename T1, typename T2>
832
- GenericWorker(const std::string& name,
833
- napi_env env,
834
- napi_value callback,
835
- Database* database,
836
- bool priority,
837
- T1&& execute,
838
- T2&& onOK)
839
- : Worker(env, database, callback, name),
840
- priority_(priority),
841
- execute_(std::forward<T1>(execute)),
842
- onOk_(std::forward<T2>(onOK)) {
843
- if (priority_) {
844
- database_->IncrementPriorityWork(env);
845
- }
846
- }
847
-
848
- rocksdb::Status Execute(Database& database) override { return execute_(state_, database); }
849
-
850
- napi_status OnOk(napi_env env, napi_value callback) override { return onOk_(state_, env, callback); }
851
-
852
- void Destroy(napi_env env) override {
853
- if (priority_) {
854
- database_->DecrementPriorityWork(env);
855
- }
856
- Worker::Destroy(env);
857
- }
858
-
859
- private:
860
- State state_;
861
- bool priority_ = false;
862
- ExecuteType execute_;
863
- OnOkType onOk_;
864
- };
865
-
866
- template <typename State, typename T1, typename T2>
867
- Worker* createWorker(const std::string& name,
868
- napi_env env,
869
- napi_value callback,
870
- Database* database,
871
- bool priority,
872
- T1&& execute,
873
- T2&& onOk) {
874
- return new GenericWorker<State, typename std::decay<T1>::type, typename std::decay<T2>::type>(
875
- name, env, callback, database, priority, std::forward<T1>(execute), std::forward<T2>(onOk));
876
- }
877
-
878
- template <typename State, typename T1, typename T2>
879
- void runWorker(const std::string& name,
880
- napi_env env,
881
- napi_value callback,
882
- Database* database,
883
- bool priority,
884
- T1&& execute,
885
- T2&& onOk) {
886
- auto worker = new GenericWorker<State, typename std::decay<T1>::type, typename std::decay<T2>::type>(
887
- name, env, callback, database, priority, std::forward<T1>(execute), std::forward<T2>(onOk));
888
- worker->Queue(env);
889
- }
890
-
891
- /**
892
- * Hook for when the environment exits. This hook will be called after
893
- * already-scheduled napi_async_work items have finished, which gives us
894
- * the guarantee that no db operations will be in-flight at this time.
895
- */
896
489
  static void env_cleanup_hook(void* arg) {
897
490
  auto database = reinterpret_cast<Database*>(arg);
898
-
899
- // Do everything that db_close() does but synchronously. We're expecting that
900
- // GC did not (yet) collect the database because that would be a user mistake
901
- // (not closing their db) made during the lifetime of the environment. That's
902
- // different from an environment being torn down (like the main process or a
903
- // worker thread) where it's our responsibility to clean up. Note also, the
904
- // following code must be a safe noop if called before db_open() or after
905
- // db_close().
906
- if (database && database->db_) {
907
- for (auto& it : database->iterators_) {
908
- // TODO: does not do `napi_delete_reference`. Problem?
909
- it->Close();
910
- }
911
-
912
- for (auto& it : database->updates_) {
913
- // TODO: does not do `napi_delete_reference`. Problem?
914
- it->Close();
915
- }
916
-
917
- for (auto& it : database->columns_) {
918
- database->db_->DestroyColumnFamilyHandle(it.second.handle);
919
- }
920
-
921
- // Having closed the iterators (and released snapshots) we can safely close.
922
- database->db_->Close();
491
+ if (database) {
492
+ database->Close();
923
493
  }
924
494
  }
925
495
 
926
496
  static void FinalizeDatabase(napi_env env, void* data, void* hint) {
927
497
  if (data) {
928
498
  auto database = reinterpret_cast<Database*>(data);
499
+ database->Close();
929
500
  napi_remove_env_cleanup_hook(env, env_cleanup_hook, database);
930
- if (database->priorityRef_)
931
- napi_delete_reference(env, database->priorityRef_);
932
- for (auto& it : database->columns_) {
933
- napi_delete_reference(env, it.second.ref);
501
+ for (auto& [id, column] : database->columns) {
502
+ napi_delete_reference(env, column.ref);
934
503
  }
935
504
  delete database;
936
505
  }
@@ -943,94 +512,109 @@ NAPI_METHOD(db_init) {
943
512
  napi_value result;
944
513
  NAPI_STATUS_THROWS(napi_create_external(env, database, FinalizeDatabase, nullptr, &result));
945
514
 
946
- NAPI_STATUS_THROWS(napi_create_reference(env, result, 0, &database->priorityRef_));
947
-
948
515
  return result;
949
516
  }
950
517
 
951
518
  template <typename T, typename U>
952
- rocksdb::Status InitOptions(napi_env env, T& columnOptions, const U& options) {
519
+ napi_status InitOptions(napi_env env, T& columnOptions, const U& options) {
953
520
  rocksdb::ConfigOptions configOptions;
954
521
 
955
- const auto memtable_memory_budget = Uint32Property(env, options, "memtableMemoryBudget").value_or(256 * 1024 * 1024);
956
-
957
- const auto compaction = StringProperty(env, options, "compaction").value_or("level");
958
-
959
- if (compaction == "universal") {
960
- columnOptions.write_buffer_size = static_cast<size_t>(memtable_memory_budget / 4);
961
- // merge two memtables when flushing to L0
962
- columnOptions.min_write_buffer_number_to_merge = 2;
963
- // this means we'll use 50% extra memory in the worst case, but will reduce
964
- // write stalls.
965
- columnOptions.max_write_buffer_number = 6;
966
- // universal style compaction
967
- columnOptions.compaction_style = rocksdb::kCompactionStyleUniversal;
968
- columnOptions.compaction_options_universal.compression_size_percent = 80;
969
- } else {
970
- // merge two memtables when flushing to L0
971
- columnOptions.min_write_buffer_number_to_merge = 2;
972
- // this means we'll use 50% extra memory in the worst case, but will reduce
973
- // write stalls.
974
- columnOptions.max_write_buffer_number = 6;
975
- // start flushing L0->L1 as soon as possible. each file on level0 is
976
- // (memtable_memory_budget / 2). This will flush level 0 when it's bigger than
977
- // memtable_memory_budget.
978
- columnOptions.level0_file_num_compaction_trigger = 2;
979
- // doesn't really matter much, but we don't want to create too many files
980
- columnOptions.target_file_size_base = memtable_memory_budget / 8;
981
- // make Level1 size equal to Level0 size, so that L0->L1 compactions are fast
982
- columnOptions.max_bytes_for_level_base = memtable_memory_budget;
983
-
984
- // level style compaction
985
- columnOptions.compaction_style = rocksdb::kCompactionStyleLevel;
986
-
987
- // TODO (perf): only compress levels >= 2
522
+ size_t memtable_memory_budget = 256 * 1024 * 1024;
523
+ NAPI_STATUS_RETURN(GetProperty(env, options, "memtableMemoryBudget", memtable_memory_budget));
524
+
525
+ std::optional<std::string> compactionOpt;
526
+ NAPI_STATUS_RETURN(GetProperty(env, options, "compaction", compactionOpt));
527
+ if (compactionOpt) {
528
+ if (*compactionOpt == "universal") {
529
+ columnOptions.write_buffer_size = memtable_memory_budget / 4;
530
+ // merge two memtables when flushing to L0
531
+ columnOptions.min_write_buffer_number_to_merge = 2;
532
+ // this means we'll use 50% extra memory in the worst case, but will reduce
533
+ // write stalls.
534
+ columnOptions.max_write_buffer_number = 6;
535
+ // universal style compaction
536
+ columnOptions.compaction_style = rocksdb::kCompactionStyleUniversal;
537
+ columnOptions.compaction_options_universal.compression_size_percent = 80;
538
+ } else if (*compactionOpt == "level") {
539
+ // merge two memtables when flushing to L0
540
+ columnOptions.min_write_buffer_number_to_merge = 2;
541
+ // this means we'll use 50% extra memory in the worst case, but will reduce
542
+ // write stalls.
543
+ columnOptions.max_write_buffer_number = 6;
544
+ // start flushing L0->L1 as soon as possible. each file on level0 is
545
+ // (memtable_memory_budget / 2). This will flush level 0 when it's bigger than
546
+ // memtable_memory_budget.
547
+ columnOptions.level0_file_num_compaction_trigger = 2;
548
+ // doesn't really matter much, but we don't want to create too many files
549
+ columnOptions.target_file_size_base = memtable_memory_budget / 8;
550
+ // make Level1 size equal to Level0 size, so that L0->L1 compactions are fast
551
+ columnOptions.max_bytes_for_level_base = memtable_memory_budget;
552
+
553
+ // level style compaction
554
+ columnOptions.compaction_style = rocksdb::kCompactionStyleLevel;
555
+
556
+ // only compress levels >= 2
557
+ columnOptions.compression_per_level.resize(columnOptions.num_levels);
558
+ for (int i = 0; i < columnOptions.compression_per_level.size(); ++i) {
559
+ if (i < 2) {
560
+ columnOptions.compression_per_level[i] = rocksdb::kNoCompression;
561
+ } else {
562
+ columnOptions.compression_per_level[i] = rocksdb::kZSTD;
563
+ }
564
+ }
565
+ } else {
566
+ return napi_invalid_arg;
567
+ }
988
568
  }
989
569
 
990
- columnOptions.compression =
991
- BooleanProperty(env, options, "compression").value_or((true)) ? rocksdb::kZSTD : rocksdb::kNoCompression;
992
- if (columnOptions.compression == rocksdb::kZSTD) {
570
+ bool compression = true;
571
+ NAPI_STATUS_RETURN(GetProperty(env, options, "compression", compression));
572
+
573
+ if (compression) {
574
+ columnOptions.compression = rocksdb::kZSTD;
993
575
  columnOptions.compression_opts.max_dict_bytes = 16 * 1024;
994
576
  columnOptions.compression_opts.zstd_max_train_bytes = 16 * 1024 * 100;
995
577
  // TODO (perf): compression_opts.parallel_threads
996
578
  }
997
579
 
998
- const auto prefixExtractorOpt = StringProperty(env, options, "prefixExtractor");
580
+ std::optional<std::string> prefixExtractorOpt;
581
+ NAPI_STATUS_RETURN(GetProperty(env, options, "prefixExtractor", prefixExtractorOpt));
999
582
  if (prefixExtractorOpt) {
1000
- ROCKS_STATUS_RETURN(
583
+ ROCKS_STATUS_RETURN_NAPI(
1001
584
  rocksdb::SliceTransform::CreateFromString(configOptions, *prefixExtractorOpt, &columnOptions.prefix_extractor));
1002
585
  }
1003
586
 
1004
- const auto comparatorOpt = StringProperty(env, options, "comparator");
587
+ std::optional<std::string> comparatorOpt;
588
+ NAPI_STATUS_RETURN(GetProperty(env, options, "comparator", comparatorOpt));
1005
589
  if (comparatorOpt) {
1006
- ROCKS_STATUS_RETURN(
590
+ ROCKS_STATUS_RETURN_NAPI(
1007
591
  rocksdb::Comparator::CreateFromString(configOptions, *comparatorOpt, &columnOptions.comparator));
1008
592
  }
1009
593
 
1010
- const auto mergeOperatorOpt = StringProperty(env, options, "mergeOperator");
594
+ std::optional<std::string> mergeOperatorOpt;
595
+ NAPI_STATUS_RETURN(GetProperty(env, options, "mergeOperator", mergeOperatorOpt));
1011
596
  if (mergeOperatorOpt) {
1012
- if (*mergeOperatorOpt == "maxRev") {
1013
- columnOptions.merge_operator = std::make_shared<MaxRevOperator>();
1014
- } else {
1015
- ROCKS_STATUS_RETURN(
1016
- rocksdb::MergeOperator::CreateFromString(configOptions, *mergeOperatorOpt, &columnOptions.merge_operator));
1017
- }
597
+ ROCKS_STATUS_RETURN_NAPI(
598
+ rocksdb::MergeOperator::CreateFromString(configOptions, *mergeOperatorOpt, &columnOptions.merge_operator));
1018
599
  }
1019
600
 
1020
- const auto cacheSize = Uint32Property(env, options, "cacheSize").value_or(8 << 20);
601
+ uint32_t cacheSize = 8 << 20;
602
+ NAPI_STATUS_RETURN(GetProperty(env, options, "cacheSize", cacheSize));
1021
603
 
1022
604
  rocksdb::BlockBasedTableOptions tableOptions;
1023
605
 
1024
606
  if (cacheSize) {
1025
607
  tableOptions.block_cache = rocksdb::NewLRUCache(cacheSize);
1026
- tableOptions.cache_index_and_filter_blocks =
1027
- BooleanProperty(env, options, "cacheIndexAndFilterBlocks").value_or(true);
608
+ tableOptions.cache_index_and_filter_blocks = true;
609
+ NAPI_STATUS_RETURN(
610
+ GetProperty(env, options, "cacheIndexAndFilterBlocks", tableOptions.cache_index_and_filter_blocks));
1028
611
  } else {
1029
612
  tableOptions.no_block_cache = true;
1030
613
  tableOptions.cache_index_and_filter_blocks = false;
1031
614
  }
1032
615
 
1033
- const auto optimize = StringProperty(env, options, "optimize").value_or("");
616
+ std::string optimize = "";
617
+ NAPI_STATUS_RETURN(GetProperty(env, options, "optimize", optimize));
1034
618
 
1035
619
  if (optimize == "point-lookup") {
1036
620
  tableOptions.data_block_index_type = rocksdb::BlockBasedTableOptions::kDataBlockBinaryAndHash;
@@ -1042,25 +626,29 @@ rocksdb::Status InitOptions(napi_env env, T& columnOptions, const U& options) {
1042
626
  } else if (optimize == "range-lookup") {
1043
627
  // TODO?
1044
628
  } else {
1045
- tableOptions.filter_policy.reset(rocksdb::NewRibbonFilterPolicy(10));
629
+ tableOptions.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10));
1046
630
  }
1047
631
 
1048
- const auto filterPolicyOpt = StringProperty(env, options, "filterPolicy");
632
+ std::optional<std::string> filterPolicyOpt;
633
+ NAPI_STATUS_RETURN(GetProperty(env, options, "filterPolicy", filterPolicyOpt));
1049
634
  if (filterPolicyOpt) {
1050
- rocksdb::ConfigOptions configOptions;
1051
- ROCKS_STATUS_RETURN(
635
+ ROCKS_STATUS_RETURN_NAPI(
1052
636
  rocksdb::FilterPolicy::CreateFromString(configOptions, *filterPolicyOpt, &tableOptions.filter_policy));
1053
637
  }
1054
638
 
1055
- tableOptions.block_size = Uint32Property(env, options, "blockSize").value_or(4096);
1056
- tableOptions.block_restart_interval = Uint32Property(env, options, "blockRestartInterval").value_or(16);
639
+ NAPI_STATUS_RETURN(GetProperty(env, options, "blockSize", tableOptions.block_size));
640
+
641
+ NAPI_STATUS_RETURN(GetProperty(env, options, "blockRestartInterval", tableOptions.block_restart_interval));
642
+
1057
643
  tableOptions.format_version = 5;
1058
644
  tableOptions.checksum = rocksdb::kXXH3;
1059
- tableOptions.optimize_filters_for_memory = BooleanProperty(env, options, "optimizeFiltersForMemory").value_or(true);
645
+
646
+ tableOptions.optimize_filters_for_memory = true;
647
+ NAPI_STATUS_RETURN(GetProperty(env, options, "optimizeFiltersForMemory", tableOptions.optimize_filters_for_memory));
1060
648
 
1061
649
  columnOptions.table_factory.reset(rocksdb::NewBlockBasedTableFactory(tableOptions));
1062
650
 
1063
- return rocksdb::Status::OK();
651
+ return napi_ok;
1064
652
  }
1065
653
 
1066
654
  NAPI_METHOD(db_open) {
@@ -1070,32 +658,44 @@ NAPI_METHOD(db_open) {
1070
658
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
1071
659
 
1072
660
  std::string location;
1073
- NAPI_STATUS_THROWS(ToString(env, argv[1], location));
661
+ NAPI_STATUS_THROWS(GetValue(env, argv[1], location));
1074
662
 
1075
663
  rocksdb::Options dbOptions;
1076
664
 
1077
- dbOptions.IncreaseParallelism(Uint32Property(env, argv[2], "parallelism")
1078
- .value_or(std::max<uint32_t>(1, std::thread::hardware_concurrency() / 2)));
665
+ const auto options = argv[2];
666
+
667
+ int parallelism = std::max<int>(1, std::thread::hardware_concurrency() / 2);
668
+ NAPI_STATUS_THROWS(GetProperty(env, options, "parallelism", parallelism));
669
+ dbOptions.IncreaseParallelism(parallelism);
670
+
671
+ uint32_t walTTL = 0;
672
+ NAPI_STATUS_THROWS(GetProperty(env, options, "walTTL", walTTL));
673
+ dbOptions.WAL_ttl_seconds = walTTL / 1e3;
674
+
675
+ uint32_t walSizeLimit = 0;
676
+ NAPI_STATUS_THROWS(GetProperty(env, options, "walSizeLimit", walSizeLimit));
677
+ dbOptions.WAL_size_limit_MB = walSizeLimit / 1e6;
678
+
679
+ bool walCompression = false;
680
+ NAPI_STATUS_THROWS(GetProperty(env, options, "walCompression", walCompression));
681
+ dbOptions.wal_compression =
682
+ walCompression ? rocksdb::CompressionType::kZSTD : rocksdb::CompressionType::kNoCompression;
1079
683
 
1080
- dbOptions.create_if_missing = BooleanProperty(env, argv[2], "createIfMissing").value_or(true);
1081
- dbOptions.error_if_exists = BooleanProperty(env, argv[2], "errorIfExists").value_or(false);
1082
684
  dbOptions.avoid_unnecessary_blocking_io = true;
1083
685
  dbOptions.write_dbid_to_manifest = true;
1084
686
  dbOptions.use_adaptive_mutex = true; // We don't have soo many threads in the libuv thread pool...
1085
687
  dbOptions.enable_pipelined_write = false; // We only write in the main thread...
1086
- dbOptions.WAL_ttl_seconds = Uint32Property(env, argv[2], "walTTL").value_or(0) / 1e3;
1087
- dbOptions.WAL_size_limit_MB = Uint32Property(env, argv[2], "walSizeLimit").value_or(0) / 1e6;
1088
- dbOptions.wal_compression = BooleanProperty(env, argv[2], "walCompression").value_or(false)
1089
- ? rocksdb::CompressionType::kZSTD
1090
- : rocksdb::CompressionType::kNoCompression;
1091
688
  dbOptions.create_missing_column_families = true;
1092
- dbOptions.unordered_write = BooleanProperty(env, argv[2], "unorderedWrite").value_or(false);
1093
689
  dbOptions.fail_if_options_file_error = true;
1094
- dbOptions.manual_wal_flush = BooleanProperty(env, argv[2], "manualWalFlush").value_or(false);
690
+
691
+ NAPI_STATUS_THROWS(GetProperty(env, options, "createIfMissing", dbOptions.create_if_missing));
692
+ NAPI_STATUS_THROWS(GetProperty(env, options, "errorIfExists", dbOptions.error_if_exists));
693
+ NAPI_STATUS_THROWS(GetProperty(env, options, "unorderedWrite", dbOptions.unordered_write));
1095
694
 
1096
695
  // TODO (feat): dbOptions.listeners
1097
696
 
1098
- const auto infoLogLevel = StringProperty(env, argv[2], "infoLogLevel").value_or("");
697
+ std::string infoLogLevel;
698
+ NAPI_STATUS_THROWS(GetProperty(env, options, "infoLogLevel", infoLogLevel));
1099
699
  if (infoLogLevel.size() > 0) {
1100
700
  rocksdb::InfoLogLevel lvl = {};
1101
701
 
@@ -1122,16 +722,16 @@ NAPI_METHOD(db_open) {
1122
722
  dbOptions.info_log.reset(new NullLogger());
1123
723
  }
1124
724
 
1125
- ROCKS_STATUS_THROWS(InitOptions(env, dbOptions, argv[2]));
725
+ NAPI_STATUS_THROWS(InitOptions(env, dbOptions, options));
1126
726
 
1127
727
  std::vector<rocksdb::ColumnFamilyDescriptor> descriptors;
1128
728
 
1129
729
  bool hasColumns;
1130
- NAPI_STATUS_THROWS(napi_has_named_property(env, argv[2], "columns", &hasColumns));
730
+ NAPI_STATUS_THROWS(napi_has_named_property(env, options, "columns", &hasColumns));
1131
731
 
1132
732
  if (hasColumns) {
1133
733
  napi_value columns;
1134
- NAPI_STATUS_THROWS(napi_get_named_property(env, argv[2], "columns", &columns));
734
+ NAPI_STATUS_THROWS(napi_get_named_property(env, options, "columns", &columns));
1135
735
 
1136
736
  napi_value keys;
1137
737
  NAPI_STATUS_THROWS(napi_get_property_names(env, columns, &keys));
@@ -1147,26 +747,25 @@ NAPI_METHOD(db_open) {
1147
747
  napi_value column;
1148
748
  NAPI_STATUS_THROWS(napi_get_property(env, columns, key, &column));
1149
749
 
1150
- ROCKS_STATUS_THROWS(InitOptions(env, descriptors[n].options, column));
750
+ NAPI_STATUS_THROWS(InitOptions(env, descriptors[n].options, column));
1151
751
 
1152
- NAPI_STATUS_THROWS(ToString(env, key, descriptors[n].name));
752
+ NAPI_STATUS_THROWS(GetValue(env, key, descriptors[n].name));
1153
753
  }
1154
754
  }
1155
755
 
1156
756
  auto callback = argv[3];
1157
757
 
1158
- runWorker<std::vector<rocksdb::ColumnFamilyHandle*>>(
1159
- "leveldown.open", env, callback, database, false,
1160
- [=](auto& handles, auto& database) -> rocksdb::Status {
758
+ runAsync<std::vector<rocksdb::ColumnFamilyHandle*>>(
759
+ "leveldown.open", env, callback,
760
+ [=](auto& handles) {
1161
761
  rocksdb::DB* db = nullptr;
1162
762
  const auto status = descriptors.empty() ? rocksdb::DB::Open(dbOptions, location, &db)
1163
763
  : rocksdb::DB::Open(dbOptions, location, descriptors, &handles, &db);
1164
- database.db_.reset(db);
764
+ database->db.reset(db);
1165
765
  return status;
1166
766
  },
1167
- [=](auto& handles, napi_env env, napi_value callback) -> napi_status {
1168
- napi_value argv[2];
1169
- NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
767
+ [=](auto& handles, auto env, auto& argv) {
768
+ argv.resize(2);
1170
769
 
1171
770
  const auto size = handles.size();
1172
771
  NAPI_STATUS_RETURN(napi_create_object(env, &argv[1]));
@@ -1177,13 +776,11 @@ NAPI_METHOD(db_open) {
1177
776
  column.descriptor = descriptors[n];
1178
777
  NAPI_STATUS_RETURN(napi_create_external(env, column.handle, nullptr, nullptr, &column.val));
1179
778
  NAPI_STATUS_RETURN(napi_create_reference(env, column.val, 1, &column.ref));
1180
-
1181
779
  NAPI_STATUS_RETURN(napi_set_named_property(env, argv[1], descriptors[n].name.c_str(), column.val));
1182
-
1183
- database->columns_[column.handle->GetID()] = column;
780
+ database->columns[column.handle->GetID()] = column;
1184
781
  }
1185
782
 
1186
- return CallFunction(env, callback, 2, argv);
783
+ return napi_ok;
1187
784
  });
1188
785
 
1189
786
  return 0;
@@ -1202,28 +799,9 @@ NAPI_METHOD(db_close) {
1202
799
  auto callback = argv[1];
1203
800
 
1204
801
  struct State {};
1205
- auto worker = createWorker<State>(
1206
- "leveldown.close", env, callback, database, false,
1207
- [=](auto& state, auto& database) -> rocksdb::Status {
1208
- for (auto& it : database.columns_) {
1209
- database.db_->DestroyColumnFamilyHandle(it.second.handle);
1210
- }
1211
- database.columns_.clear();
1212
-
1213
- return database.db_->Close();
1214
- },
1215
- [](auto& state, napi_env env, napi_value callback) -> napi_status {
1216
- napi_value argv[1];
1217
- NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
1218
-
1219
- return CallFunction(env, callback, 1, &argv[0]);
1220
- });
1221
-
1222
- if (!database->HasPriorityWork()) {
1223
- worker->Queue(env);
1224
- } else {
1225
- database->pendingCloseWorker_ = worker;
1226
- }
802
+ runAsync<State>(
803
+ "leveldown.close", env, callback, [=](auto& state) { return database->Close(); },
804
+ [](auto& state, auto env, auto& argv) { return napi_ok; });
1227
805
 
1228
806
  return 0;
1229
807
  }
@@ -1234,40 +812,38 @@ NAPI_METHOD(updates_init) {
1234
812
  Database* database;
1235
813
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
1236
814
 
1237
- napi_value sinceProperty;
1238
- int64_t since;
1239
- NAPI_STATUS_THROWS(napi_get_named_property(env, argv[1], "since", &sinceProperty));
1240
- NAPI_STATUS_THROWS(napi_get_value_int64(env, sinceProperty, &since));
815
+ const auto options = argv[1];
816
+
817
+ int64_t since = 0;
818
+ NAPI_STATUS_THROWS(GetProperty(env, options, "since", since));
819
+
820
+ bool keys = true;
821
+ NAPI_STATUS_THROWS(GetProperty(env, options, "keys", keys));
1241
822
 
1242
- napi_value keysProperty;
1243
- bool keys;
1244
- NAPI_STATUS_THROWS(napi_get_named_property(env, argv[1], "keys", &keysProperty));
1245
- NAPI_STATUS_THROWS(napi_get_value_bool(env, keysProperty, &keys));
823
+ bool values = true;
824
+ NAPI_STATUS_THROWS(GetProperty(env, options, "values", values));
1246
825
 
1247
- napi_value valuesProperty;
1248
- bool values;
1249
- NAPI_STATUS_THROWS(napi_get_named_property(env, argv[1], "values", &valuesProperty));
1250
- NAPI_STATUS_THROWS(napi_get_value_bool(env, valuesProperty, &values));
826
+ bool data = true;
827
+ NAPI_STATUS_THROWS(GetProperty(env, options, "data", data));
1251
828
 
1252
- napi_value dataProperty;
1253
- bool data;
1254
- NAPI_STATUS_THROWS(napi_get_named_property(env, argv[1], "data", &dataProperty));
1255
- NAPI_STATUS_THROWS(napi_get_value_bool(env, dataProperty, &data));
829
+ Encoding keyEncoding = Encoding::String;
830
+ NAPI_STATUS_THROWS(GetProperty(env, options, "keyEncoding", keyEncoding));
1256
831
 
1257
- const bool keyAsBuffer = EncodingIsBuffer(env, argv[1], "keyEncoding");
1258
- const bool valueAsBuffer = EncodingIsBuffer(env, argv[1], "valueEncoding");
832
+ Encoding valueEncoding = Encoding::String;
833
+ NAPI_STATUS_THROWS(GetProperty(env, options, "valueEncoding", valueEncoding));
1259
834
 
1260
835
  rocksdb::ColumnFamilyHandle* column = nullptr;
1261
- NAPI_STATUS_THROWS(GetColumnFamily(nullptr, env, argv[1], &column));
836
+ NAPI_STATUS_THROWS(GetProperty(env, options, "column", column));
1262
837
 
1263
- auto updates = std::make_unique<Updates>(database, since, keys, values, data, column, keyAsBuffer, valueAsBuffer);
838
+ auto updates =
839
+ std::unique_ptr<Updates>(new Updates(database, since, keys, values, data, column, keyEncoding, valueEncoding));
1264
840
 
1265
841
  napi_value result;
1266
842
  NAPI_STATUS_THROWS(napi_create_external(env, updates.get(), Finalize<Updates>, updates.get(), &result));
1267
843
 
1268
844
  // Prevent GC of JS object before the iterator is closed (explicitly or on
1269
845
  // db close) and keep track of non-closed iterators to end them on db close.
1270
- updates.release()->Attach(env, result);
846
+ NAPI_STATUS_THROWS(updates.release()->Attach(env, result));
1271
847
 
1272
848
  return result;
1273
849
  }
@@ -1280,12 +856,12 @@ NAPI_METHOD(updates_next) {
1280
856
 
1281
857
  auto callback = argv[1];
1282
858
 
1283
- runWorker<rocksdb::BatchResult>(
1284
- "leveldown.updates.next", env, callback, updates->database_, true,
1285
- [=](auto& batchResult, auto& database) -> rocksdb::Status {
859
+ runAsync<rocksdb::BatchResult>(
860
+ "leveldown.updates.next", env, callback,
861
+ [=](auto& batchResult) {
1286
862
  if (!updates->iterator_) {
1287
863
  rocksdb::TransactionLogIterator::ReadOptions options;
1288
- const auto status = database.db_->GetUpdatesSince(updates->start_, &updates->iterator_, options);
864
+ const auto status = updates->database_->db->GetUpdatesSince(updates->start_, &updates->iterator_, options);
1289
865
  if (!status.ok()) {
1290
866
  return status;
1291
867
  }
@@ -1301,22 +877,18 @@ NAPI_METHOD(updates_next) {
1301
877
 
1302
878
  return rocksdb::Status::OK();
1303
879
  },
1304
- [=](auto& batchResult, napi_env env, napi_value callback) -> napi_status {
1305
- napi_value argv[5];
1306
-
1307
- NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
1308
-
880
+ [=](auto& batchResult, auto env, auto& argv) {
1309
881
  if (!batchResult.writeBatchPtr) {
1310
- return CallFunction(env, callback, 1, argv);
882
+ return napi_ok;
1311
883
  }
1312
884
 
885
+ argv.resize(5);
1313
886
  NAPI_STATUS_RETURN(updates->Iterate(env, *batchResult.writeBatchPtr, &argv[1]));
1314
-
1315
887
  NAPI_STATUS_RETURN(napi_create_int64(env, batchResult.sequence, &argv[2]));
1316
888
  NAPI_STATUS_RETURN(napi_create_int64(env, batchResult.writeBatchPtr->Count(), &argv[3]));
1317
889
  NAPI_STATUS_RETURN(napi_create_int64(env, updates->start_, &argv[4]));
1318
890
 
1319
- return CallFunction(env, callback, 5, argv);
891
+ return napi_ok;
1320
892
  });
1321
893
 
1322
894
  return 0;
@@ -1328,8 +900,8 @@ NAPI_METHOD(updates_close) {
1328
900
  Updates* updates;
1329
901
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&updates)));
1330
902
 
1331
- updates->Detach(env);
1332
- updates->Close();
903
+ ROCKS_STATUS_THROWS_NAPI(updates->Close());
904
+ NAPI_STATUS_THROWS(updates->Detach(env));
1333
905
 
1334
906
  return 0;
1335
907
  }
@@ -1349,44 +921,53 @@ NAPI_METHOD(db_get_many) {
1349
921
  for (uint32_t n = 0; n < length; n++) {
1350
922
  napi_value element;
1351
923
  NAPI_STATUS_THROWS(napi_get_element(env, argv[1], n, &element));
1352
- NAPI_STATUS_THROWS(ToString(env, element, keys[n]));
924
+ NAPI_STATUS_THROWS(GetValue(env, element, keys[n]));
1353
925
  }
1354
926
  }
1355
927
 
1356
- const bool valueAsBuffer = EncodingIsBuffer(env, argv[2], "valueEncoding");
1357
- const bool fillCache = BooleanProperty(env, argv[2], "fillCache").value_or(true);
1358
- const bool ignoreRangeDeletions = BooleanProperty(env, argv[2], "ignoreRangeDeletions").value_or(false);
928
+ const auto options = argv[2];
929
+
930
+ Encoding valueEncoding = Encoding::String;
931
+ NAPI_STATUS_THROWS(GetProperty(env, options, "valueEncoding", valueEncoding));
1359
932
 
1360
- rocksdb::ColumnFamilyHandle* column;
1361
- NAPI_STATUS_THROWS(GetColumnFamily(database, env, argv[2], &column));
933
+ bool fillCache = true;
934
+ NAPI_STATUS_THROWS(GetProperty(env, options, "fillCache", fillCache));
935
+
936
+ bool ignoreRangeDeletions = false;
937
+ NAPI_STATUS_THROWS(GetProperty(env, options, "ignoreRangeDeletions", ignoreRangeDeletions));
938
+
939
+ rocksdb::ColumnFamilyHandle* column = database->db->DefaultColumnFamily();
940
+ NAPI_STATUS_THROWS(GetProperty(env, options, "column", column));
1362
941
 
1363
942
  auto callback = argv[3];
1364
943
 
1365
944
  auto snapshot = std::shared_ptr<const rocksdb::Snapshot>(
1366
- database->db_->GetSnapshot(), [database](auto ptr) { database->db_->ReleaseSnapshot(ptr); });
945
+ database->db->GetSnapshot(), [database](auto ptr) { database->db->ReleaseSnapshot(ptr); });
1367
946
 
1368
- runWorker<std::vector<rocksdb::PinnableSlice>>(
1369
- "leveldown.get.many", env, callback, database, true,
1370
- [=, keys = std::move(keys)](auto& values, Database& db) -> rocksdb::Status {
947
+ runAsync<std::vector<rocksdb::PinnableSlice>>(
948
+ "leveldown.get.many", env, callback,
949
+ [=, keys = std::move(keys), snapshot = std::move(snapshot)](auto& values) {
1371
950
  rocksdb::ReadOptions readOptions;
1372
951
  readOptions.fill_cache = fillCache;
1373
952
  readOptions.snapshot = snapshot.get();
1374
953
  readOptions.async_io = true;
1375
954
  readOptions.ignore_range_deletions = ignoreRangeDeletions;
1376
955
 
956
+ const auto size = keys.size();
957
+
1377
958
  std::vector<rocksdb::Slice> keys2;
1378
- keys2.reserve(keys.size());
959
+ keys2.reserve(size);
1379
960
  for (const auto& key : keys) {
1380
961
  keys2.emplace_back(key);
1381
962
  }
1382
963
  std::vector<rocksdb::Status> statuses;
1383
964
 
1384
- statuses.resize(keys2.size());
1385
- values.resize(keys2.size());
965
+ statuses.resize(size);
966
+ values.resize(size);
1386
967
 
1387
- db.db_->MultiGet(readOptions, column, keys2.size(), keys2.data(), values.data(), statuses.data());
968
+ database->db->MultiGet(readOptions, column, size, keys2.data(), values.data(), statuses.data());
1388
969
 
1389
- for (size_t idx = 0; idx < keys2.size(); idx++) {
970
+ for (size_t idx = 0; idx < size; idx++) {
1390
971
  if (statuses[idx].IsNotFound()) {
1391
972
  values[idx] = rocksdb::PinnableSlice(nullptr);
1392
973
  } else if (!statuses[idx].ok()) {
@@ -1396,23 +977,22 @@ NAPI_METHOD(db_get_many) {
1396
977
 
1397
978
  return rocksdb::Status::OK();
1398
979
  },
1399
- [=](auto& values, napi_env env, napi_value callback) -> napi_status {
1400
- napi_value argv[2];
1401
- NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
980
+ [=](auto& values, auto env, auto& argv) {
981
+ argv.resize(2);
1402
982
 
1403
983
  NAPI_STATUS_RETURN(napi_create_array_with_length(env, values.size(), &argv[1]));
1404
984
 
1405
985
  for (size_t idx = 0; idx < values.size(); idx++) {
1406
986
  napi_value element;
1407
987
  if (values[idx].GetSelf()) {
1408
- NAPI_STATUS_RETURN(Convert(env, &values[idx], valueAsBuffer, element));
988
+ NAPI_STATUS_RETURN(Convert(env, &values[idx], valueEncoding, element));
1409
989
  } else {
1410
990
  NAPI_STATUS_RETURN(napi_get_undefined(env, &element));
1411
991
  }
1412
992
  NAPI_STATUS_RETURN(napi_set_element(env, argv[1], static_cast<uint32_t>(idx), element));
1413
993
  }
1414
994
 
1415
- return CallFunction(env, callback, 2, argv);
995
+ return napi_ok;
1416
996
  });
1417
997
 
1418
998
  return 0;
@@ -1424,16 +1004,28 @@ NAPI_METHOD(db_clear) {
1424
1004
  Database* database;
1425
1005
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
1426
1006
 
1427
- const auto reverse = BooleanProperty(env, argv[1], "reverse").value_or(false);
1428
- const auto limit = Int32Property(env, argv[1], "limit").value_or(-1);
1007
+ const auto options = argv[1];
1008
+
1009
+ bool reverse = false;
1010
+ NAPI_STATUS_THROWS(GetProperty(env, options, "reverse", reverse));
1011
+
1012
+ int32_t limit = -1;
1013
+ NAPI_STATUS_THROWS(GetProperty(env, options, "limit", limit));
1014
+
1015
+ rocksdb::ColumnFamilyHandle* column = database->db->DefaultColumnFamily();
1016
+ NAPI_STATUS_THROWS(GetProperty(env, options, "column", column));
1017
+
1018
+ std::optional<std::string> lt;
1019
+ NAPI_STATUS_THROWS(GetProperty(env, options, "lt", lt));
1020
+
1021
+ std::optional<std::string> lte;
1022
+ NAPI_STATUS_THROWS(GetProperty(env, options, "lte", lte));
1429
1023
 
1430
- rocksdb::ColumnFamilyHandle* column;
1431
- NAPI_STATUS_THROWS(GetColumnFamily(database, env, argv[1], &column));
1024
+ std::optional<std::string> gt;
1025
+ NAPI_STATUS_THROWS(GetProperty(env, options, "gt", gt));
1432
1026
 
1433
- auto lt = StringProperty(env, argv[1], "lt");
1434
- auto lte = StringProperty(env, argv[1], "lte");
1435
- auto gt = StringProperty(env, argv[1], "gt");
1436
- auto gte = StringProperty(env, argv[1], "gte");
1027
+ std::optional<std::string> gte;
1028
+ NAPI_STATUS_THROWS(GetProperty(env, options, "gte", gte));
1437
1029
 
1438
1030
  if (limit == -1) {
1439
1031
  rocksdb::PinnableSlice begin;
@@ -1458,7 +1050,7 @@ NAPI_METHOD(db_clear) {
1458
1050
 
1459
1051
  if (begin.compare(end) < 0) {
1460
1052
  rocksdb::WriteOptions writeOptions;
1461
- ROCKS_STATUS_THROWS(database->db_->DeleteRange(writeOptions, column, begin, end));
1053
+ ROCKS_STATUS_THROWS_NAPI(database->db->DeleteRange(writeOptions, column, begin, end));
1462
1054
  }
1463
1055
 
1464
1056
  return 0;
@@ -1466,8 +1058,8 @@ NAPI_METHOD(db_clear) {
1466
1058
  // TODO (fix): Error handling.
1467
1059
  // TODO (fix): This should be async...
1468
1060
 
1469
- std::shared_ptr<const rocksdb::Snapshot> snapshot(database->db_->GetSnapshot(),
1470
- [=](const auto ptr) { database->db_->ReleaseSnapshot(ptr); });
1061
+ std::shared_ptr<const rocksdb::Snapshot> snapshot(database->db->GetSnapshot(),
1062
+ [=](const auto ptr) { database->db->ReleaseSnapshot(ptr); });
1471
1063
  BaseIterator it(database, column, reverse, lt, lte, gt, gte, limit, false, snapshot);
1472
1064
 
1473
1065
  it.SeekToRange();
@@ -1491,7 +1083,7 @@ NAPI_METHOD(db_clear) {
1491
1083
  break;
1492
1084
  }
1493
1085
 
1494
- status = database->db_->Write(writeOptions, &batch);
1086
+ status = database->db->Write(writeOptions, &batch);
1495
1087
  if (!status.ok()) {
1496
1088
  break;
1497
1089
  }
@@ -1502,7 +1094,7 @@ NAPI_METHOD(db_clear) {
1502
1094
  it.Close();
1503
1095
 
1504
1096
  if (!status.ok()) {
1505
- ROCKS_STATUS_THROWS(status);
1097
+ ROCKS_STATUS_THROWS_NAPI(status);
1506
1098
  }
1507
1099
 
1508
1100
  return 0;
@@ -1516,10 +1108,10 @@ NAPI_METHOD(db_get_property) {
1516
1108
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
1517
1109
 
1518
1110
  rocksdb::PinnableSlice property;
1519
- NAPI_STATUS_THROWS(ToString(env, argv[1], property));
1111
+ NAPI_STATUS_THROWS(GetValue(env, argv[1], property));
1520
1112
 
1521
1113
  std::string value;
1522
- database->db_->GetProperty(property, &value);
1114
+ database->db->GetProperty(property, &value);
1523
1115
 
1524
1116
  napi_value result;
1525
1117
  NAPI_STATUS_THROWS(napi_create_string_utf8(env, value.data(), value.size(), &result));
@@ -1533,7 +1125,7 @@ NAPI_METHOD(db_get_latest_sequence) {
1533
1125
  Database* database;
1534
1126
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
1535
1127
 
1536
- const auto seq = database->db_->GetLatestSequenceNumber();
1128
+ const auto seq = database->db->GetLatestSequenceNumber();
1537
1129
 
1538
1130
  napi_value result;
1539
1131
  NAPI_STATUS_THROWS(napi_create_int64(env, seq, &result));
@@ -1548,35 +1140,59 @@ NAPI_METHOD(iterator_init) {
1548
1140
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
1549
1141
 
1550
1142
  const auto options = argv[1];
1551
- const auto reverse = BooleanProperty(env, options, "reverse").value_or(false);
1552
- const auto keys = BooleanProperty(env, options, "keys").value_or(true);
1553
- const auto values = BooleanProperty(env, options, "values").value_or(true);
1554
- const auto fillCache = BooleanProperty(env, options, "fillCache").value_or(false);
1555
- const bool keyAsBuffer = EncodingIsBuffer(env, options, "keyEncoding");
1556
- const bool valueAsBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1557
- const auto limit = Int32Property(env, options, "limit").value_or(-1);
1558
- const auto highWaterMarkBytes = Uint32Property(env, options, "highWaterMarkBytes").value_or(64 * 1024);
1559
1143
 
1560
- const auto lt = StringProperty(env, options, "lt");
1561
- const auto lte = StringProperty(env, options, "lte");
1562
- const auto gt = StringProperty(env, options, "gt");
1563
- const auto gte = StringProperty(env, options, "gte");
1144
+ bool reverse = false;
1145
+ NAPI_STATUS_THROWS(GetProperty(env, options, "reverse", reverse));
1564
1146
 
1565
- rocksdb::ColumnFamilyHandle* column;
1566
- NAPI_STATUS_THROWS(GetColumnFamily(database, env, options, &column));
1147
+ bool keys = true;
1148
+ NAPI_STATUS_THROWS(GetProperty(env, options, "keys", keys));
1567
1149
 
1568
- std::shared_ptr<const rocksdb::Snapshot> snapshot(database->db_->GetSnapshot(),
1569
- [=](const auto ptr) { database->db_->ReleaseSnapshot(ptr); });
1150
+ bool values = true;
1151
+ NAPI_STATUS_THROWS(GetProperty(env, options, "values", values));
1570
1152
 
1571
- auto iterator = std::make_unique<Iterator>(database, column, reverse, keys, values, limit, lt, lte, gt, gte,
1572
- fillCache, keyAsBuffer, valueAsBuffer, highWaterMarkBytes, snapshot);
1153
+ bool fillCache = false;
1154
+ NAPI_STATUS_THROWS(GetProperty(env, options, "fillCache", fillCache));
1155
+
1156
+ Encoding keyEncoding = Encoding::String;
1157
+ NAPI_STATUS_THROWS(GetProperty(env, options, "keyEncoding", keyEncoding));
1158
+
1159
+ Encoding valueEncoding = Encoding::String;
1160
+ NAPI_STATUS_THROWS(GetProperty(env, options, "valueEncoding", valueEncoding));
1161
+
1162
+ int32_t limit = -1;
1163
+ NAPI_STATUS_THROWS(GetProperty(env, options, "limit", limit));
1164
+
1165
+ int32_t highWaterMarkBytes = 64 * 1024;
1166
+ NAPI_STATUS_THROWS(GetProperty(env, options, "highWaterMarkBytes", highWaterMarkBytes));
1167
+
1168
+ std::optional<std::string> lt;
1169
+ NAPI_STATUS_THROWS(GetProperty(env, options, "lt", lt));
1170
+
1171
+ std::optional<std::string> lte;
1172
+ NAPI_STATUS_THROWS(GetProperty(env, options, "lte", lte));
1173
+
1174
+ std::optional<std::string> gt;
1175
+ NAPI_STATUS_THROWS(GetProperty(env, options, "gt", gt));
1176
+
1177
+ std::optional<std::string> gte;
1178
+ NAPI_STATUS_THROWS(GetProperty(env, options, "gte", gte));
1179
+
1180
+ rocksdb::ColumnFamilyHandle* column = database->db->DefaultColumnFamily();
1181
+ NAPI_STATUS_THROWS(GetProperty(env, options, "column", column));
1182
+
1183
+ std::shared_ptr<const rocksdb::Snapshot> snapshot(database->db->GetSnapshot(),
1184
+ [=](const auto ptr) { database->db->ReleaseSnapshot(ptr); });
1185
+
1186
+ auto iterator =
1187
+ std::unique_ptr<Iterator>(new Iterator(database, column, reverse, keys, values, limit, lt, lte, gt, gte,
1188
+ fillCache, keyEncoding, valueEncoding, highWaterMarkBytes, snapshot));
1573
1189
 
1574
1190
  napi_value result;
1575
1191
  NAPI_STATUS_THROWS(napi_create_external(env, iterator.get(), Finalize<Iterator>, iterator.get(), &result));
1576
1192
 
1577
1193
  // Prevent GC of JS object before the iterator is closed (explicitly or on
1578
1194
  // db close) and keep track of non-closed iterators to end them on db close.
1579
- iterator.release()->Attach(env, result);
1195
+ NAPI_STATUS_THROWS(iterator.release()->Attach(env, result));
1580
1196
 
1581
1197
  return result;
1582
1198
  }
@@ -1588,7 +1204,7 @@ NAPI_METHOD(iterator_seek) {
1588
1204
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&iterator)));
1589
1205
 
1590
1206
  rocksdb::PinnableSlice target;
1591
- NAPI_STATUS_THROWS(ToString(env, argv[1], target));
1207
+ NAPI_STATUS_THROWS(GetValue(env, argv[1], target));
1592
1208
 
1593
1209
  iterator->first_ = true;
1594
1210
  iterator->Seek(target); // TODO: Does seek causing blocking IO?
@@ -1602,8 +1218,8 @@ NAPI_METHOD(iterator_close) {
1602
1218
  Iterator* iterator;
1603
1219
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&iterator)));
1604
1220
 
1605
- iterator->Detach(env);
1606
- iterator->Close();
1221
+ ROCKS_STATUS_THROWS_NAPI(iterator->Close());
1222
+ NAPI_STATUS_THROWS(iterator->Detach(env));
1607
1223
 
1608
1224
  return 0;
1609
1225
  }
@@ -1638,9 +1254,9 @@ NAPI_METHOD(iterator_nextv) {
1638
1254
  bool finished = false;
1639
1255
  };
1640
1256
 
1641
- runWorker<State>(
1642
- std::string("leveldown.iterator.next"), env, callback, iterator->database_, true,
1643
- [=](auto& state, Database& db) -> rocksdb::Status {
1257
+ runAsync<State>(
1258
+ std::string("leveldown.iterator.next"), env, callback,
1259
+ [=](auto& state) {
1644
1260
  if (!iterator->DidSeek()) {
1645
1261
  iterator->SeekToRange();
1646
1262
  }
@@ -1685,9 +1301,8 @@ NAPI_METHOD(iterator_nextv) {
1685
1301
 
1686
1302
  return iterator->Status();
1687
1303
  },
1688
- [=](auto& state, napi_env env, napi_value callback) -> napi_status {
1689
- napi_value argv[3];
1690
- NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
1304
+ [=](auto& state, auto env, auto& argv) {
1305
+ argv.resize(3);
1691
1306
 
1692
1307
  NAPI_STATUS_RETURN(napi_create_array_with_length(env, state.cache.size(), &argv[1]));
1693
1308
 
@@ -1695,8 +1310,8 @@ NAPI_METHOD(iterator_nextv) {
1695
1310
  napi_value key;
1696
1311
  napi_value val;
1697
1312
 
1698
- NAPI_STATUS_RETURN(Convert(env, state.cache[n + 0], iterator->keyAsBuffer_, key));
1699
- NAPI_STATUS_RETURN(Convert(env, state.cache[n + 1], iterator->valueAsBuffer_, val));
1313
+ NAPI_STATUS_RETURN(Convert(env, state.cache[n + 0], iterator->keyEncoding_, key));
1314
+ NAPI_STATUS_RETURN(Convert(env, state.cache[n + 1], iterator->valueEncoding_, val));
1700
1315
 
1701
1316
  NAPI_STATUS_RETURN(napi_set_element(env, argv[1], static_cast<int>(n + 0), key));
1702
1317
  NAPI_STATUS_RETURN(napi_set_element(env, argv[1], static_cast<int>(n + 1), val));
@@ -1704,7 +1319,7 @@ NAPI_METHOD(iterator_nextv) {
1704
1319
 
1705
1320
  NAPI_STATUS_RETURN(napi_get_boolean(env, state.finished, &argv[2]));
1706
1321
 
1707
- return CallFunction(env, callback, 3, argv);
1322
+ return napi_ok;
1708
1323
  });
1709
1324
 
1710
1325
  return 0;
@@ -1718,63 +1333,49 @@ NAPI_METHOD(batch_do) {
1718
1333
 
1719
1334
  rocksdb::WriteBatch batch;
1720
1335
 
1336
+ auto elements = argv[1];
1337
+
1721
1338
  uint32_t length;
1722
- NAPI_STATUS_THROWS(napi_get_array_length(env, argv[1], &length));
1339
+ NAPI_STATUS_THROWS(napi_get_array_length(env, elements, &length));
1340
+
1341
+ rocksdb::PinnableSlice type;
1342
+ rocksdb::PinnableSlice key;
1343
+ rocksdb::PinnableSlice value;
1723
1344
 
1724
1345
  for (uint32_t i = 0; i < length; i++) {
1725
- rocksdb::PinnableSlice type;
1726
- rocksdb::PinnableSlice key;
1727
- rocksdb::PinnableSlice value;
1346
+ type.Reset();
1347
+ key.Reset();
1348
+ value.Reset();
1728
1349
 
1729
1350
  napi_value element;
1730
- NAPI_STATUS_THROWS(napi_get_element(env, argv[1], i, &element));
1351
+ NAPI_STATUS_THROWS(napi_get_element(env, elements, i, &element));
1731
1352
 
1732
- napi_value typeProperty;
1733
- NAPI_STATUS_THROWS(napi_get_named_property(env, element, "type", &typeProperty));
1734
- NAPI_STATUS_THROWS(ToString(env, typeProperty, type));
1353
+ NAPI_STATUS_THROWS(GetProperty(env, element, "type", type, true));
1735
1354
 
1736
- rocksdb::ColumnFamilyHandle* column;
1737
- NAPI_STATUS_THROWS(GetColumnFamily(database, env, element, &column));
1355
+ rocksdb::ColumnFamilyHandle* column = database->db->DefaultColumnFamily();
1356
+ NAPI_STATUS_THROWS(GetProperty(env, element, "column", column));
1738
1357
 
1739
1358
  if (type == "del") {
1740
- napi_value keyProperty;
1741
- NAPI_STATUS_THROWS(napi_get_named_property(env, element, "key", &keyProperty));
1742
- NAPI_STATUS_THROWS(ToString(env, keyProperty, key));
1743
-
1744
- ROCKS_STATUS_THROWS(batch.Delete(column, key));
1359
+ NAPI_STATUS_THROWS(GetProperty(env, element, "key", key, true));
1360
+ ROCKS_STATUS_THROWS_NAPI(batch.Delete(column, key));
1745
1361
  } else if (type == "put") {
1746
- napi_value keyProperty;
1747
- NAPI_STATUS_THROWS(napi_get_named_property(env, element, "key", &keyProperty));
1748
- NAPI_STATUS_THROWS(ToString(env, keyProperty, key));
1749
-
1750
- napi_value valueProperty;
1751
- NAPI_STATUS_THROWS(napi_get_named_property(env, element, "value", &valueProperty));
1752
- NAPI_STATUS_THROWS(ToString(env, valueProperty, value));
1753
-
1754
- ROCKS_STATUS_THROWS(batch.Put(column, key, value));
1362
+ NAPI_STATUS_THROWS(GetProperty(env, element, "key", key, true));
1363
+ NAPI_STATUS_THROWS(GetProperty(env, element, "value", value, true));
1364
+ ROCKS_STATUS_THROWS_NAPI(batch.Put(column, key, value));
1755
1365
  } else if (type == "data") {
1756
- napi_value valueProperty;
1757
- NAPI_STATUS_THROWS(napi_get_named_property(env, element, "value", &valueProperty));
1758
- NAPI_STATUS_THROWS(ToString(env, valueProperty, value));
1759
-
1760
- ROCKS_STATUS_THROWS(batch.PutLogData(value));
1366
+ NAPI_STATUS_THROWS(GetProperty(env, element, "value", value, true));
1367
+ ROCKS_STATUS_THROWS_NAPI(batch.PutLogData(value));
1761
1368
  } else if (type == "merge") {
1762
- napi_value keyProperty;
1763
- NAPI_STATUS_THROWS(napi_get_named_property(env, element, "key", &keyProperty));
1764
- NAPI_STATUS_THROWS(ToString(env, keyProperty, key));
1765
-
1766
- napi_value valueProperty;
1767
- NAPI_STATUS_THROWS(napi_get_named_property(env, element, "value", &valueProperty));
1768
- NAPI_STATUS_THROWS(ToString(env, valueProperty, value));
1769
-
1770
- ROCKS_STATUS_THROWS(batch.Merge(column, key, value));
1369
+ NAPI_STATUS_THROWS(GetProperty(env, element, "key", key, true));
1370
+ NAPI_STATUS_THROWS(GetProperty(env, element, "value", value, true));
1371
+ ROCKS_STATUS_THROWS_NAPI(batch.Merge(column, key, value));
1771
1372
  } else {
1772
- ROCKS_STATUS_THROWS(rocksdb::Status::InvalidArgument());
1373
+ NAPI_STATUS_THROWS(napi_invalid_arg);
1773
1374
  }
1774
1375
  }
1775
1376
 
1776
1377
  rocksdb::WriteOptions writeOptions;
1777
- ROCKS_STATUS_THROWS(database->db_->Write(writeOptions, &batch));
1378
+ ROCKS_STATUS_THROWS_NAPI(database->db->Write(writeOptions, &batch));
1778
1379
 
1779
1380
  return 0;
1780
1381
  }
@@ -1792,21 +1393,23 @@ NAPI_METHOD(batch_put) {
1792
1393
  NAPI_ARGV(4);
1793
1394
 
1794
1395
  rocksdb::WriteBatch* batch;
1795
- NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)(&batch)));
1396
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&batch)));
1796
1397
 
1797
1398
  rocksdb::PinnableSlice key;
1798
- NAPI_STATUS_THROWS(ToString(env, argv[1], key));
1399
+ NAPI_STATUS_THROWS(GetValue(env, argv[1], key));
1799
1400
 
1800
1401
  rocksdb::PinnableSlice val;
1801
- NAPI_STATUS_THROWS(ToString(env, argv[2], val));
1402
+ NAPI_STATUS_THROWS(GetValue(env, argv[2], val));
1802
1403
 
1803
- rocksdb::ColumnFamilyHandle* column;
1804
- NAPI_STATUS_THROWS(GetColumnFamily(nullptr, env, argv[3], &column));
1404
+ const auto options = argv[3];
1405
+
1406
+ rocksdb::ColumnFamilyHandle* column = nullptr;
1407
+ NAPI_STATUS_THROWS(GetProperty(env, options, "column", column));
1805
1408
 
1806
1409
  if (column) {
1807
- ROCKS_STATUS_THROWS(batch->Put(column, key, val));
1410
+ ROCKS_STATUS_THROWS_NAPI(batch->Put(column, key, val));
1808
1411
  } else {
1809
- ROCKS_STATUS_THROWS(batch->Put(key, val));
1412
+ ROCKS_STATUS_THROWS_NAPI(batch->Put(key, val));
1810
1413
  }
1811
1414
 
1812
1415
  return 0;
@@ -1819,15 +1422,17 @@ NAPI_METHOD(batch_del) {
1819
1422
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&batch)));
1820
1423
 
1821
1424
  rocksdb::PinnableSlice key;
1822
- NAPI_STATUS_THROWS(ToString(env, argv[1], key));
1425
+ NAPI_STATUS_THROWS(GetValue(env, argv[1], key));
1426
+
1427
+ const auto options = argv[2];
1823
1428
 
1824
- rocksdb::ColumnFamilyHandle* column;
1825
- NAPI_STATUS_THROWS(GetColumnFamily(nullptr, env, argv[2], &column));
1429
+ rocksdb::ColumnFamilyHandle* column = nullptr;
1430
+ NAPI_STATUS_THROWS(GetProperty(env, options, "column", column));
1826
1431
 
1827
1432
  if (column) {
1828
- ROCKS_STATUS_THROWS(batch->Delete(column, key));
1433
+ ROCKS_STATUS_THROWS_NAPI(batch->Delete(column, key));
1829
1434
  } else {
1830
- ROCKS_STATUS_THROWS(batch->Delete(key));
1435
+ ROCKS_STATUS_THROWS_NAPI(batch->Delete(key));
1831
1436
  }
1832
1437
 
1833
1438
  return 0;
@@ -1840,18 +1445,20 @@ NAPI_METHOD(batch_merge) {
1840
1445
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)(&batch)));
1841
1446
 
1842
1447
  rocksdb::PinnableSlice key;
1843
- NAPI_STATUS_THROWS(ToString(env, argv[1], key));
1448
+ NAPI_STATUS_THROWS(GetValue(env, argv[1], key));
1844
1449
 
1845
1450
  rocksdb::PinnableSlice val;
1846
- NAPI_STATUS_THROWS(ToString(env, argv[2], val));
1451
+ NAPI_STATUS_THROWS(GetValue(env, argv[2], val));
1452
+
1453
+ const auto options = argv[3];
1847
1454
 
1848
- rocksdb::ColumnFamilyHandle* column;
1849
- NAPI_STATUS_THROWS(GetColumnFamily(nullptr, env, argv[3], &column));
1455
+ rocksdb::ColumnFamilyHandle* column = nullptr;
1456
+ NAPI_STATUS_THROWS(GetProperty(env, options, "column", column));
1850
1457
 
1851
1458
  if (column) {
1852
- ROCKS_STATUS_THROWS(batch->Merge(column, key, val));
1459
+ ROCKS_STATUS_THROWS_NAPI(batch->Merge(column, key, val));
1853
1460
  } else {
1854
- ROCKS_STATUS_THROWS(batch->Merge(key, val));
1461
+ ROCKS_STATUS_THROWS_NAPI(batch->Merge(key, val));
1855
1462
  }
1856
1463
 
1857
1464
  return 0;
@@ -1878,7 +1485,7 @@ NAPI_METHOD(batch_write) {
1878
1485
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[1], reinterpret_cast<void**>(&batch)));
1879
1486
 
1880
1487
  rocksdb::WriteOptions writeOptions;
1881
- ROCKS_STATUS_THROWS(database->db_->Write(writeOptions, batch));
1488
+ ROCKS_STATUS_THROWS_NAPI(database->db->Write(writeOptions, batch));
1882
1489
 
1883
1490
  return 0;
1884
1491
  }
@@ -1890,9 +1497,9 @@ NAPI_METHOD(batch_put_log_data) {
1890
1497
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&batch)));
1891
1498
 
1892
1499
  rocksdb::PinnableSlice logData;
1893
- NAPI_STATUS_THROWS(ToString(env, argv[1], logData));
1500
+ NAPI_STATUS_THROWS(GetValue(env, argv[1], logData));
1894
1501
 
1895
- ROCKS_STATUS_THROWS(batch->PutLogData(logData));
1502
+ ROCKS_STATUS_THROWS_NAPI(batch->PutLogData(logData));
1896
1503
 
1897
1504
  return 0;
1898
1505
  }
@@ -1918,111 +1525,34 @@ NAPI_METHOD(batch_iterate) {
1918
1525
  rocksdb::WriteBatch* batch;
1919
1526
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[1], reinterpret_cast<void**>(&batch)));
1920
1527
 
1921
- napi_value keysProperty;
1922
- bool keys;
1923
- NAPI_STATUS_THROWS(napi_get_named_property(env, argv[2], "keys", &keysProperty));
1924
- NAPI_STATUS_THROWS(napi_get_value_bool(env, keysProperty, &keys));
1528
+ const auto options = argv[2];
1529
+
1530
+ bool keys = true;
1531
+ NAPI_STATUS_THROWS(GetProperty(env, options, "keys", keys));
1532
+
1533
+ bool values = true;
1534
+ NAPI_STATUS_THROWS(GetProperty(env, options, "values", values));
1925
1535
 
1926
- napi_value valuesProperty;
1927
- bool values;
1928
- NAPI_STATUS_THROWS(napi_get_named_property(env, argv[2], "values", &valuesProperty));
1929
- NAPI_STATUS_THROWS(napi_get_value_bool(env, valuesProperty, &values));
1536
+ bool data = true;
1537
+ NAPI_STATUS_THROWS(GetProperty(env, options, "data", data));
1930
1538
 
1931
- napi_value dataProperty;
1932
- bool data;
1933
- NAPI_STATUS_THROWS(napi_get_named_property(env, argv[2], "data", &dataProperty));
1934
- NAPI_STATUS_THROWS(napi_get_value_bool(env, dataProperty, &data));
1539
+ Encoding keyEncoding = Encoding::String;
1540
+ NAPI_STATUS_THROWS(GetProperty(env, options, "keyEncoding", keyEncoding));
1935
1541
 
1936
- const bool keyAsBuffer = EncodingIsBuffer(env, argv[1], "keyEncoding");
1937
- const bool valueAsBuffer = EncodingIsBuffer(env, argv[1], "valueEncoding");
1542
+ Encoding valueEncoding = Encoding::String;
1543
+ NAPI_STATUS_THROWS(GetProperty(env, options, "valueEncoding", valueEncoding));
1938
1544
 
1939
1545
  rocksdb::ColumnFamilyHandle* column = nullptr;
1940
- NAPI_STATUS_THROWS(GetColumnFamily(nullptr, env, argv[2], &column));
1546
+ NAPI_STATUS_THROWS(GetProperty(env, options, "column", column));
1941
1547
 
1942
- napi_value result;
1943
- BatchIterator iterator(nullptr, keys, values, data, column, keyAsBuffer, valueAsBuffer);
1548
+ BatchIterator iterator(nullptr, keys, values, data, column, keyEncoding, valueEncoding);
1944
1549
 
1550
+ napi_value result;
1945
1551
  NAPI_STATUS_THROWS(iterator.Iterate(env, *batch, &result));
1946
1552
 
1947
1553
  return result;
1948
1554
  }
1949
1555
 
1950
- NAPI_METHOD(db_flush_wal) {
1951
- NAPI_ARGV(2);
1952
-
1953
- Database* database;
1954
- NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
1955
-
1956
- const auto flush = BooleanProperty(env, argv[1], "flush").value_or(false);
1957
-
1958
- ROCKS_STATUS_THROWS(database->db_->FlushWAL(flush));
1959
-
1960
- return 0;
1961
- }
1962
-
1963
- template <typename T>
1964
- napi_status FromLogFile(napi_env env, const T& file, napi_value* obj) {
1965
- NAPI_STATUS_RETURN(napi_create_object(env, obj));
1966
-
1967
- napi_value pathName;
1968
- NAPI_STATUS_RETURN(napi_create_string_utf8(env, file->PathName().c_str(), NAPI_AUTO_LENGTH, &pathName));
1969
- NAPI_STATUS_RETURN(napi_set_named_property(env, *obj, "pathName", pathName));
1970
-
1971
- napi_value logNumber;
1972
- NAPI_STATUS_RETURN(napi_create_int64(env, file->LogNumber(), &logNumber));
1973
- NAPI_STATUS_RETURN(napi_set_named_property(env, *obj, "logNumber", logNumber));
1974
-
1975
- napi_value type;
1976
- NAPI_STATUS_RETURN(napi_create_int64(env, file->Type(), &type));
1977
- NAPI_STATUS_RETURN(napi_set_named_property(env, *obj, "type", type));
1978
-
1979
- napi_value startSequence;
1980
- NAPI_STATUS_RETURN(napi_create_int64(env, file->StartSequence(), &startSequence));
1981
- NAPI_STATUS_RETURN(napi_set_named_property(env, *obj, "startSequence", startSequence));
1982
-
1983
- napi_value sizeFileBytes;
1984
- NAPI_STATUS_RETURN(napi_create_int64(env, file->SizeFileBytes(), &sizeFileBytes));
1985
- NAPI_STATUS_RETURN(napi_set_named_property(env, *obj, "sizeFileBytes", sizeFileBytes));
1986
-
1987
- return napi_ok;
1988
- }
1989
-
1990
- NAPI_METHOD(db_get_sorted_wal_files) {
1991
- NAPI_ARGV(1);
1992
-
1993
- Database* database;
1994
- NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
1995
-
1996
- rocksdb::VectorLogPtr files;
1997
- ROCKS_STATUS_THROWS(database->db_->GetSortedWalFiles(files));
1998
-
1999
- napi_value ret;
2000
- NAPI_STATUS_THROWS(napi_create_array_with_length(env, files.size(), &ret));
2001
-
2002
- for (size_t n = 0; n < files.size(); ++n) {
2003
- napi_value obj;
2004
- NAPI_STATUS_THROWS(FromLogFile(env, files[n], &obj));
2005
- NAPI_STATUS_THROWS(napi_set_element(env, ret, n, obj));
2006
- }
2007
-
2008
- return ret;
2009
- }
2010
-
2011
- NAPI_METHOD(db_get_current_wal_file) {
2012
- NAPI_ARGV(1);
2013
-
2014
- Database* database;
2015
- NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
2016
-
2017
- std::unique_ptr<rocksdb::LogFile> file;
2018
- ROCKS_STATUS_THROWS(database->db_->GetCurrentWalFile(&file));
2019
-
2020
- napi_value ret;
2021
- NAPI_STATUS_THROWS(FromLogFile(env, file, &ret));
2022
-
2023
- return ret;
2024
- }
2025
-
2026
1556
  NAPI_INIT() {
2027
1557
  NAPI_EXPORT_FUNCTION(db_init);
2028
1558
  NAPI_EXPORT_FUNCTION(db_open);
@@ -2042,10 +1572,6 @@ NAPI_INIT() {
2042
1572
  NAPI_EXPORT_FUNCTION(updates_close);
2043
1573
  NAPI_EXPORT_FUNCTION(updates_next);
2044
1574
 
2045
- NAPI_EXPORT_FUNCTION(db_flush_wal);
2046
- NAPI_EXPORT_FUNCTION(db_get_sorted_wal_files);
2047
- NAPI_EXPORT_FUNCTION(db_get_current_wal_file);
2048
-
2049
1575
  NAPI_EXPORT_FUNCTION(batch_do);
2050
1576
  NAPI_EXPORT_FUNCTION(batch_init);
2051
1577
  NAPI_EXPORT_FUNCTION(batch_put);