@nxtedition/rocksdb 6.0.3 → 7.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/binding.cc CHANGED
@@ -30,6 +30,7 @@ class NullLogger : public rocksdb::Logger {
30
30
 
31
31
  struct Database;
32
32
  struct Iterator;
33
+ struct Updates;
33
34
 
34
35
  #define NAPI_STATUS_RETURN(call) \
35
36
  { \
@@ -39,17 +40,14 @@ struct Iterator;
39
40
  } \
40
41
  }
41
42
 
42
- #define NAPI_DB_CONTEXT() \
43
- Database* database = nullptr; \
44
- NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
45
-
46
- #define NAPI_ITERATOR_CONTEXT() \
47
- Iterator* iterator = nullptr; \
48
- NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&iterator));
49
-
50
- #define NAPI_BATCH_CONTEXT() \
51
- rocksdb::WriteBatch* batch = nullptr; \
52
- NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&batch));
43
+ #define ROCKS_STATUS_THROWS(call) \
44
+ { \
45
+ const auto status = (call); \
46
+ if (!status.ok()) { \
47
+ napi_throw(env, ToError(env, status)); \
48
+ return NULL; \
49
+ } \
50
+ }
53
51
 
54
52
  static bool IsString(napi_env env, napi_value value) {
55
53
  napi_valuetype type;
@@ -111,6 +109,20 @@ static bool EncodingIsBuffer(napi_env env, napi_value obj, const std::string_vie
111
109
  return false;
112
110
  }
113
111
 
112
+ template <typename T>
113
+ static std::optional<T*> ExternalValueProperty(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
+
117
+ T* result;
118
+ if (napi_get_value_external(env, value, reinterpret_cast<void**>(&result)) == napi_ok) {
119
+ return result;
120
+ }
121
+ }
122
+
123
+ return {};
124
+ }
125
+
114
126
  static std::optional<uint32_t> Uint32Property(napi_env env, napi_value obj, const std::string_view& key) {
115
127
  if (HasProperty(env, obj, key.data())) {
116
128
  const auto value = GetProperty(env, obj, key.data());
@@ -133,6 +145,18 @@ static std::optional<int> Int32Property(napi_env env, napi_value obj, const std:
133
145
  return {};
134
146
  }
135
147
 
148
+ static std::optional<int64_t> Int64Property(napi_env env, napi_value obj, const std::string_view& key) {
149
+ if (HasProperty(env, obj, key.data())) {
150
+ const auto value = GetProperty(env, obj, key.data());
151
+ int64_t result;
152
+ bool lossless;
153
+ napi_get_value_bigint_int64(env, value, &result, &lossless);
154
+ return result;
155
+ }
156
+
157
+ return {};
158
+ }
159
+
136
160
  static std::string ToString(napi_env env, napi_value from, const std::string& defaultValue = "") {
137
161
  if (IsString(env, from)) {
138
162
  size_t length = 0;
@@ -210,6 +234,18 @@ napi_status Convert(napi_env env, rocksdb::PinnableSlice&& s, bool asBuffer, nap
210
234
  }
211
235
  }
212
236
 
237
+ napi_status Convert(napi_env env, std::optional<std::string>&& s, bool asBuffer, napi_value& result) {
238
+ if (!s) {
239
+ return napi_get_null(env, &result);
240
+ } else if (asBuffer) {
241
+ auto ptr = new std::string(std::move(*s));
242
+ return napi_create_external_buffer(env, ptr->size(), const_cast<char*>(ptr->data()), Finalize<std::string>, ptr,
243
+ &result);
244
+ } else {
245
+ return napi_create_string_utf8(env, s->data(), s->size(), &result);
246
+ }
247
+ }
248
+
213
249
  struct NapiSlice : public rocksdb::Slice {
214
250
  std::unique_ptr<char[]> heap_;
215
251
  std::array<char, 128> stack_;
@@ -318,6 +354,16 @@ struct Database {
318
354
  DecrementPriorityWork(env);
319
355
  }
320
356
 
357
+ void AttachUpdates(napi_env env, Updates* update) {
358
+ updates_.insert(update);
359
+ IncrementPriorityWork(env);
360
+ }
361
+
362
+ void DetachUpdates(napi_env env, Updates* update) {
363
+ updates_.erase(update);
364
+ DecrementPriorityWork(env);
365
+ }
366
+
321
367
  void IncrementPriorityWork(napi_env env) { napi_reference_ref(env, priorityRef_, &priorityWork_); }
322
368
 
323
369
  void DecrementPriorityWork(napi_env env) {
@@ -334,6 +380,8 @@ struct Database {
334
380
  std::unique_ptr<rocksdb::DB> db_;
335
381
  Worker* pendingCloseWorker_;
336
382
  std::set<Iterator*> iterators_;
383
+ std::set<Updates*> updates_;
384
+ std::vector<rocksdb::ColumnFamilyHandle*> columns_;
337
385
  napi_ref priorityRef_;
338
386
 
339
387
  private:
@@ -342,16 +390,18 @@ struct Database {
342
390
 
343
391
  struct BaseIterator {
344
392
  BaseIterator(Database* database,
393
+ rocksdb::ColumnFamilyHandle* column,
345
394
  const bool reverse,
346
395
  const std::optional<std::string>& lt,
347
396
  const std::optional<std::string>& lte,
348
397
  const std::optional<std::string>& gt,
349
398
  const std::optional<std::string>& gte,
350
399
  const int limit,
351
- const bool fillCache)
400
+ const bool fillCache,
401
+ std::shared_ptr<const rocksdb::Snapshot> snapshot)
352
402
  : database_(database),
353
- snapshot_(database_->db_->GetSnapshot(),
354
- [this](const rocksdb::Snapshot* ptr) { database_->db_->ReleaseSnapshot(ptr); }),
403
+ column_(column),
404
+ snapshot_(snapshot),
355
405
  reverse_(reverse),
356
406
  limit_(limit),
357
407
  fillCache_(fillCache) {
@@ -434,6 +484,8 @@ struct BaseIterator {
434
484
  rocksdb::Status Status() const { return iterator_->status(); }
435
485
 
436
486
  Database* database_;
487
+ rocksdb::ColumnFamilyHandle* column_;
488
+ std::shared_ptr<const rocksdb::Snapshot> snapshot_;
437
489
 
438
490
  private:
439
491
  void Init() {
@@ -448,12 +500,11 @@ struct BaseIterator {
448
500
  options.snapshot = snapshot_.get();
449
501
  options.async_io = true;
450
502
 
451
- iterator_.reset(database_->db_->NewIterator(options));
503
+ iterator_.reset(database_->db_->NewIterator(options, column_));
452
504
  }
453
505
 
454
506
  std::optional<rocksdb::PinnableSlice> lower_bound_;
455
507
  std::optional<rocksdb::PinnableSlice> upper_bound_;
456
- std::shared_ptr<const rocksdb::Snapshot> snapshot_;
457
508
  std::unique_ptr<rocksdb::Iterator> iterator_;
458
509
  const bool reverse_;
459
510
  const int limit_;
@@ -463,6 +514,7 @@ struct BaseIterator {
463
514
 
464
515
  struct Iterator final : public BaseIterator {
465
516
  Iterator(Database* database,
517
+ rocksdb::ColumnFamilyHandle* column,
466
518
  const bool reverse,
467
519
  const bool keys,
468
520
  const bool values,
@@ -474,8 +526,9 @@ struct Iterator final : public BaseIterator {
474
526
  const bool fillCache,
475
527
  const bool keyAsBuffer,
476
528
  const bool valueAsBuffer,
477
- const uint32_t highWaterMarkBytes)
478
- : BaseIterator(database, reverse, lt, lte, gt, gte, limit, fillCache),
529
+ const uint32_t highWaterMarkBytes,
530
+ std::shared_ptr<const rocksdb::Snapshot> snapshot)
531
+ : BaseIterator(database, column, reverse, lt, lte, gt, gte, limit, fillCache, snapshot),
479
532
  keys_(keys),
480
533
  values_(values),
481
534
  keyAsBuffer_(keyAsBuffer),
@@ -505,6 +558,39 @@ struct Iterator final : public BaseIterator {
505
558
  napi_ref ref_ = nullptr;
506
559
  };
507
560
 
561
+ struct Updates {
562
+ Updates(Database* database, const bool keyAsBuffer, const bool valueAsBuffer, int64_t seqNumber)
563
+ : database_(database), keyAsBuffer_(keyAsBuffer), valueAsBuffer_(valueAsBuffer), seqNumber_(seqNumber) {}
564
+
565
+ void Close() { iterator_.reset(); }
566
+
567
+ void Attach(napi_env env, napi_value context) {
568
+ napi_create_reference(env, context, 1, &ref_);
569
+ database_->AttachUpdates(env, this);
570
+ }
571
+
572
+ void Detach(napi_env env) {
573
+ database_->DetachUpdates(env, this);
574
+ if (ref_) {
575
+ napi_delete_reference(env, ref_);
576
+ }
577
+ }
578
+
579
+ Database* database_;
580
+ const bool keyAsBuffer_;
581
+ const bool valueAsBuffer_;
582
+ int64_t seqNumber_;
583
+ std::unique_ptr<rocksdb::TransactionLogIterator> iterator_;
584
+
585
+ private:
586
+ napi_ref ref_ = nullptr;
587
+ };
588
+
589
+ static rocksdb::ColumnFamilyHandle* GetColumnFamily(Database* database, napi_env env, napi_value options) {
590
+ auto column_opt = ExternalValueProperty<rocksdb::ColumnFamilyHandle>(env, options, "column");
591
+ return column_opt ? *column_opt : database->db_->DefaultColumnFamily();
592
+ }
593
+
508
594
  /**
509
595
  * Hook for when the environment exits. This hook will be called after
510
596
  * already-scheduled napi_async_work items have finished, which gives us
@@ -521,11 +607,20 @@ static void env_cleanup_hook(void* arg) {
521
607
  // following code must be a safe noop if called before db_open() or after
522
608
  // db_close().
523
609
  if (database && database->db_) {
524
- // TODO: does not do `napi_delete_reference(env, iterator->ref_)`. Problem?
525
610
  for (auto it : database->iterators_) {
611
+ // TODO: does not do `napi_delete_reference`. Problem?
612
+ it->Close();
613
+ }
614
+
615
+ for (auto it : database->updates_) {
616
+ // TODO: does not do `napi_delete_reference`. Problem?
526
617
  it->Close();
527
618
  }
528
619
 
620
+ for (auto it : database->columns_) {
621
+ database->db_->DestroyColumnFamilyHandle(it);
622
+ }
623
+
529
624
  // Having closed the iterators (and released snapshots) we can safely close.
530
625
  database->db_->Close();
531
626
  }
@@ -558,55 +653,154 @@ struct OpenWorker final : public Worker {
558
653
  Database* database,
559
654
  napi_value callback,
560
655
  const std::string& location,
561
- rocksdb::Options options,
562
- const bool readOnly)
656
+ const rocksdb::Options& options,
657
+ std::vector<rocksdb::ColumnFamilyDescriptor> column_families)
563
658
  : Worker(env, database, callback, "leveldown.db.open"),
564
659
  options_(options),
565
- readOnly_(readOnly),
566
- location_(location) {}
660
+ location_(location),
661
+ column_families_(std::move(column_families)) {}
567
662
 
568
663
  rocksdb::Status Execute(Database& database) override {
569
664
  rocksdb::DB* db = nullptr;
570
- const auto status = readOnly_ ? rocksdb::DB::OpenForReadOnly(options_, location_, &db)
571
- : rocksdb::DB::Open(options_, location_, &db);
665
+ const auto status = column_families_.empty()
666
+ ? rocksdb::DB::Open(options_, location_, &db)
667
+ : rocksdb::DB::Open(options_, location_, column_families_, &database.columns_, &db);
572
668
  database.db_.reset(db);
573
669
  return status;
574
670
  }
575
671
 
672
+ napi_status OnOk(napi_env env, napi_value callback) override {
673
+ const auto size = database_->columns_.size();
674
+ napi_value result;
675
+ NAPI_STATUS_RETURN(napi_create_object(env, &result));
676
+
677
+ for (size_t n = 0; n < size; ++n) {
678
+ napi_value column;
679
+ NAPI_STATUS_RETURN(napi_create_external(env, database_->columns_[n], nullptr, nullptr, &column));
680
+ NAPI_STATUS_RETURN(napi_set_named_property(env, result, column_families_[n].name.c_str(), column));
681
+ }
682
+
683
+ napi_value argv[2];
684
+ NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
685
+ argv[1] = result;
686
+ return CallFunction(env, callback, 2, argv);
687
+ }
688
+
576
689
  rocksdb::Options options_;
577
- const bool readOnly_;
578
690
  const std::string location_;
691
+ std::vector<rocksdb::ColumnFamilyDescriptor> column_families_;
579
692
  };
580
693
 
581
- NAPI_METHOD(db_open) {
582
- NAPI_ARGV(4);
583
- NAPI_DB_CONTEXT();
584
-
585
- rocksdb::Options options;
586
-
587
- options.IncreaseParallelism(
588
- Uint32Property(env, argv[2], "parallelism").value_or(std::thread::hardware_concurrency() / 2));
694
+ napi_status InitOptions(napi_env env, auto& options, auto options2) {
695
+ const auto memtable_memory_budget = Uint32Property(env, options2, "memtableMemoryBudget").value_or(256 * 1024 * 1024);
696
+
697
+ const auto compaction = StringProperty(env, options2, "compaction").value_or("level");
698
+
699
+ if (compaction == "universal") {
700
+ options.write_buffer_size = static_cast<size_t>(memtable_memory_budget / 4);
701
+ // merge two memtables when flushing to L0
702
+ options.min_write_buffer_number_to_merge = 2;
703
+ // this means we'll use 50% extra memory in the worst case, but will reduce
704
+ // write stalls.
705
+ options.max_write_buffer_number = 6;
706
+ // universal style compaction
707
+ options.compaction_style = rocksdb::kCompactionStyleUniversal;
708
+ options.compaction_options_universal.compression_size_percent = 80;
709
+ } else {
710
+ // merge two memtables when flushing to L0
711
+ options.min_write_buffer_number_to_merge = 2;
712
+ // this means we'll use 50% extra memory in the worst case, but will reduce
713
+ // write stalls.
714
+ options.max_write_buffer_number = 6;
715
+ // start flushing L0->L1 as soon as possible. each file on level0 is
716
+ // (memtable_memory_budget / 2). This will flush level 0 when it's bigger than
717
+ // memtable_memory_budget.
718
+ options.level0_file_num_compaction_trigger = 2;
719
+ // doesn't really matter much, but we don't want to create too many files
720
+ options.target_file_size_base = memtable_memory_budget / 8;
721
+ // make Level1 size equal to Level0 size, so that L0->L1 compactions are fast
722
+ options.max_bytes_for_level_base = memtable_memory_budget;
723
+
724
+ // level style compaction
725
+ options.compaction_style = rocksdb::kCompactionStyleLevel;
726
+
727
+ // TODO (perf): only compress levels >= 2
728
+ }
589
729
 
590
- const auto location = ToString(env, argv[1]);
591
- options.create_if_missing = BooleanProperty(env, argv[2], "createIfMissing").value_or(true);
592
- options.error_if_exists = BooleanProperty(env, argv[2], "errorIfExists").value_or(false);
593
- options.compression = BooleanProperty(env, argv[2], "compression").value_or((true)) ? rocksdb::kZSTD
594
- : rocksdb::kNoCompression;
730
+ options.compression =
731
+ BooleanProperty(env, options2, "compression").value_or((true)) ? rocksdb::kZSTD : rocksdb::kNoCompression;
595
732
  if (options.compression == rocksdb::kZSTD) {
596
733
  options.compression_opts.max_dict_bytes = 16 * 1024;
597
734
  options.compression_opts.zstd_max_train_bytes = 16 * 1024 * 100;
598
735
  // options.compression_opts.parallel_threads
599
736
  }
600
737
 
601
- options.use_adaptive_mutex = BooleanProperty(env, argv[2], "useAdaptiveMutex").value_or(true);
602
- options.enable_pipelined_write = BooleanProperty(env, argv[2], "enablePipelinedWrite").value_or(true);
603
- options.max_background_jobs =
604
- Uint32Property(env, argv[2], "maxBackgroundJobs").value_or(std::thread::hardware_concurrency() / 4);
738
+ const auto cacheSize = Uint32Property(env, options2, "cacheSize").value_or(8 << 20);
739
+
740
+ rocksdb::BlockBasedTableOptions tableOptions;
741
+
742
+ if (cacheSize) {
743
+ tableOptions.block_cache = rocksdb::NewLRUCache(cacheSize);
744
+ tableOptions.cache_index_and_filter_blocks =
745
+ BooleanProperty(env, options2, "cacheIndexAndFilterBlocks").value_or(true);
746
+ } else {
747
+ tableOptions.no_block_cache = true;
748
+ tableOptions.cache_index_and_filter_blocks = false;
749
+ }
750
+
751
+ const auto optimize = StringProperty(env, options2, "optimize").value_or("");
752
+
753
+ if (optimize == "point-lookup") {
754
+ tableOptions.data_block_index_type = rocksdb::BlockBasedTableOptions::kDataBlockBinaryAndHash;
755
+ tableOptions.data_block_hash_table_util_ratio = 0.75;
756
+ tableOptions.filter_policy.reset(rocksdb::NewRibbonFilterPolicy(10, 1));
757
+
758
+ options.memtable_prefix_bloom_size_ratio = 0.02;
759
+ options.memtable_whole_key_filtering = true;
760
+ } else if (optimize == "range-lookup") {
761
+ // TODO?
762
+ } else {
763
+ tableOptions.filter_policy.reset(rocksdb::NewRibbonFilterPolicy(10));
764
+ }
765
+
766
+ tableOptions.block_size = Uint32Property(env, options2, "blockSize").value_or(4096);
767
+ tableOptions.block_restart_interval = Uint32Property(env, options2, "blockRestartInterval").value_or(16);
768
+ tableOptions.format_version = 5;
769
+ tableOptions.checksum = rocksdb::kXXH3;
770
+ tableOptions.optimize_filters_for_memory = BooleanProperty(env, options2, "optimizeFiltersForMemory").value_or(true);
771
+
772
+ options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(tableOptions));
773
+
774
+ return napi_ok;
775
+ }
776
+
777
+ NAPI_METHOD(db_open) {
778
+ NAPI_ARGV(4);
779
+
780
+ Database* database;
781
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
782
+
783
+ const auto location = ToString(env, argv[1]);
784
+ const auto options = argv[2];
785
+ const auto callback = argv[3];
786
+
787
+ rocksdb::Options dbOptions;
788
+
789
+ dbOptions.IncreaseParallelism(
790
+ Uint32Property(env, options, "parallelism").value_or(std::thread::hardware_concurrency() / 2));
605
791
 
606
- // TODO: Consider direct IO (https://github.com/facebook/rocksdb/wiki/Direct-IO) once
607
- // secondary compressed cache is stable.
792
+ dbOptions.create_if_missing = BooleanProperty(env, options, "createIfMissing").value_or(true);
793
+ dbOptions.error_if_exists = BooleanProperty(env, options, "errorIfExists").value_or(false);
794
+ dbOptions.avoid_unnecessary_blocking_io = true;
795
+ dbOptions.use_adaptive_mutex = BooleanProperty(env, options, "useAdaptiveMutex").value_or(true);
796
+ dbOptions.enable_pipelined_write = BooleanProperty(env, options, "enablePipelinedWrite").value_or(true);
797
+ dbOptions.max_background_jobs =
798
+ Uint32Property(env, options, "maxBackgroundJobs").value_or(std::thread::hardware_concurrency() / 4);
799
+ dbOptions.WAL_ttl_seconds = Uint32Property(env, options, "walTTL").value_or(0) / 1e3;
800
+ dbOptions.WAL_size_limit_MB = Uint32Property(env, options, "walSizeLimit").value_or(0) / 1e6;
801
+ dbOptions.create_missing_column_families = true;
608
802
 
609
- const auto infoLogLevel = StringProperty(env, argv[2], "infoLogLevel").value_or("");
803
+ const auto infoLogLevel = StringProperty(env, options, "infoLogLevel").value_or("");
610
804
  if (infoLogLevel.size() > 0) {
611
805
  rocksdb::InfoLogLevel lvl = {};
612
806
 
@@ -625,40 +819,43 @@ NAPI_METHOD(db_open) {
625
819
  else
626
820
  napi_throw_error(env, nullptr, "invalid log level");
627
821
 
628
- options.info_log_level = lvl;
822
+ dbOptions.info_log_level = lvl;
629
823
  } else {
630
824
  // In some places RocksDB checks this option to see if it should prepare
631
825
  // debug information (ahead of logging), so set it to the highest level.
632
- options.info_log_level = rocksdb::InfoLogLevel::HEADER_LEVEL;
633
- options.info_log.reset(new NullLogger());
826
+ dbOptions.info_log_level = rocksdb::InfoLogLevel::HEADER_LEVEL;
827
+ dbOptions.info_log.reset(new NullLogger());
634
828
  }
635
829
 
636
- const auto readOnly = BooleanProperty(env, argv[2], "readOnly").value_or(false);
637
- const auto cacheSize = Uint32Property(env, argv[2], "cacheSize").value_or(8 << 20);
830
+ NAPI_STATUS_THROWS(InitOptions(env, dbOptions, options));
638
831
 
639
- rocksdb::BlockBasedTableOptions tableOptions;
832
+ std::vector<rocksdb::ColumnFamilyDescriptor> column_families;
833
+
834
+ if (HasProperty(env, options, "columns")) {
835
+ napi_value columns;
836
+ NAPI_STATUS_THROWS(napi_get_named_property(env, options, "columns", &columns));
640
837
 
641
- if (cacheSize) {
642
- tableOptions.block_cache = rocksdb::NewLRUCache(cacheSize);
643
- tableOptions.cache_index_and_filter_blocks =
644
- BooleanProperty(env, argv[2], "cacheIndexAndFilterBlocks").value_or(true);
645
- } else {
646
- tableOptions.no_block_cache = true;
647
- tableOptions.cache_index_and_filter_blocks = false;
648
- }
838
+ napi_value keys;
839
+ NAPI_STATUS_THROWS(napi_get_property_names(env, columns, &keys));
649
840
 
650
- tableOptions.block_size = Uint32Property(env, argv[2], "blockSize").value_or(4096);
651
- tableOptions.block_restart_interval = Uint32Property(env, argv[2], "blockRestartInterval").value_or(16);
652
- tableOptions.filter_policy.reset(rocksdb::NewRibbonFilterPolicy(10));
653
- tableOptions.format_version = 5;
654
- tableOptions.checksum = rocksdb::kXXH3;
655
- tableOptions.optimize_filters_for_memory = BooleanProperty(env, argv[2], "optimizeFiltersForMemory").value_or(true);
841
+ uint32_t len;
842
+ NAPI_STATUS_THROWS(napi_get_array_length(env, keys, &len));
656
843
 
657
- options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(tableOptions));
844
+ for (uint32_t n = 0; n < len; ++n) {
845
+ napi_value key;
846
+ NAPI_STATUS_THROWS(napi_get_element(env, keys, n, &key));
847
+
848
+ napi_value column;
849
+ NAPI_STATUS_THROWS(napi_get_property(env, columns, key, &column));
658
850
 
659
- const auto callback = argv[3];
851
+ rocksdb::ColumnFamilyOptions columnOptions;
852
+ NAPI_STATUS_THROWS(InitOptions(env, columnOptions, column));
660
853
 
661
- auto worker = new OpenWorker(env, database, callback, location, options, readOnly);
854
+ column_families.emplace_back(ToString(env, key), columnOptions);
855
+ }
856
+ }
857
+
858
+ auto worker = new OpenWorker(env, database, callback, location, dbOptions, column_families);
662
859
  worker->Queue(env);
663
860
 
664
861
  return 0;
@@ -668,7 +865,13 @@ struct CloseWorker final : public Worker {
668
865
  CloseWorker(napi_env env, Database* database, napi_value callback)
669
866
  : Worker(env, database, callback, "leveldown.db.close") {}
670
867
 
671
- rocksdb::Status Execute(Database& database) override { return database.db_->Close(); }
868
+ rocksdb::Status Execute(Database& database) override {
869
+ for (auto it : database.columns_) {
870
+ database.db_->DestroyColumnFamilyHandle(it);
871
+ }
872
+
873
+ return database.db_->Close();
874
+ }
672
875
  };
673
876
 
674
877
  napi_value noop_callback(napi_env env, napi_callback_info info) {
@@ -677,7 +880,9 @@ napi_value noop_callback(napi_env env, napi_callback_info info) {
677
880
 
678
881
  NAPI_METHOD(db_close) {
679
882
  NAPI_ARGV(2);
680
- NAPI_DB_CONTEXT();
883
+
884
+ Database* database;
885
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
681
886
 
682
887
  const auto callback = argv[1];
683
888
 
@@ -692,9 +897,147 @@ NAPI_METHOD(db_close) {
692
897
  return 0;
693
898
  }
694
899
 
900
+ struct UpdateNextWorker final : public rocksdb::WriteBatch::Handler, public Worker {
901
+ UpdateNextWorker(napi_env env, Updates* updates, napi_value callback)
902
+ : Worker(env, updates->database_, callback, "rocks_level.db.get"), updates_(updates) {
903
+ database_->IncrementPriorityWork(env);
904
+ }
905
+
906
+ rocksdb::Status Execute(Database& database) override {
907
+ rocksdb::TransactionLogIterator::ReadOptions options;
908
+
909
+ if (!updates_->iterator_) {
910
+ const auto status = database_->db_->GetUpdatesSince(updates_->seqNumber_, &updates_->iterator_, options);
911
+ if (!status.ok()) {
912
+ return status;
913
+ }
914
+ }
915
+
916
+ if (!updates_->iterator_->Valid()) {
917
+ return rocksdb::Status::OK();
918
+ }
919
+
920
+ auto batch = updates_->iterator_->GetBatch();
921
+
922
+ updates_->seqNumber_ = batch.sequence;
923
+
924
+ const auto status = batch.writeBatchPtr->Iterate(this);
925
+ if (!status.ok()) {
926
+ return status;
927
+ }
928
+
929
+ updates_->iterator_->Next();
930
+
931
+ return rocksdb::Status::OK();
932
+ }
933
+
934
+ napi_status OnOk(napi_env env, napi_value callback) override {
935
+ const auto size = cache_.size();
936
+ napi_value result;
937
+
938
+ if (cache_.size()) {
939
+ NAPI_STATUS_RETURN(napi_create_array_with_length(env, size, &result));
940
+
941
+ for (size_t idx = 0; idx < cache_.size(); idx += 2) {
942
+ napi_value key;
943
+ napi_value val;
944
+
945
+ NAPI_STATUS_RETURN(Convert(env, std::move(cache_[idx + 0]), updates_->keyAsBuffer_, key));
946
+ NAPI_STATUS_RETURN(Convert(env, std::move(cache_[idx + 1]), updates_->valueAsBuffer_, val));
947
+
948
+ NAPI_STATUS_RETURN(napi_set_element(env, result, static_cast<int>(idx + 0), key));
949
+ NAPI_STATUS_RETURN(napi_set_element(env, result, static_cast<int>(idx + 1), val));
950
+ }
951
+
952
+ cache_.clear();
953
+ } else {
954
+ NAPI_STATUS_RETURN(napi_get_null(env, &result));
955
+ }
956
+
957
+ napi_value argv[3];
958
+ NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
959
+ argv[1] = result;
960
+ NAPI_STATUS_RETURN(napi_create_bigint_int64(env, updates_->seqNumber_, &argv[2]));
961
+ return CallFunction(env, callback, 3, argv);
962
+ }
963
+
964
+ void Destroy(napi_env env) override {
965
+ database_->DecrementPriorityWork(env);
966
+ Worker::Destroy(env);
967
+ }
968
+
969
+ void Put(const rocksdb::Slice& key, const rocksdb::Slice& value) override {
970
+ cache_.emplace_back(key.ToString());
971
+ cache_.emplace_back(value.ToString());
972
+ }
973
+
974
+ void Delete(const rocksdb::Slice& key) override {
975
+ cache_.emplace_back(key.ToString());
976
+ cache_.emplace_back(std::nullopt);
977
+ }
978
+
979
+ bool Continue() override { return true; }
980
+
981
+ private:
982
+ std::vector<std::optional<std::string>> cache_;
983
+ Updates* updates_;
984
+ };
985
+
986
+ NAPI_METHOD(updates_init) {
987
+ NAPI_ARGV(2);
988
+
989
+ Database* database;
990
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
991
+
992
+ const auto options = argv[1];
993
+
994
+ const bool keyAsBuffer = EncodingIsBuffer(env, options, "keyEncoding");
995
+ const bool valueAsBuffer = EncodingIsBuffer(env, options, "valueEncoding");
996
+ const auto seqNumber = Int64Property(env, options, "since").value_or(database->db_->GetLatestSequenceNumber());
997
+
998
+ auto updates = std::make_unique<Updates>(database, keyAsBuffer, valueAsBuffer, seqNumber);
999
+
1000
+ napi_value result;
1001
+ NAPI_STATUS_THROWS(napi_create_external(env, updates.get(), Finalize<Updates>, updates.get(), &result));
1002
+
1003
+ // Prevent GC of JS object before the iterator is closed (explicitly or on
1004
+ // db close) and keep track of non-closed iterators to end them on db close.
1005
+ updates.release()->Attach(env, result);
1006
+
1007
+ return result;
1008
+ }
1009
+
1010
+ NAPI_METHOD(updates_next) {
1011
+ NAPI_ARGV(2);
1012
+
1013
+ Updates* updates;
1014
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&updates));
1015
+
1016
+ const auto callback = argv[1];
1017
+
1018
+ auto worker = new UpdateNextWorker(env, updates, callback);
1019
+ worker->Queue(env);
1020
+
1021
+ return 0;
1022
+ }
1023
+
1024
+ NAPI_METHOD(updates_close) {
1025
+ NAPI_ARGV(1);
1026
+
1027
+ Updates* updates;
1028
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&updates));
1029
+
1030
+ updates->Detach(env);
1031
+ updates->Close();
1032
+
1033
+ return 0;
1034
+ }
1035
+
695
1036
  NAPI_METHOD(db_put) {
696
1037
  NAPI_ARGV(4);
697
- NAPI_DB_CONTEXT();
1038
+
1039
+ Database* database;
1040
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
698
1041
 
699
1042
  NapiSlice key;
700
1043
  NAPI_STATUS_THROWS(ToNapiSlice(env, argv[1], key));
@@ -702,18 +1045,20 @@ NAPI_METHOD(db_put) {
702
1045
  NapiSlice val;
703
1046
  NAPI_STATUS_THROWS(ToNapiSlice(env, argv[2], val));
704
1047
 
705
- rocksdb::WriteOptions options;
706
- return ToError(env, database->db_->Put(options, key, val));
1048
+ rocksdb::WriteOptions writeOptions;
1049
+ return ToError(env, database->db_->Put(writeOptions, key, val));
707
1050
  }
708
1051
 
709
1052
  struct GetWorker final : public Worker {
710
1053
  GetWorker(napi_env env,
711
1054
  Database* database,
1055
+ rocksdb::ColumnFamilyHandle* column,
712
1056
  napi_value callback,
713
1057
  const std::string& key,
714
1058
  const bool asBuffer,
715
1059
  const bool fillCache)
716
1060
  : Worker(env, database, callback, "rocks_level.db.get"),
1061
+ column_(column),
717
1062
  key_(key),
718
1063
  asBuffer_(asBuffer),
719
1064
  fillCache_(fillCache),
@@ -723,11 +1068,11 @@ struct GetWorker final : public Worker {
723
1068
  }
724
1069
 
725
1070
  rocksdb::Status Execute(Database& database) override {
726
- rocksdb::ReadOptions options;
727
- options.fill_cache = fillCache_;
728
- options.snapshot = snapshot_.get();
1071
+ rocksdb::ReadOptions readOptions;
1072
+ readOptions.fill_cache = fillCache_;
1073
+ readOptions.snapshot = snapshot_.get();
729
1074
 
730
- auto status = database.db_->Get(options, database.db_->DefaultColumnFamily(), key_, &value_);
1075
+ auto status = database.db_->Get(readOptions, column_, key_, &value_);
731
1076
 
732
1077
  key_.clear();
733
1078
  snapshot_ = nullptr;
@@ -748,6 +1093,7 @@ struct GetWorker final : public Worker {
748
1093
  }
749
1094
 
750
1095
  private:
1096
+ rocksdb::ColumnFamilyHandle* column_;
751
1097
  std::string key_;
752
1098
  rocksdb::PinnableSlice value_;
753
1099
  const bool asBuffer_;
@@ -757,15 +1103,18 @@ struct GetWorker final : public Worker {
757
1103
 
758
1104
  NAPI_METHOD(db_get) {
759
1105
  NAPI_ARGV(4);
760
- NAPI_DB_CONTEXT();
1106
+
1107
+ Database* database;
1108
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
761
1109
 
762
1110
  const auto key = ToString(env, argv[1]);
763
1111
  const auto options = argv[2];
764
1112
  const auto asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
765
1113
  const auto fillCache = BooleanProperty(env, options, "fillCache").value_or(true);
1114
+ const auto column = GetColumnFamily(database, env, options);
766
1115
  const auto callback = argv[3];
767
1116
 
768
- auto worker = new GetWorker(env, database, callback, key, asBuffer, fillCache);
1117
+ auto worker = new GetWorker(env, database, column, callback, key, asBuffer, fillCache);
769
1118
  worker->Queue(env);
770
1119
 
771
1120
  return 0;
@@ -774,24 +1123,25 @@ NAPI_METHOD(db_get) {
774
1123
  struct GetManyWorker final : public Worker {
775
1124
  GetManyWorker(napi_env env,
776
1125
  Database* database,
1126
+ rocksdb::ColumnFamilyHandle* column,
777
1127
  std::vector<std::string> keys,
778
1128
  napi_value callback,
779
1129
  const bool valueAsBuffer,
780
1130
  const bool fillCache)
781
1131
  : Worker(env, database, callback, "leveldown.get.many"),
1132
+ column_(column),
782
1133
  keys_(std::move(keys)),
783
1134
  valueAsBuffer_(valueAsBuffer),
784
1135
  fillCache_(fillCache),
785
- snapshot_(database_->db_->GetSnapshot(),
786
- [this](const rocksdb::Snapshot* ptr) { database_->db_->ReleaseSnapshot(ptr); }) {
1136
+ snapshot_(database_->db_->GetSnapshot(), [=](const auto ptr) { database_->db_->ReleaseSnapshot(ptr); }) {
787
1137
  database_->IncrementPriorityWork(env);
788
1138
  }
789
1139
 
790
1140
  rocksdb::Status Execute(Database& database) override {
791
- rocksdb::ReadOptions options;
792
- options.fill_cache = fillCache_;
793
- options.snapshot = snapshot_.get();
794
- options.async_io = true;
1141
+ rocksdb::ReadOptions readOptions;
1142
+ readOptions.fill_cache = fillCache_;
1143
+ readOptions.snapshot = snapshot_.get();
1144
+ readOptions.async_io = true;
795
1145
 
796
1146
  const auto numKeys = keys_.size();
797
1147
 
@@ -804,8 +1154,7 @@ struct GetManyWorker final : public Worker {
804
1154
  statuses_.resize(numKeys);
805
1155
  values_.resize(numKeys);
806
1156
 
807
- database.db_->MultiGet(options, database.db_->DefaultColumnFamily(), numKeys, keys.data(), values_.data(),
808
- statuses_.data());
1157
+ database.db_->MultiGet(readOptions, column_, numKeys, keys.data(), values_.data(), statuses_.data());
809
1158
 
810
1159
  keys_.clear();
811
1160
  snapshot_ = nullptr;
@@ -850,6 +1199,7 @@ struct GetManyWorker final : public Worker {
850
1199
  }
851
1200
 
852
1201
  private:
1202
+ rocksdb::ColumnFamilyHandle* column_;
853
1203
  std::vector<std::string> keys_;
854
1204
  std::vector<rocksdb::PinnableSlice> values_;
855
1205
  std::vector<rocksdb::Status> statuses_;
@@ -860,7 +1210,9 @@ struct GetManyWorker final : public Worker {
860
1210
 
861
1211
  NAPI_METHOD(db_get_many) {
862
1212
  NAPI_ARGV(4);
863
- NAPI_DB_CONTEXT();
1213
+
1214
+ Database* database;
1215
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
864
1216
 
865
1217
  std::vector<std::string> keys;
866
1218
  {
@@ -880,9 +1232,10 @@ NAPI_METHOD(db_get_many) {
880
1232
  const auto options = argv[2];
881
1233
  const bool asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
882
1234
  const bool fillCache = BooleanProperty(env, options, "fillCache").value_or(true);
1235
+ const auto column = GetColumnFamily(database, env, options);
883
1236
  const auto callback = argv[3];
884
1237
 
885
- auto worker = new GetManyWorker(env, database, std::move(keys), callback, asBuffer, fillCache);
1238
+ auto worker = new GetManyWorker(env, database, column, std::move(keys), callback, asBuffer, fillCache);
886
1239
  worker->Queue(env);
887
1240
 
888
1241
  return 0;
@@ -890,27 +1243,36 @@ NAPI_METHOD(db_get_many) {
890
1243
 
891
1244
  NAPI_METHOD(db_del) {
892
1245
  NAPI_ARGV(3);
893
- NAPI_DB_CONTEXT();
1246
+
1247
+ Database* database;
1248
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
894
1249
 
895
1250
  NapiSlice key;
896
1251
  NAPI_STATUS_THROWS(ToNapiSlice(env, argv[1], key));
897
1252
 
898
- rocksdb::WriteOptions options;
899
- return ToError(env, database->db_->Delete(options, key));
1253
+ const auto options = argv[2];
1254
+ const auto column = GetColumnFamily(database, env, options);
1255
+
1256
+ rocksdb::WriteOptions writeOptions;
1257
+ return ToError(env, database->db_->Delete(writeOptions, column, key));
900
1258
  }
901
1259
 
902
1260
  NAPI_METHOD(db_clear) {
903
1261
  NAPI_ARGV(2);
904
- NAPI_DB_CONTEXT();
905
1262
 
906
- const auto reverse = BooleanProperty(env, argv[1], "reverse").value_or(false);
907
- const auto limit = Int32Property(env, argv[1], "limit").value_or(-1);
1263
+ Database* database;
1264
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
1265
+
1266
+ const auto options = argv[1];
1267
+ const auto reverse = BooleanProperty(env, options, "reverse").value_or(false);
1268
+ const auto limit = Int32Property(env, options, "limit").value_or(-1);
1269
+
1270
+ const auto lt = StringProperty(env, options, "lt");
1271
+ const auto lte = StringProperty(env, options, "lte");
1272
+ const auto gt = StringProperty(env, options, "gt");
1273
+ const auto gte = StringProperty(env, options, "gte");
1274
+ const auto column = GetColumnFamily(database, env, options);
908
1275
 
909
- const auto lt = StringProperty(env, argv[1], "lt");
910
- const auto lte = StringProperty(env, argv[1], "lte");
911
- const auto gt = StringProperty(env, argv[1], "gt");
912
- const auto gte = StringProperty(env, argv[1], "gte");
913
-
914
1276
  if (!reverse && limit == -1 && (lte || lt)) {
915
1277
  rocksdb::Slice begin;
916
1278
  if (gte) {
@@ -932,13 +1294,15 @@ NAPI_METHOD(db_clear) {
932
1294
  return ToError(env, rocksdb::Status::OK());
933
1295
  }
934
1296
 
935
- rocksdb::WriteOptions options;
936
- const auto status = database->db_->DeleteRange(options, database->db_->DefaultColumnFamily(), begin, end);
1297
+ rocksdb::WriteOptions writeOptions;
1298
+ const auto status = database->db_->DeleteRange(writeOptions, column, begin, end);
937
1299
  return ToError(env, status);
938
1300
  } else {
939
1301
  // TODO (perf): Use DeleteRange.
940
1302
 
941
- BaseIterator it(database, reverse, lt, lte, gt, gte, limit, false);
1303
+ std::shared_ptr<const rocksdb::Snapshot> snapshot(database->db_->GetSnapshot(),
1304
+ [=](const auto ptr) { database->db_->ReleaseSnapshot(ptr); });
1305
+ BaseIterator it(database, column, reverse, lt, lte, gt, gte, limit, false, snapshot);
942
1306
 
943
1307
  it.SeekToRange();
944
1308
 
@@ -946,7 +1310,7 @@ NAPI_METHOD(db_clear) {
946
1310
  const uint32_t hwm = 16 * 1024;
947
1311
 
948
1312
  rocksdb::WriteBatch batch;
949
- rocksdb::WriteOptions options;
1313
+ rocksdb::WriteOptions writeOptions;
950
1314
  rocksdb::Status status;
951
1315
 
952
1316
  while (true) {
@@ -954,7 +1318,7 @@ NAPI_METHOD(db_clear) {
954
1318
 
955
1319
  while (bytesRead <= hwm && it.Valid() && it.Increment()) {
956
1320
  const auto key = it.CurrentKey();
957
- batch.Delete(key);
1321
+ batch.Delete(column, key);
958
1322
  bytesRead += key.size();
959
1323
  it.Next();
960
1324
  }
@@ -964,7 +1328,7 @@ NAPI_METHOD(db_clear) {
964
1328
  break;
965
1329
  }
966
1330
 
967
- status = database->db_->Write(options, &batch);
1331
+ status = database->db_->Write(writeOptions, &batch);
968
1332
  if (!status.ok()) {
969
1333
  break;
970
1334
  }
@@ -980,7 +1344,9 @@ NAPI_METHOD(db_clear) {
980
1344
 
981
1345
  NAPI_METHOD(db_get_property) {
982
1346
  NAPI_ARGV(2);
983
- NAPI_DB_CONTEXT();
1347
+
1348
+ Database* database;
1349
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
984
1350
 
985
1351
  NapiSlice property;
986
1352
  NAPI_STATUS_THROWS(ToNapiSlice(env, argv[1], property));
@@ -996,7 +1362,9 @@ NAPI_METHOD(db_get_property) {
996
1362
 
997
1363
  NAPI_METHOD(iterator_init) {
998
1364
  NAPI_ARGV(2);
999
- NAPI_DB_CONTEXT();
1365
+
1366
+ Database* database;
1367
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
1000
1368
 
1001
1369
  const auto options = argv[1];
1002
1370
  const auto reverse = BooleanProperty(env, options, "reverse").value_or(false);
@@ -1013,8 +1381,13 @@ NAPI_METHOD(iterator_init) {
1013
1381
  const auto gt = StringProperty(env, options, "gt");
1014
1382
  const auto gte = StringProperty(env, options, "gte");
1015
1383
 
1016
- auto iterator = std::make_unique<Iterator>(database, reverse, keys, values, limit, lt, lte, gt, gte, fillCache,
1017
- keyAsBuffer, valueAsBuffer, highWaterMarkBytes);
1384
+ const auto column = GetColumnFamily(database, env, options);
1385
+
1386
+ std::shared_ptr<const rocksdb::Snapshot> snapshot(database->db_->GetSnapshot(),
1387
+ [=](const auto ptr) { database->db_->ReleaseSnapshot(ptr); });
1388
+
1389
+ auto iterator = std::make_unique<Iterator>(database, column, reverse, keys, values, limit, lt, lte, gt, gte,
1390
+ fillCache, keyAsBuffer, valueAsBuffer, highWaterMarkBytes, snapshot);
1018
1391
 
1019
1392
  napi_value result;
1020
1393
  NAPI_STATUS_THROWS(napi_create_external(env, iterator.get(), Finalize<Iterator>, iterator.get(), &result));
@@ -1028,7 +1401,9 @@ NAPI_METHOD(iterator_init) {
1028
1401
 
1029
1402
  NAPI_METHOD(iterator_seek) {
1030
1403
  NAPI_ARGV(2);
1031
- NAPI_ITERATOR_CONTEXT();
1404
+
1405
+ Iterator* iterator;
1406
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&iterator));
1032
1407
 
1033
1408
  NapiSlice target;
1034
1409
  NAPI_STATUS_THROWS(ToNapiSlice(env, argv[1], target));
@@ -1041,7 +1416,9 @@ NAPI_METHOD(iterator_seek) {
1041
1416
 
1042
1417
  NAPI_METHOD(iterator_close) {
1043
1418
  NAPI_ARGV(1);
1044
- NAPI_ITERATOR_CONTEXT();
1419
+
1420
+ Iterator* iterator;
1421
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&iterator));
1045
1422
 
1046
1423
  iterator->Detach(env);
1047
1424
  iterator->Close();
@@ -1049,6 +1426,20 @@ NAPI_METHOD(iterator_close) {
1049
1426
  return 0;
1050
1427
  }
1051
1428
 
1429
+ NAPI_METHOD(iterator_get_sequence) {
1430
+ NAPI_ARGV(1);
1431
+
1432
+ Iterator* iterator;
1433
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&iterator));
1434
+
1435
+ const auto seq = iterator->snapshot_->GetSequenceNumber();
1436
+
1437
+ napi_value result;
1438
+ NAPI_STATUS_THROWS(napi_create_bigint_int64(env, seq, &result));
1439
+
1440
+ return 0;
1441
+ }
1442
+
1052
1443
  struct NextWorker final : public Worker {
1053
1444
  NextWorker(napi_env env, Iterator* iterator, uint32_t size, napi_value callback)
1054
1445
  : Worker(env, iterator->database_, callback, "leveldown.iterator.next"), iterator_(iterator), size_(size) {}
@@ -1073,33 +1464,29 @@ struct NextWorker final : public Worker {
1073
1464
  if (iterator_->keys_ && iterator_->values_) {
1074
1465
  auto k = iterator_->CurrentKey();
1075
1466
  auto v = iterator_->CurrentValue();
1076
- cache_.push_back({});
1077
- cache_.back().PinSelf(k);
1078
- cache_.push_back({});
1079
- cache_.back().PinSelf(v);
1080
1467
  bytesRead += k.size() + v.size();
1468
+ cache_.push_back(k.ToString());
1469
+ cache_.push_back(v.ToString());
1081
1470
  } else if (iterator_->keys_) {
1082
1471
  auto k = iterator_->CurrentKey();
1083
- cache_.push_back({});
1084
- cache_.back().PinSelf(k);
1085
- cache_.push_back({});
1086
- // no value
1087
1472
  bytesRead += k.size();
1473
+ cache_.push_back(k.ToString());
1474
+ cache_.push_back(std::nullopt);
1088
1475
  } else if (iterator_->values_) {
1089
1476
  auto v = iterator_->CurrentValue();
1090
- cache_.push_back({});
1091
- // no key
1092
- cache_.push_back({});
1093
- cache_.back().PinSelf(v);
1094
1477
  bytesRead += v.size();
1478
+ cache_.push_back(std::nullopt);
1479
+ cache_.push_back(v.ToString());
1095
1480
  }
1096
1481
 
1097
1482
  if (bytesRead > iterator_->highWaterMarkBytes_ || cache_.size() / 2 >= size_) {
1098
- finished_ = true;
1483
+ finished_ = false;
1099
1484
  return rocksdb::Status::OK();
1100
1485
  }
1101
1486
  }
1102
1487
 
1488
+ finished_ = true;
1489
+
1103
1490
  return iterator_->Status();
1104
1491
  }
1105
1492
 
@@ -1108,15 +1495,15 @@ struct NextWorker final : public Worker {
1108
1495
  napi_value result;
1109
1496
  NAPI_STATUS_RETURN(napi_create_array_with_length(env, size, &result));
1110
1497
 
1111
- for (size_t idx = 0; idx < cache_.size(); idx += 2) {
1498
+ for (size_t n = 0; n < size; n += 2) {
1112
1499
  napi_value key;
1113
1500
  napi_value val;
1114
1501
 
1115
- NAPI_STATUS_RETURN(Convert(env, std::move(cache_[idx + 0]), iterator_->keyAsBuffer_, key));
1116
- NAPI_STATUS_RETURN(Convert(env, std::move(cache_[idx + 1]), iterator_->valueAsBuffer_, val));
1502
+ NAPI_STATUS_RETURN(Convert(env, std::move(cache_[n + 0]), iterator_->keyAsBuffer_, key));
1503
+ NAPI_STATUS_RETURN(Convert(env, std::move(cache_[n + 1]), iterator_->valueAsBuffer_, val));
1117
1504
 
1118
- NAPI_STATUS_RETURN(napi_set_element(env, result, static_cast<int>(idx + 0), key));
1119
- NAPI_STATUS_RETURN(napi_set_element(env, result, static_cast<int>(idx + 1), val));
1505
+ NAPI_STATUS_RETURN(napi_set_element(env, result, static_cast<int>(n + 0), key));
1506
+ NAPI_STATUS_RETURN(napi_set_element(env, result, static_cast<int>(n + 1), val));
1120
1507
  }
1121
1508
 
1122
1509
  cache_.clear();
@@ -1124,20 +1511,22 @@ struct NextWorker final : public Worker {
1124
1511
  napi_value argv[3];
1125
1512
  NAPI_STATUS_RETURN(napi_get_null(env, &argv[0]));
1126
1513
  argv[1] = result;
1127
- NAPI_STATUS_RETURN(napi_get_boolean(env, !finished_, &argv[2]));
1514
+ NAPI_STATUS_RETURN(napi_get_boolean(env, finished_, &argv[2]));
1128
1515
  return CallFunction(env, callback, 3, argv);
1129
1516
  }
1130
1517
 
1131
1518
  private:
1132
- std::vector<rocksdb::PinnableSlice> cache_;
1519
+ std::vector<std::optional<std::string>> cache_;
1133
1520
  Iterator* iterator_ = nullptr;
1134
1521
  uint32_t size_ = 0;
1135
- bool finished_ = false;
1522
+ bool finished_ = true;
1136
1523
  };
1137
1524
 
1138
1525
  NAPI_METHOD(iterator_nextv) {
1139
1526
  NAPI_ARGV(3);
1140
- NAPI_ITERATOR_CONTEXT();
1527
+
1528
+ Iterator* iterator;
1529
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&iterator));
1141
1530
 
1142
1531
  uint32_t size;
1143
1532
  NAPI_STATUS_THROWS(napi_get_value_uint32(env, argv[1], &size));
@@ -1152,9 +1541,12 @@ NAPI_METHOD(iterator_nextv) {
1152
1541
 
1153
1542
  NAPI_METHOD(batch_do) {
1154
1543
  NAPI_ARGV(3);
1155
- NAPI_DB_CONTEXT();
1544
+
1545
+ Database* database;
1546
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
1156
1547
 
1157
1548
  const auto operations = argv[1];
1549
+ const auto options = argv[2];
1158
1550
 
1159
1551
  rocksdb::WriteBatch batch;
1160
1552
 
@@ -1168,26 +1560,30 @@ NAPI_METHOD(batch_do) {
1168
1560
  NapiSlice type;
1169
1561
  NAPI_STATUS_THROWS(ToNapiSlice(env, GetProperty(env, element, "type"), type));
1170
1562
 
1563
+ const auto column = GetColumnFamily(database, env, element);
1564
+
1171
1565
  if (type == "del") {
1172
1566
  NapiSlice key;
1173
1567
  NAPI_STATUS_THROWS(ToNapiSlice(env, GetProperty(env, element, "key"), key));
1174
- batch.Delete(key);
1568
+ batch.Delete(column, key);
1175
1569
  } else if (type == "put") {
1176
1570
  NapiSlice key;
1177
1571
  NAPI_STATUS_THROWS(ToNapiSlice(env, GetProperty(env, element, "key"), key));
1178
1572
  NapiSlice value;
1179
1573
  NAPI_STATUS_THROWS(ToNapiSlice(env, GetProperty(env, element, "value"), value));
1180
- batch.Put(key, value);
1574
+ batch.Put(column, key, value);
1181
1575
  }
1182
1576
  }
1183
1577
 
1184
- rocksdb::WriteOptions options;
1185
- return ToError(env, database->db_->Write(options, &batch));
1578
+ rocksdb::WriteOptions writeOptions;
1579
+ return ToError(env, database->db_->Write(writeOptions, &batch));
1186
1580
  }
1187
1581
 
1188
1582
  NAPI_METHOD(batch_init) {
1189
1583
  NAPI_ARGV(1);
1190
- NAPI_DB_CONTEXT();
1584
+
1585
+ Database* database;
1586
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
1191
1587
 
1192
1588
  auto batch = new rocksdb::WriteBatch();
1193
1589
 
@@ -1197,35 +1593,53 @@ NAPI_METHOD(batch_init) {
1197
1593
  }
1198
1594
 
1199
1595
  NAPI_METHOD(batch_put) {
1200
- NAPI_ARGV(3);
1201
- NAPI_BATCH_CONTEXT();
1596
+ NAPI_ARGV(5);
1597
+
1598
+ Database* database;
1599
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
1600
+
1601
+ rocksdb::WriteBatch* batch;
1602
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[1], (void**)(&batch)));
1202
1603
 
1203
1604
  NapiSlice key;
1204
- NAPI_STATUS_THROWS(ToNapiSlice(env, argv[1], key));
1605
+ NAPI_STATUS_THROWS(ToNapiSlice(env, argv[2], key));
1205
1606
 
1206
1607
  NapiSlice val;
1207
- NAPI_STATUS_THROWS(ToNapiSlice(env, argv[2], val));
1608
+ NAPI_STATUS_THROWS(ToNapiSlice(env, argv[3], val));
1609
+
1610
+ const auto options = argv[4];
1611
+ const auto column = GetColumnFamily(database, env, options);
1208
1612
 
1209
- batch->Put(key, val);
1613
+ batch->Put(column, key, val);
1210
1614
 
1211
1615
  return 0;
1212
1616
  }
1213
1617
 
1214
1618
  NAPI_METHOD(batch_del) {
1215
- NAPI_ARGV(2);
1216
- NAPI_BATCH_CONTEXT();
1619
+ NAPI_ARGV(4);
1620
+
1621
+ Database* database;
1622
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
1623
+
1624
+ rocksdb::WriteBatch* batch;
1625
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[1], reinterpret_cast<void**>(&batch)));
1217
1626
 
1218
1627
  NapiSlice key;
1219
- NAPI_STATUS_THROWS(ToNapiSlice(env, argv[1], key));
1628
+ NAPI_STATUS_THROWS(ToNapiSlice(env, argv[2], key));
1220
1629
 
1221
- batch->Delete(key);
1630
+ const auto options = argv[3];
1631
+ const auto column = GetColumnFamily(database, env, options);
1632
+
1633
+ batch->Delete(column, key);
1222
1634
 
1223
1635
  return 0;
1224
1636
  }
1225
1637
 
1226
1638
  NAPI_METHOD(batch_clear) {
1227
- NAPI_ARGV(1);
1228
- NAPI_BATCH_CONTEXT();
1639
+ NAPI_ARGV(2);
1640
+
1641
+ rocksdb::WriteBatch* batch;
1642
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[1], reinterpret_cast<void**>(&batch)));
1229
1643
 
1230
1644
  batch->Clear();
1231
1645
 
@@ -1234,13 +1648,15 @@ NAPI_METHOD(batch_clear) {
1234
1648
 
1235
1649
  NAPI_METHOD(batch_write) {
1236
1650
  NAPI_ARGV(3);
1237
- NAPI_DB_CONTEXT();
1651
+
1652
+ Database* database;
1653
+ NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], (void**)&database));
1238
1654
 
1239
1655
  rocksdb::WriteBatch* batch;
1240
1656
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[1], reinterpret_cast<void**>(&batch)));
1241
1657
 
1242
- rocksdb::WriteOptions options;
1243
- return ToError(env, database->db_->Write(options, batch));
1658
+ rocksdb::WriteOptions writeOptions;
1659
+ return ToError(env, database->db_->Write(writeOptions, batch));
1244
1660
  }
1245
1661
 
1246
1662
  NAPI_INIT() {
@@ -1258,6 +1674,11 @@ NAPI_INIT() {
1258
1674
  NAPI_EXPORT_FUNCTION(iterator_seek);
1259
1675
  NAPI_EXPORT_FUNCTION(iterator_close);
1260
1676
  NAPI_EXPORT_FUNCTION(iterator_nextv);
1677
+ NAPI_EXPORT_FUNCTION(iterator_get_sequence);
1678
+
1679
+ NAPI_EXPORT_FUNCTION(updates_init);
1680
+ NAPI_EXPORT_FUNCTION(updates_close);
1681
+ NAPI_EXPORT_FUNCTION(updates_next);
1261
1682
 
1262
1683
  NAPI_EXPORT_FUNCTION(batch_do);
1263
1684
  NAPI_EXPORT_FUNCTION(batch_init);