@nxtedition/rocksdb 6.0.3 → 7.0.0-alpha.1
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 +417 -35
- package/index.js +187 -3
- package/iterator.js +1 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/snapshot.js +23 -0
- package/prebuilds/darwin-x64/node.napi.node +0 -0
- package/prebuilds/linux-x64/node.napi.node +0 -0
package/binding.cc
CHANGED
|
@@ -30,6 +30,8 @@ class NullLogger : public rocksdb::Logger {
|
|
|
30
30
|
|
|
31
31
|
struct Database;
|
|
32
32
|
struct Iterator;
|
|
33
|
+
struct Updates;
|
|
34
|
+
struct Snapshot;
|
|
33
35
|
|
|
34
36
|
#define NAPI_STATUS_RETURN(call) \
|
|
35
37
|
{ \
|
|
@@ -47,6 +49,10 @@ struct Iterator;
|
|
|
47
49
|
Iterator* iterator = nullptr; \
|
|
48
50
|
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&iterator));
|
|
49
51
|
|
|
52
|
+
#define NAPI_UPDATES_CONTEXT() \
|
|
53
|
+
Updates* updates = nullptr; \
|
|
54
|
+
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&updates));
|
|
55
|
+
|
|
50
56
|
#define NAPI_BATCH_CONTEXT() \
|
|
51
57
|
rocksdb::WriteBatch* batch = nullptr; \
|
|
52
58
|
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&batch));
|
|
@@ -133,6 +139,18 @@ static std::optional<int> Int32Property(napi_env env, napi_value obj, const std:
|
|
|
133
139
|
return {};
|
|
134
140
|
}
|
|
135
141
|
|
|
142
|
+
static std::optional<int64_t> Int64Property(napi_env env, napi_value obj, const std::string_view& key) {
|
|
143
|
+
if (HasProperty(env, obj, key.data())) {
|
|
144
|
+
const auto value = GetProperty(env, obj, key.data());
|
|
145
|
+
int64_t result;
|
|
146
|
+
bool lossless;
|
|
147
|
+
napi_get_value_bigint_int64(env, value, &result, &lossless);
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {};
|
|
152
|
+
}
|
|
153
|
+
|
|
136
154
|
static std::string ToString(napi_env env, napi_value from, const std::string& defaultValue = "") {
|
|
137
155
|
if (IsString(env, from)) {
|
|
138
156
|
size_t length = 0;
|
|
@@ -210,6 +228,18 @@ napi_status Convert(napi_env env, rocksdb::PinnableSlice&& s, bool asBuffer, nap
|
|
|
210
228
|
}
|
|
211
229
|
}
|
|
212
230
|
|
|
231
|
+
napi_status Convert(napi_env env, std::optional<std::string>&& s, bool asBuffer, napi_value& result) {
|
|
232
|
+
if (!s) {
|
|
233
|
+
return napi_get_null(env, &result);
|
|
234
|
+
} else if (asBuffer) {
|
|
235
|
+
auto ptr = new std::string(std::move(*s));
|
|
236
|
+
return napi_create_external_buffer(env, ptr->size(), const_cast<char*>(ptr->data()), Finalize<std::string>, ptr,
|
|
237
|
+
&result);
|
|
238
|
+
} else {
|
|
239
|
+
return napi_create_string_utf8(env, s->data(), s->size(), &result);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
213
243
|
struct NapiSlice : public rocksdb::Slice {
|
|
214
244
|
std::unique_ptr<char[]> heap_;
|
|
215
245
|
std::array<char, 128> stack_;
|
|
@@ -318,6 +348,26 @@ struct Database {
|
|
|
318
348
|
DecrementPriorityWork(env);
|
|
319
349
|
}
|
|
320
350
|
|
|
351
|
+
void AttachSnapshot(napi_env env, Snapshot* snapshot) {
|
|
352
|
+
snapshots_.insert(snapshot);
|
|
353
|
+
IncrementPriorityWork(env);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
void DetachSnapshot(napi_env env, Snapshot* snapshot) {
|
|
357
|
+
snapshots_.erase(snapshot);
|
|
358
|
+
DecrementPriorityWork(env);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
void AttachUpdates(napi_env env, Updates* updates) {
|
|
362
|
+
updates_.insert(updates);
|
|
363
|
+
IncrementPriorityWork(env);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
void DetachUpdates(napi_env env, Updates* updates) {
|
|
367
|
+
updates_.erase(updates);
|
|
368
|
+
DecrementPriorityWork(env);
|
|
369
|
+
}
|
|
370
|
+
|
|
321
371
|
void IncrementPriorityWork(napi_env env) { napi_reference_ref(env, priorityRef_, &priorityWork_); }
|
|
322
372
|
|
|
323
373
|
void DecrementPriorityWork(napi_env env) {
|
|
@@ -334,12 +384,42 @@ struct Database {
|
|
|
334
384
|
std::unique_ptr<rocksdb::DB> db_;
|
|
335
385
|
Worker* pendingCloseWorker_;
|
|
336
386
|
std::set<Iterator*> iterators_;
|
|
387
|
+
std::set<Snapshot*> snapshots_;
|
|
388
|
+
std::set<Updates*> updates_;
|
|
337
389
|
napi_ref priorityRef_;
|
|
338
390
|
|
|
339
391
|
private:
|
|
340
392
|
uint32_t priorityWork_ = 0;
|
|
341
393
|
};
|
|
342
394
|
|
|
395
|
+
struct Snapshot {
|
|
396
|
+
Snapshot(Database* database)
|
|
397
|
+
: snapshot_(database->db_->GetSnapshot(), [=](auto ptr) { database->db_->ReleaseSnapshot(ptr); }),
|
|
398
|
+
database_(database) {}
|
|
399
|
+
|
|
400
|
+
void Close() { snapshot_.reset(); }
|
|
401
|
+
|
|
402
|
+
void Attach(napi_env env, napi_value context) {
|
|
403
|
+
napi_create_reference(env, context, 1, &ref_);
|
|
404
|
+
database_->AttachSnapshot(env, this);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
void Detach(napi_env env) {
|
|
408
|
+
database_->DetachSnapshot(env, this);
|
|
409
|
+
if (ref_) {
|
|
410
|
+
napi_delete_reference(env, ref_);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
int64_t GetSequenceNumber() const { return snapshot_->GetSequenceNumber(); }
|
|
415
|
+
|
|
416
|
+
std::shared_ptr<const rocksdb::Snapshot> snapshot_;
|
|
417
|
+
|
|
418
|
+
private:
|
|
419
|
+
Database* database_;
|
|
420
|
+
napi_ref ref_ = nullptr;
|
|
421
|
+
};
|
|
422
|
+
|
|
343
423
|
struct BaseIterator {
|
|
344
424
|
BaseIterator(Database* database,
|
|
345
425
|
const bool reverse,
|
|
@@ -348,13 +428,9 @@ struct BaseIterator {
|
|
|
348
428
|
const std::optional<std::string>& gt,
|
|
349
429
|
const std::optional<std::string>& gte,
|
|
350
430
|
const int limit,
|
|
351
|
-
const bool fillCache
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
[this](const rocksdb::Snapshot* ptr) { database_->db_->ReleaseSnapshot(ptr); }),
|
|
355
|
-
reverse_(reverse),
|
|
356
|
-
limit_(limit),
|
|
357
|
-
fillCache_(fillCache) {
|
|
431
|
+
const bool fillCache,
|
|
432
|
+
std::shared_ptr<const rocksdb::Snapshot> snapshot)
|
|
433
|
+
: database_(database), snapshot_(snapshot), reverse_(reverse), limit_(limit), fillCache_(fillCache) {
|
|
358
434
|
if (lte) {
|
|
359
435
|
upper_bound_ = rocksdb::PinnableSlice();
|
|
360
436
|
*upper_bound_->GetSelf() = std::move(*lte) + '\0';
|
|
@@ -434,6 +510,7 @@ struct BaseIterator {
|
|
|
434
510
|
rocksdb::Status Status() const { return iterator_->status(); }
|
|
435
511
|
|
|
436
512
|
Database* database_;
|
|
513
|
+
std::shared_ptr<const rocksdb::Snapshot> snapshot_;
|
|
437
514
|
|
|
438
515
|
private:
|
|
439
516
|
void Init() {
|
|
@@ -453,7 +530,6 @@ struct BaseIterator {
|
|
|
453
530
|
|
|
454
531
|
std::optional<rocksdb::PinnableSlice> lower_bound_;
|
|
455
532
|
std::optional<rocksdb::PinnableSlice> upper_bound_;
|
|
456
|
-
std::shared_ptr<const rocksdb::Snapshot> snapshot_;
|
|
457
533
|
std::unique_ptr<rocksdb::Iterator> iterator_;
|
|
458
534
|
const bool reverse_;
|
|
459
535
|
const int limit_;
|
|
@@ -474,8 +550,9 @@ struct Iterator final : public BaseIterator {
|
|
|
474
550
|
const bool fillCache,
|
|
475
551
|
const bool keyAsBuffer,
|
|
476
552
|
const bool valueAsBuffer,
|
|
477
|
-
const uint32_t highWaterMarkBytes
|
|
478
|
-
|
|
553
|
+
const uint32_t highWaterMarkBytes,
|
|
554
|
+
std::shared_ptr<const rocksdb::Snapshot> snapshot)
|
|
555
|
+
: BaseIterator(database, reverse, lt, lte, gt, gte, limit, fillCache, snapshot),
|
|
479
556
|
keys_(keys),
|
|
480
557
|
values_(values),
|
|
481
558
|
keyAsBuffer_(keyAsBuffer),
|
|
@@ -505,6 +582,34 @@ struct Iterator final : public BaseIterator {
|
|
|
505
582
|
napi_ref ref_ = nullptr;
|
|
506
583
|
};
|
|
507
584
|
|
|
585
|
+
struct Updates {
|
|
586
|
+
Updates(Database* database, const bool keyAsBuffer, const bool valueAsBuffer, int64_t seqNumber)
|
|
587
|
+
: database_(database), keyAsBuffer_(keyAsBuffer), valueAsBuffer_(valueAsBuffer), seqNumber_(seqNumber) {}
|
|
588
|
+
|
|
589
|
+
void Close() { iterator_.reset(); }
|
|
590
|
+
|
|
591
|
+
void Attach(napi_env env, napi_value context) {
|
|
592
|
+
napi_create_reference(env, context, 1, &ref_);
|
|
593
|
+
database_->AttachUpdates(env, this);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
void Detach(napi_env env) {
|
|
597
|
+
database_->DetachUpdates(env, this);
|
|
598
|
+
if (ref_) {
|
|
599
|
+
napi_delete_reference(env, ref_);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
Database* database_;
|
|
604
|
+
const bool keyAsBuffer_;
|
|
605
|
+
const bool valueAsBuffer_;
|
|
606
|
+
int64_t seqNumber_;
|
|
607
|
+
std::unique_ptr<rocksdb::TransactionLogIterator> iterator_;
|
|
608
|
+
|
|
609
|
+
private:
|
|
610
|
+
napi_ref ref_ = nullptr;
|
|
611
|
+
};
|
|
612
|
+
|
|
508
613
|
/**
|
|
509
614
|
* Hook for when the environment exits. This hook will be called after
|
|
510
615
|
* already-scheduled napi_async_work items have finished, which gives us
|
|
@@ -521,8 +626,18 @@ static void env_cleanup_hook(void* arg) {
|
|
|
521
626
|
// following code must be a safe noop if called before db_open() or after
|
|
522
627
|
// db_close().
|
|
523
628
|
if (database && database->db_) {
|
|
524
|
-
// TODO: does not do `napi_delete_reference(env, iterator->ref_)`. Problem?
|
|
525
629
|
for (auto it : database->iterators_) {
|
|
630
|
+
// TODO: does not do `napi_delete_reference`. Problem?
|
|
631
|
+
it->Close();
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
for (auto it : database->snapshots_) {
|
|
635
|
+
// TODO: does not do `napi_delete_reference`. Problem?
|
|
636
|
+
it->Close();
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
for (auto it : database->updates_) {
|
|
640
|
+
// TODO: does not do `napi_delete_reference`. Problem?
|
|
526
641
|
it->Close();
|
|
527
642
|
}
|
|
528
643
|
|
|
@@ -587,11 +702,48 @@ NAPI_METHOD(db_open) {
|
|
|
587
702
|
options.IncreaseParallelism(
|
|
588
703
|
Uint32Property(env, argv[2], "parallelism").value_or(std::thread::hardware_concurrency() / 2));
|
|
589
704
|
|
|
705
|
+
options.avoid_unnecessary_blocking_io = true;
|
|
706
|
+
|
|
707
|
+
const auto memtable_memory_budget = Uint32Property(env, argv[2], "memtableMemoryBudget").value_or(256 * 1024 * 1024);
|
|
708
|
+
|
|
709
|
+
const auto compaction = StringProperty(env, argv[2], "compaction").value_or("level");
|
|
710
|
+
|
|
711
|
+
if (compaction == "universal") {
|
|
712
|
+
options.write_buffer_size = static_cast<size_t>(memtable_memory_budget / 4);
|
|
713
|
+
// merge two memtables when flushing to L0
|
|
714
|
+
options.min_write_buffer_number_to_merge = 2;
|
|
715
|
+
// this means we'll use 50% extra memory in the worst case, but will reduce
|
|
716
|
+
// write stalls.
|
|
717
|
+
options.max_write_buffer_number = 6;
|
|
718
|
+
// universal style compaction
|
|
719
|
+
options.compaction_style = rocksdb::kCompactionStyleUniversal;
|
|
720
|
+
options.compaction_options_universal.compression_size_percent = 80;
|
|
721
|
+
} else {
|
|
722
|
+
// merge two memtables when flushing to L0
|
|
723
|
+
options.min_write_buffer_number_to_merge = 2;
|
|
724
|
+
// this means we'll use 50% extra memory in the worst case, but will reduce
|
|
725
|
+
// write stalls.
|
|
726
|
+
options.max_write_buffer_number = 6;
|
|
727
|
+
// start flushing L0->L1 as soon as possible. each file on level0 is
|
|
728
|
+
// (memtable_memory_budget / 2). This will flush level 0 when it's bigger than
|
|
729
|
+
// memtable_memory_budget.
|
|
730
|
+
options.level0_file_num_compaction_trigger = 2;
|
|
731
|
+
// doesn't really matter much, but we don't want to create too many files
|
|
732
|
+
options.target_file_size_base = memtable_memory_budget / 8;
|
|
733
|
+
// make Level1 size equal to Level0 size, so that L0->L1 compactions are fast
|
|
734
|
+
options.max_bytes_for_level_base = memtable_memory_budget;
|
|
735
|
+
|
|
736
|
+
// level style compaction
|
|
737
|
+
options.compaction_style = rocksdb::kCompactionStyleLevel;
|
|
738
|
+
|
|
739
|
+
// TODO (perf): only compress levels >= 2
|
|
740
|
+
}
|
|
741
|
+
|
|
590
742
|
const auto location = ToString(env, argv[1]);
|
|
591
743
|
options.create_if_missing = BooleanProperty(env, argv[2], "createIfMissing").value_or(true);
|
|
592
744
|
options.error_if_exists = BooleanProperty(env, argv[2], "errorIfExists").value_or(false);
|
|
593
|
-
options.compression =
|
|
594
|
-
|
|
745
|
+
options.compression =
|
|
746
|
+
BooleanProperty(env, argv[2], "compression").value_or((true)) ? rocksdb::kZSTD : rocksdb::kNoCompression;
|
|
595
747
|
if (options.compression == rocksdb::kZSTD) {
|
|
596
748
|
options.compression_opts.max_dict_bytes = 16 * 1024;
|
|
597
749
|
options.compression_opts.zstd_max_train_bytes = 16 * 1024 * 100;
|
|
@@ -603,6 +755,9 @@ NAPI_METHOD(db_open) {
|
|
|
603
755
|
options.max_background_jobs =
|
|
604
756
|
Uint32Property(env, argv[2], "maxBackgroundJobs").value_or(std::thread::hardware_concurrency() / 4);
|
|
605
757
|
|
|
758
|
+
options.WAL_ttl_seconds = Uint32Property(env, argv[2], "walTTL").value_or(0) / 1e3;
|
|
759
|
+
options.WAL_size_limit_MB = Uint32Property(env, argv[2], "walSizeLimit").value_or(0) / 1e6;
|
|
760
|
+
|
|
606
761
|
// TODO: Consider direct IO (https://github.com/facebook/rocksdb/wiki/Direct-IO) once
|
|
607
762
|
// secondary compressed cache is stable.
|
|
608
763
|
|
|
@@ -647,9 +802,21 @@ NAPI_METHOD(db_open) {
|
|
|
647
802
|
tableOptions.cache_index_and_filter_blocks = false;
|
|
648
803
|
}
|
|
649
804
|
|
|
805
|
+
const auto optimize = StringProperty(env, argv[2], "optimize").value_or("");
|
|
806
|
+
|
|
807
|
+
if (optimize == "point-lookup") {
|
|
808
|
+
tableOptions.data_block_index_type = rocksdb::BlockBasedTableOptions::kDataBlockBinaryAndHash;
|
|
809
|
+
tableOptions.data_block_hash_table_util_ratio = 0.75;
|
|
810
|
+
tableOptions.filter_policy.reset(rocksdb::NewRibbonFilterPolicy(10));
|
|
811
|
+
|
|
812
|
+
options.memtable_prefix_bloom_size_ratio = 0.02;
|
|
813
|
+
options.memtable_whole_key_filtering = true;
|
|
814
|
+
} else {
|
|
815
|
+
tableOptions.filter_policy.reset(rocksdb::NewRibbonFilterPolicy(10));
|
|
816
|
+
}
|
|
817
|
+
|
|
650
818
|
tableOptions.block_size = Uint32Property(env, argv[2], "blockSize").value_or(4096);
|
|
651
819
|
tableOptions.block_restart_interval = Uint32Property(env, argv[2], "blockRestartInterval").value_or(16);
|
|
652
|
-
tableOptions.filter_policy.reset(rocksdb::NewRibbonFilterPolicy(10));
|
|
653
820
|
tableOptions.format_version = 5;
|
|
654
821
|
tableOptions.checksum = rocksdb::kXXH3;
|
|
655
822
|
tableOptions.optimize_filters_for_memory = BooleanProperty(env, argv[2], "optimizeFiltersForMemory").value_or(true);
|
|
@@ -692,6 +859,136 @@ NAPI_METHOD(db_close) {
|
|
|
692
859
|
return 0;
|
|
693
860
|
}
|
|
694
861
|
|
|
862
|
+
struct UpdateNextWorker final : public rocksdb::WriteBatch::Handler, public Worker {
|
|
863
|
+
UpdateNextWorker(napi_env env, Updates* updates, napi_value callback)
|
|
864
|
+
: Worker(env, updates->database_, callback, "rocks_level.db.get"), updates_(updates) {
|
|
865
|
+
database_->IncrementPriorityWork(env);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
rocksdb::Status Execute(Database& database) override {
|
|
869
|
+
rocksdb::TransactionLogIterator::ReadOptions options;
|
|
870
|
+
|
|
871
|
+
if (!updates_->iterator_) {
|
|
872
|
+
const auto status = database_->db_->GetUpdatesSince(updates_->seqNumber_, &updates_->iterator_, options);
|
|
873
|
+
if (!status.ok()) {
|
|
874
|
+
return status;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (!updates_->iterator_->Valid()) {
|
|
879
|
+
return rocksdb::Status::OK();
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
auto batch = updates_->iterator_->GetBatch();
|
|
883
|
+
|
|
884
|
+
updates_->seqNumber_ = batch.sequence;
|
|
885
|
+
|
|
886
|
+
const auto status = batch.writeBatchPtr->Iterate(this);
|
|
887
|
+
if (!status.ok()) {
|
|
888
|
+
return status;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
updates_->iterator_->Next();
|
|
892
|
+
|
|
893
|
+
return rocksdb::Status::OK();
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
napi_status OnOk(napi_env env, napi_value callback) override {
|
|
897
|
+
const auto size = cache_.size();
|
|
898
|
+
napi_value result;
|
|
899
|
+
|
|
900
|
+
if (cache_.size()) {
|
|
901
|
+
NAPI_STATUS_RETURN(napi_create_array_with_length(env, size, &result));
|
|
902
|
+
|
|
903
|
+
for (size_t idx = 0; idx < cache_.size(); idx += 2) {
|
|
904
|
+
napi_value key;
|
|
905
|
+
napi_value val;
|
|
906
|
+
|
|
907
|
+
NAPI_STATUS_RETURN(Convert(env, std::move(cache_[idx + 0]), updates_->keyAsBuffer_, key));
|
|
908
|
+
NAPI_STATUS_RETURN(Convert(env, std::move(cache_[idx + 1]), updates_->valueAsBuffer_, val));
|
|
909
|
+
|
|
910
|
+
NAPI_STATUS_RETURN(napi_set_element(env, result, static_cast<int>(idx + 0), key));
|
|
911
|
+
NAPI_STATUS_RETURN(napi_set_element(env, result, static_cast<int>(idx + 1), val));
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
cache_.clear();
|
|
915
|
+
} else {
|
|
916
|
+
NAPI_STATUS_RETURN(napi_get_null(env, &result));
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
napi_value argv[3];
|
|
920
|
+
NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
|
|
921
|
+
argv[1] = result;
|
|
922
|
+
NAPI_STATUS_RETURN(napi_create_bigint_int64(env, updates_->seqNumber_, &argv[2]));
|
|
923
|
+
return CallFunction(env, callback, 3, argv);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
void Destroy(napi_env env) override {
|
|
927
|
+
database_->DecrementPriorityWork(env);
|
|
928
|
+
Worker::Destroy(env);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
void Put(const rocksdb::Slice& key, const rocksdb::Slice& value) override {
|
|
932
|
+
cache_.emplace_back(key.ToString());
|
|
933
|
+
cache_.emplace_back(value.ToString());
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
void Delete(const rocksdb::Slice& key) override {
|
|
937
|
+
cache_.emplace_back(key.ToString());
|
|
938
|
+
cache_.emplace_back(std::nullopt);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
bool Continue() override { return true; }
|
|
942
|
+
|
|
943
|
+
private:
|
|
944
|
+
std::vector<std::optional<std::string>> cache_;
|
|
945
|
+
Updates* updates_;
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
NAPI_METHOD(updates_init) {
|
|
949
|
+
NAPI_ARGV(2);
|
|
950
|
+
NAPI_DB_CONTEXT();
|
|
951
|
+
|
|
952
|
+
const auto options = argv[1];
|
|
953
|
+
|
|
954
|
+
const bool keyAsBuffer = EncodingIsBuffer(env, options, "keyEncoding");
|
|
955
|
+
const bool valueAsBuffer = EncodingIsBuffer(env, options, "valueEncoding");
|
|
956
|
+
const auto seqNumber = Int64Property(env, options, "since").value_or(database->db_->GetLatestSequenceNumber());
|
|
957
|
+
|
|
958
|
+
auto updates = std::make_unique<Updates>(database, keyAsBuffer, valueAsBuffer, seqNumber);
|
|
959
|
+
|
|
960
|
+
napi_value result;
|
|
961
|
+
NAPI_STATUS_THROWS(napi_create_external(env, updates.get(), Finalize<Updates>, updates.get(), &result));
|
|
962
|
+
|
|
963
|
+
// Prevent GC of JS object before the iterator is closed (explicitly or on
|
|
964
|
+
// db close) and keep track of non-closed iterators to end them on db close.
|
|
965
|
+
updates.release()->Attach(env, result);
|
|
966
|
+
|
|
967
|
+
return result;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
NAPI_METHOD(updates_next) {
|
|
971
|
+
NAPI_ARGV(2);
|
|
972
|
+
NAPI_UPDATES_CONTEXT();
|
|
973
|
+
|
|
974
|
+
const auto callback = argv[1];
|
|
975
|
+
|
|
976
|
+
auto worker = new UpdateNextWorker(env, updates, callback);
|
|
977
|
+
worker->Queue(env);
|
|
978
|
+
|
|
979
|
+
return 0;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
NAPI_METHOD(updates_close) {
|
|
983
|
+
NAPI_ARGV(1);
|
|
984
|
+
NAPI_UPDATES_CONTEXT();
|
|
985
|
+
|
|
986
|
+
updates->Detach(env);
|
|
987
|
+
updates->Close();
|
|
988
|
+
|
|
989
|
+
return 0;
|
|
990
|
+
}
|
|
991
|
+
|
|
695
992
|
NAPI_METHOD(db_put) {
|
|
696
993
|
NAPI_ARGV(4);
|
|
697
994
|
NAPI_DB_CONTEXT();
|
|
@@ -782,8 +1079,7 @@ struct GetManyWorker final : public Worker {
|
|
|
782
1079
|
keys_(std::move(keys)),
|
|
783
1080
|
valueAsBuffer_(valueAsBuffer),
|
|
784
1081
|
fillCache_(fillCache),
|
|
785
|
-
snapshot_(database_->db_->GetSnapshot(),
|
|
786
|
-
[this](const rocksdb::Snapshot* ptr) { database_->db_->ReleaseSnapshot(ptr); }) {
|
|
1082
|
+
snapshot_(database_->db_->GetSnapshot(), [=](const auto ptr) { database_->db_->ReleaseSnapshot(ptr); }) {
|
|
787
1083
|
database_->IncrementPriorityWork(env);
|
|
788
1084
|
}
|
|
789
1085
|
|
|
@@ -910,7 +1206,7 @@ NAPI_METHOD(db_clear) {
|
|
|
910
1206
|
const auto lte = StringProperty(env, argv[1], "lte");
|
|
911
1207
|
const auto gt = StringProperty(env, argv[1], "gt");
|
|
912
1208
|
const auto gte = StringProperty(env, argv[1], "gte");
|
|
913
|
-
|
|
1209
|
+
|
|
914
1210
|
if (!reverse && limit == -1 && (lte || lt)) {
|
|
915
1211
|
rocksdb::Slice begin;
|
|
916
1212
|
if (gte) {
|
|
@@ -938,7 +1234,8 @@ NAPI_METHOD(db_clear) {
|
|
|
938
1234
|
} else {
|
|
939
1235
|
// TODO (perf): Use DeleteRange.
|
|
940
1236
|
|
|
941
|
-
|
|
1237
|
+
Snapshot snapshot(database);
|
|
1238
|
+
BaseIterator it(database, reverse, lt, lte, gt, gte, limit, false, snapshot.snapshot_);
|
|
942
1239
|
|
|
943
1240
|
it.SeekToRange();
|
|
944
1241
|
|
|
@@ -994,6 +1291,18 @@ NAPI_METHOD(db_get_property) {
|
|
|
994
1291
|
return result;
|
|
995
1292
|
}
|
|
996
1293
|
|
|
1294
|
+
NAPI_METHOD(db_get_latest_sequence_number) {
|
|
1295
|
+
NAPI_ARGV(1);
|
|
1296
|
+
NAPI_DB_CONTEXT();
|
|
1297
|
+
|
|
1298
|
+
const auto seq = database->db_->GetLatestSequenceNumber();
|
|
1299
|
+
|
|
1300
|
+
napi_value result;
|
|
1301
|
+
NAPI_STATUS_THROWS(napi_create_bigint_int64(env, seq, &result));
|
|
1302
|
+
|
|
1303
|
+
return result;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
997
1306
|
NAPI_METHOD(iterator_init) {
|
|
998
1307
|
NAPI_ARGV(2);
|
|
999
1308
|
NAPI_DB_CONTEXT();
|
|
@@ -1013,8 +1322,20 @@ NAPI_METHOD(iterator_init) {
|
|
|
1013
1322
|
const auto gt = StringProperty(env, options, "gt");
|
|
1014
1323
|
const auto gte = StringProperty(env, options, "gte");
|
|
1015
1324
|
|
|
1325
|
+
std::shared_ptr<const rocksdb::Snapshot> snapshotRaw;
|
|
1326
|
+
|
|
1327
|
+
if (HasProperty(env, options, "snapshot")) {
|
|
1328
|
+
const auto property = GetProperty(env, options, "snapshot");
|
|
1329
|
+
Snapshot* snapshot;
|
|
1330
|
+
NAPI_STATUS_THROWS(napi_get_value_external(env, property, reinterpret_cast<void**>(&snapshot)));
|
|
1331
|
+
snapshotRaw = snapshot->snapshot_;
|
|
1332
|
+
} else {
|
|
1333
|
+
Snapshot snapshot(database);
|
|
1334
|
+
snapshotRaw = snapshot.snapshot_;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1016
1337
|
auto iterator = std::make_unique<Iterator>(database, reverse, keys, values, limit, lt, lte, gt, gte, fillCache,
|
|
1017
|
-
keyAsBuffer, valueAsBuffer, highWaterMarkBytes);
|
|
1338
|
+
keyAsBuffer, valueAsBuffer, highWaterMarkBytes, std::move(snapshotRaw));
|
|
1018
1339
|
|
|
1019
1340
|
napi_value result;
|
|
1020
1341
|
NAPI_STATUS_THROWS(napi_create_external(env, iterator.get(), Finalize<Iterator>, iterator.get(), &result));
|
|
@@ -1049,6 +1370,18 @@ NAPI_METHOD(iterator_close) {
|
|
|
1049
1370
|
return 0;
|
|
1050
1371
|
}
|
|
1051
1372
|
|
|
1373
|
+
NAPI_METHOD(iterator_get_sequence) {
|
|
1374
|
+
NAPI_ARGV(1);
|
|
1375
|
+
NAPI_ITERATOR_CONTEXT();
|
|
1376
|
+
|
|
1377
|
+
const auto seq = iterator->snapshot_->GetSequenceNumber();
|
|
1378
|
+
|
|
1379
|
+
napi_value result;
|
|
1380
|
+
NAPI_STATUS_THROWS(napi_create_bigint_int64(env, seq, &result));
|
|
1381
|
+
|
|
1382
|
+
return 0;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1052
1385
|
struct NextWorker final : public Worker {
|
|
1053
1386
|
NextWorker(napi_env env, Iterator* iterator, uint32_t size, napi_value callback)
|
|
1054
1387
|
: Worker(env, iterator->database_, callback, "leveldown.iterator.next"), iterator_(iterator), size_(size) {}
|
|
@@ -1073,33 +1406,29 @@ struct NextWorker final : public Worker {
|
|
|
1073
1406
|
if (iterator_->keys_ && iterator_->values_) {
|
|
1074
1407
|
auto k = iterator_->CurrentKey();
|
|
1075
1408
|
auto v = iterator_->CurrentValue();
|
|
1076
|
-
cache_.push_back({});
|
|
1077
|
-
cache_.back().PinSelf(k);
|
|
1078
|
-
cache_.push_back({});
|
|
1079
|
-
cache_.back().PinSelf(v);
|
|
1080
1409
|
bytesRead += k.size() + v.size();
|
|
1410
|
+
cache_.push_back(k.ToString());
|
|
1411
|
+
cache_.push_back(v.ToString());
|
|
1081
1412
|
} else if (iterator_->keys_) {
|
|
1082
1413
|
auto k = iterator_->CurrentKey();
|
|
1083
|
-
cache_.push_back({});
|
|
1084
|
-
cache_.back().PinSelf(k);
|
|
1085
|
-
cache_.push_back({});
|
|
1086
|
-
// no value
|
|
1087
1414
|
bytesRead += k.size();
|
|
1415
|
+
cache_.push_back(k.ToString());
|
|
1416
|
+
cache_.push_back(std::nullopt);
|
|
1088
1417
|
} else if (iterator_->values_) {
|
|
1089
1418
|
auto v = iterator_->CurrentValue();
|
|
1090
|
-
cache_.push_back({});
|
|
1091
|
-
// no key
|
|
1092
|
-
cache_.push_back({});
|
|
1093
|
-
cache_.back().PinSelf(v);
|
|
1094
1419
|
bytesRead += v.size();
|
|
1420
|
+
cache_.push_back(std::nullopt);
|
|
1421
|
+
cache_.push_back(v.ToString());
|
|
1095
1422
|
}
|
|
1096
1423
|
|
|
1097
1424
|
if (bytesRead > iterator_->highWaterMarkBytes_ || cache_.size() / 2 >= size_) {
|
|
1098
|
-
finished_ =
|
|
1425
|
+
finished_ = false;
|
|
1099
1426
|
return rocksdb::Status::OK();
|
|
1100
1427
|
}
|
|
1101
1428
|
}
|
|
1102
1429
|
|
|
1430
|
+
finished_ = true;
|
|
1431
|
+
|
|
1103
1432
|
return iterator_->Status();
|
|
1104
1433
|
}
|
|
1105
1434
|
|
|
@@ -1124,15 +1453,15 @@ struct NextWorker final : public Worker {
|
|
|
1124
1453
|
napi_value argv[3];
|
|
1125
1454
|
NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
|
|
1126
1455
|
argv[1] = result;
|
|
1127
|
-
NAPI_STATUS_RETURN(napi_get_boolean(env,
|
|
1456
|
+
NAPI_STATUS_RETURN(napi_get_boolean(env, finished_, &argv[2]));
|
|
1128
1457
|
return CallFunction(env, callback, 3, argv);
|
|
1129
1458
|
}
|
|
1130
1459
|
|
|
1131
1460
|
private:
|
|
1132
|
-
std::vector<
|
|
1461
|
+
std::vector<std::optional<std::string>> cache_;
|
|
1133
1462
|
Iterator* iterator_ = nullptr;
|
|
1134
1463
|
uint32_t size_ = 0;
|
|
1135
|
-
bool finished_ =
|
|
1464
|
+
bool finished_ = true;
|
|
1136
1465
|
};
|
|
1137
1466
|
|
|
1138
1467
|
NAPI_METHOD(iterator_nextv) {
|
|
@@ -1243,10 +1572,54 @@ NAPI_METHOD(batch_write) {
|
|
|
1243
1572
|
return ToError(env, database->db_->Write(options, batch));
|
|
1244
1573
|
}
|
|
1245
1574
|
|
|
1575
|
+
NAPI_METHOD(snapshot_init) {
|
|
1576
|
+
NAPI_ARGV(1);
|
|
1577
|
+
NAPI_DB_CONTEXT();
|
|
1578
|
+
|
|
1579
|
+
auto snapshot = std::make_unique<Snapshot>(database);
|
|
1580
|
+
|
|
1581
|
+
napi_value result;
|
|
1582
|
+
NAPI_STATUS_THROWS(napi_create_external(env, snapshot.get(), Finalize<Snapshot>, snapshot.get(), &result));
|
|
1583
|
+
|
|
1584
|
+
// Prevent GC of JS object before the iterator is closed (explicitly or on
|
|
1585
|
+
// db close) and keep track of non-closed iterators to end them on db close.
|
|
1586
|
+
snapshot.release()->Attach(env, result);
|
|
1587
|
+
|
|
1588
|
+
return result;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
NAPI_METHOD(snapshot_get_sequence_number) {
|
|
1592
|
+
NAPI_ARGV(3);
|
|
1593
|
+
NAPI_DB_CONTEXT();
|
|
1594
|
+
|
|
1595
|
+
Snapshot* snapshot;
|
|
1596
|
+
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[1], reinterpret_cast<void**>(&snapshot)));
|
|
1597
|
+
|
|
1598
|
+
const auto seq = snapshot->GetSequenceNumber();
|
|
1599
|
+
|
|
1600
|
+
napi_value result;
|
|
1601
|
+
NAPI_STATUS_THROWS(napi_create_bigint_int64(env, seq, &result));
|
|
1602
|
+
|
|
1603
|
+
return result;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
NAPI_METHOD(snapshot_close) {
|
|
1607
|
+
NAPI_ARGV(2);
|
|
1608
|
+
NAPI_DB_CONTEXT();
|
|
1609
|
+
|
|
1610
|
+
Snapshot* snapshot;
|
|
1611
|
+
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[1], reinterpret_cast<void**>(&snapshot)));
|
|
1612
|
+
|
|
1613
|
+
snapshot->Close();
|
|
1614
|
+
|
|
1615
|
+
return 0;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1246
1618
|
NAPI_INIT() {
|
|
1247
1619
|
NAPI_EXPORT_FUNCTION(db_init);
|
|
1248
1620
|
NAPI_EXPORT_FUNCTION(db_open);
|
|
1249
1621
|
NAPI_EXPORT_FUNCTION(db_close);
|
|
1622
|
+
NAPI_EXPORT_FUNCTION(db_get_latest_sequence_number);
|
|
1250
1623
|
NAPI_EXPORT_FUNCTION(db_put);
|
|
1251
1624
|
NAPI_EXPORT_FUNCTION(db_get);
|
|
1252
1625
|
NAPI_EXPORT_FUNCTION(db_get_many);
|
|
@@ -1258,6 +1631,11 @@ NAPI_INIT() {
|
|
|
1258
1631
|
NAPI_EXPORT_FUNCTION(iterator_seek);
|
|
1259
1632
|
NAPI_EXPORT_FUNCTION(iterator_close);
|
|
1260
1633
|
NAPI_EXPORT_FUNCTION(iterator_nextv);
|
|
1634
|
+
NAPI_EXPORT_FUNCTION(iterator_get_sequence);
|
|
1635
|
+
|
|
1636
|
+
NAPI_EXPORT_FUNCTION(updates_init);
|
|
1637
|
+
NAPI_EXPORT_FUNCTION(updates_close);
|
|
1638
|
+
NAPI_EXPORT_FUNCTION(updates_next);
|
|
1261
1639
|
|
|
1262
1640
|
NAPI_EXPORT_FUNCTION(batch_do);
|
|
1263
1641
|
NAPI_EXPORT_FUNCTION(batch_init);
|
|
@@ -1265,4 +1643,8 @@ NAPI_INIT() {
|
|
|
1265
1643
|
NAPI_EXPORT_FUNCTION(batch_del);
|
|
1266
1644
|
NAPI_EXPORT_FUNCTION(batch_clear);
|
|
1267
1645
|
NAPI_EXPORT_FUNCTION(batch_write);
|
|
1646
|
+
|
|
1647
|
+
NAPI_EXPORT_FUNCTION(snapshot_init);
|
|
1648
|
+
NAPI_EXPORT_FUNCTION(snapshot_get_sequence_number);
|
|
1649
|
+
NAPI_EXPORT_FUNCTION(snapshot_close);
|
|
1268
1650
|
}
|
package/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const fs = require('fs')
|
|
|
6
6
|
const binding = require('./binding')
|
|
7
7
|
const { ChainedBatch } = require('./chained-batch')
|
|
8
8
|
const { Iterator } = require('./iterator')
|
|
9
|
+
const { Snapshot } = require('./snapshot')
|
|
9
10
|
|
|
10
11
|
const kContext = Symbol('context')
|
|
11
12
|
const kLocation = Symbol('location')
|
|
@@ -30,7 +31,13 @@ class RocksLevel extends AbstractLevel {
|
|
|
30
31
|
},
|
|
31
32
|
seek: true,
|
|
32
33
|
createIfMissing: true,
|
|
33
|
-
errorIfExists: true
|
|
34
|
+
errorIfExists: true,
|
|
35
|
+
additionalMethods: {
|
|
36
|
+
getLatestSequenceNumber: true,
|
|
37
|
+
getSnapshot: true,
|
|
38
|
+
updates: true,
|
|
39
|
+
query: true
|
|
40
|
+
}
|
|
34
41
|
}, options)
|
|
35
42
|
|
|
36
43
|
this[kLocation] = location
|
|
@@ -84,6 +91,10 @@ class RocksLevel extends AbstractLevel {
|
|
|
84
91
|
process.nextTick(callback, binding.batch_do(this[kContext], operations, options))
|
|
85
92
|
}
|
|
86
93
|
|
|
94
|
+
_iterator (options) {
|
|
95
|
+
return new Iterator(this, this[kContext], options)
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
getProperty (property) {
|
|
88
99
|
if (typeof property !== 'string') {
|
|
89
100
|
throw new TypeError("The first argument 'property' must be a string")
|
|
@@ -99,8 +110,181 @@ class RocksLevel extends AbstractLevel {
|
|
|
99
110
|
return binding.db_get_property(this[kContext], property)
|
|
100
111
|
}
|
|
101
112
|
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
getLatestSequenceNumber () {
|
|
114
|
+
if (this.status !== 'open') {
|
|
115
|
+
throw new ModuleError('Database is not open', {
|
|
116
|
+
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return binding.db_get_latest_sequence_number(this[kContext])
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getSnapshot () {
|
|
124
|
+
if (this.status !== 'open') {
|
|
125
|
+
throw new ModuleError('Database is not open', {
|
|
126
|
+
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return new Snapshot(this, this[kContext])
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async * query (options) {
|
|
134
|
+
if (this.status !== 'open') {
|
|
135
|
+
throw new ModuleError('Database is not open', {
|
|
136
|
+
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
class Query {
|
|
141
|
+
constructor (db, options) {
|
|
142
|
+
this.context = binding.iterator_init(db[kContext], options)
|
|
143
|
+
this.closed = false
|
|
144
|
+
this.promise = null
|
|
145
|
+
this.sequence = binding.iterator_get_sequence(this.context)
|
|
146
|
+
this.db = db
|
|
147
|
+
this.db.attachResource(this)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async next () {
|
|
151
|
+
if (this.closed) {
|
|
152
|
+
return {}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.promise = new Promise(resolve => binding.iterator_nextv(this.context, 1000, (err, rows, finished) => {
|
|
156
|
+
this.promise = null
|
|
157
|
+
if (err) {
|
|
158
|
+
resolve(Promise.reject(err))
|
|
159
|
+
} else {
|
|
160
|
+
resolve({
|
|
161
|
+
finished,
|
|
162
|
+
rows,
|
|
163
|
+
sequence: this.sequence
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}))
|
|
167
|
+
|
|
168
|
+
return this.promise
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async close (callback) {
|
|
172
|
+
try {
|
|
173
|
+
await this.promise
|
|
174
|
+
} catch {
|
|
175
|
+
// Do nothing...
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
if (!this.closed) {
|
|
180
|
+
this.closed = true
|
|
181
|
+
binding.iterator_close(this.context)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (callback) {
|
|
185
|
+
process.nextTick(callback)
|
|
186
|
+
}
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (callback) {
|
|
189
|
+
process.nextTick(callback, err)
|
|
190
|
+
} else {
|
|
191
|
+
throw err
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
194
|
+
this.db.detachResource(this)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const query = new Query(this, options)
|
|
200
|
+
try {
|
|
201
|
+
while (true) {
|
|
202
|
+
const { finished, rows, sequence } = await query.next()
|
|
203
|
+
|
|
204
|
+
yield { rows, sequence }
|
|
205
|
+
|
|
206
|
+
if (finished) {
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} finally {
|
|
211
|
+
await query.close()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async * updates (options) {
|
|
216
|
+
if (this.status !== 'open') {
|
|
217
|
+
throw new ModuleError('Database is not open', {
|
|
218
|
+
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
class Updates {
|
|
223
|
+
constructor (db, options) {
|
|
224
|
+
this.context = binding.updates_init(db[kContext], options)
|
|
225
|
+
this.closed = false
|
|
226
|
+
this.promise = null
|
|
227
|
+
this.db = db
|
|
228
|
+
this.db.attachResource(this)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async next () {
|
|
232
|
+
if (this.closed) {
|
|
233
|
+
return {}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.promise = new Promise(resolve => binding.updates_next(this.context, (err, rows, sequence) => {
|
|
237
|
+
this.promise = null
|
|
238
|
+
if (err) {
|
|
239
|
+
resolve(Promise.reject(err))
|
|
240
|
+
} else {
|
|
241
|
+
resolve({ rows, sequence })
|
|
242
|
+
}
|
|
243
|
+
}))
|
|
244
|
+
|
|
245
|
+
return this.promise
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async close (callback) {
|
|
249
|
+
try {
|
|
250
|
+
await this.promise
|
|
251
|
+
} catch {
|
|
252
|
+
// Do nothing...
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
if (!this.closed) {
|
|
257
|
+
this.closed = true
|
|
258
|
+
binding.updates_close(this.context)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (callback) {
|
|
262
|
+
process.nextTick(callback)
|
|
263
|
+
}
|
|
264
|
+
} catch (err) {
|
|
265
|
+
if (callback) {
|
|
266
|
+
process.nextTick(callback, err)
|
|
267
|
+
} else {
|
|
268
|
+
throw err
|
|
269
|
+
}
|
|
270
|
+
} finally {
|
|
271
|
+
this.db.detachResource(this)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const updates = new Updates(this, options)
|
|
277
|
+
try {
|
|
278
|
+
while (true) {
|
|
279
|
+
const { rows, sequence } = await updates.next()
|
|
280
|
+
if (!rows) {
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
yield { rows, sequence }
|
|
284
|
+
}
|
|
285
|
+
} finally {
|
|
286
|
+
await updates.close()
|
|
287
|
+
}
|
|
104
288
|
}
|
|
105
289
|
}
|
|
106
290
|
|
package/iterator.js
CHANGED
package/package.json
CHANGED
|
Binary file
|
package/snapshot.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const binding = require('./binding')
|
|
4
|
+
|
|
5
|
+
const kDbContext = Symbol('db')
|
|
6
|
+
const kContext = Symbol('context')
|
|
7
|
+
|
|
8
|
+
class Snapshot {
|
|
9
|
+
constructor (db, context) {
|
|
10
|
+
this[kDbContext] = context
|
|
11
|
+
this[kContext] = binding.snapshot_init(context)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get sequenceNumber () {
|
|
15
|
+
return binding.snapshot_get_sequence_number(this[kContext])
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
close () {
|
|
19
|
+
binding.snapshot_close(this[kContext])
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
exports.Snapshot = Snapshot
|
|
Binary file
|
|
Binary file
|