@nxtedition/rocksdb 16.0.6 → 16.0.9
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 +215 -5
- package/index.js +10 -0
- package/iterator.js +57 -10
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/@nxtedition+rocksdb.node +0 -0
- package/prebuilds/linux-x64/@nxtedition+rocksdb.node +0 -0
- package/release.sh +29 -0
- package/write-buffer-manager.js +21 -0
package/binding.cc
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
#include <rocksdb/status.h>
|
|
18
18
|
#include <rocksdb/table.h>
|
|
19
19
|
#include <rocksdb/write_batch.h>
|
|
20
|
+
#include <rocksdb/write_buffer_manager.h>
|
|
20
21
|
|
|
21
22
|
#include <re2/re2.h>
|
|
22
23
|
|
|
@@ -29,6 +30,19 @@
|
|
|
29
30
|
#include <thread>
|
|
30
31
|
#include <vector>
|
|
31
32
|
|
|
33
|
+
#ifdef __linux__
|
|
34
|
+
#include <sys/syscall.h>
|
|
35
|
+
#include <unistd.h>
|
|
36
|
+
|
|
37
|
+
#include <cerrno>
|
|
38
|
+
|
|
39
|
+
// Older libc headers may lack the SYS_ alias for io_uring_setup even though
|
|
40
|
+
// the kernel number (__NR_) is available — keep the Linux probe a boolean.
|
|
41
|
+
#if !defined(SYS_io_uring_setup) && defined(__NR_io_uring_setup)
|
|
42
|
+
#define SYS_io_uring_setup __NR_io_uring_setup
|
|
43
|
+
#endif
|
|
44
|
+
#endif
|
|
45
|
+
|
|
32
46
|
#include "max_rev_operator.h"
|
|
33
47
|
#include "util.h"
|
|
34
48
|
|
|
@@ -334,6 +348,12 @@ struct BaseIterator : public Closable {
|
|
|
334
348
|
const int limit,
|
|
335
349
|
rocksdb::ReadOptions readOptions = {})
|
|
336
350
|
: database_(database), column_(column), reverse_(reverse), limit_(limit) {
|
|
351
|
+
// TODO (correctness): the +'\0' byte-successor trick below converts
|
|
352
|
+
// inclusive/exclusive bounds correctly only under bytewise ordering. With a
|
|
353
|
+
// custom CF comparator (InitOptions "comparator", e.g.
|
|
354
|
+
// rocksdb.ReverseBytewiseComparator) RocksDB applies these bounds with that
|
|
355
|
+
// comparator, silently inverting the lte/gt boundary semantics. Seek()'s
|
|
356
|
+
// manual bound check below uses raw bytewise Slice::compare as well.
|
|
337
357
|
if (lte) {
|
|
338
358
|
upper_bound_ = rocksdb::PinnableSlice();
|
|
339
359
|
*upper_bound_->GetSelf() = std::move(*lte) + '\0';
|
|
@@ -446,7 +466,17 @@ struct BaseIterator : public Closable {
|
|
|
446
466
|
// otherwise an iterator that already yielded `limit` rows returns nothing
|
|
447
467
|
// after a refresh even though every other piece of state was reset.
|
|
448
468
|
count_ = 0;
|
|
449
|
-
|
|
469
|
+
ROCKS_STATUS_RETURN(iterator_->Refresh());
|
|
470
|
+
// rocksdb::Iterator::Refresh invalidates the iterator (a Seek* is required
|
|
471
|
+
// before use), so re-establish the starting position like the constructor
|
|
472
|
+
// does — otherwise the next read sees Valid()==false and reports an empty
|
|
473
|
+
// database.
|
|
474
|
+
if (reverse_) {
|
|
475
|
+
iterator_->SeekToLast();
|
|
476
|
+
} else {
|
|
477
|
+
iterator_->SeekToFirst();
|
|
478
|
+
}
|
|
479
|
+
return iterator_->status();
|
|
450
480
|
}
|
|
451
481
|
|
|
452
482
|
Database* database_;
|
|
@@ -701,9 +731,9 @@ class Iterator final : public BaseIterator {
|
|
|
701
731
|
v.PinSelf(CurrentValue());
|
|
702
732
|
state.bytes += v.size();
|
|
703
733
|
state.values.push_back(std::move(v));
|
|
704
|
-
} else {
|
|
705
|
-
assert(false);
|
|
706
734
|
}
|
|
735
|
+
// keys:false + values:false is valid per abstract-level: rows still
|
|
736
|
+
// count, each entry surfaces as [undefined, undefined].
|
|
707
737
|
state.count += 1;
|
|
708
738
|
}
|
|
709
739
|
|
|
@@ -733,7 +763,8 @@ class Iterator final : public BaseIterator {
|
|
|
733
763
|
NAPI_STATUS_RETURN(napi_get_undefined(env, &key));
|
|
734
764
|
NAPI_STATUS_RETURN(Convert(env, std::move(state.values[n]), valueEncoding_, val, unsafe_));
|
|
735
765
|
} else {
|
|
736
|
-
|
|
766
|
+
NAPI_STATUS_RETURN(napi_get_undefined(env, &key));
|
|
767
|
+
NAPI_STATUS_RETURN(napi_get_undefined(env, &val));
|
|
737
768
|
}
|
|
738
769
|
|
|
739
770
|
NAPI_STATUS_RETURN(napi_set_element(env, rows, n * 2 + 0, key));
|
|
@@ -827,7 +858,8 @@ class Iterator final : public BaseIterator {
|
|
|
827
858
|
NAPI_STATUS_THROWS(napi_get_undefined(env, &key));
|
|
828
859
|
NAPI_STATUS_THROWS(Convert(env, CurrentValue(), valueEncoding_, val, unsafe_));
|
|
829
860
|
} else {
|
|
830
|
-
|
|
861
|
+
NAPI_STATUS_THROWS(napi_get_undefined(env, &key));
|
|
862
|
+
NAPI_STATUS_THROWS(napi_get_undefined(env, &val));
|
|
831
863
|
}
|
|
832
864
|
|
|
833
865
|
NAPI_STATUS_THROWS(napi_set_element(env, rows, idx++, key));
|
|
@@ -904,6 +936,10 @@ NAPI_METHOD(db_init) {
|
|
|
904
936
|
int64_t value;
|
|
905
937
|
bool lossless;
|
|
906
938
|
NAPI_STATUS_THROWS(napi_get_value_bigint_int64(env, argv[0], &value, &lossless));
|
|
939
|
+
if (!lossless) {
|
|
940
|
+
napi_throw_error(env, nullptr, "invalid database handle");
|
|
941
|
+
return NULL;
|
|
942
|
+
}
|
|
907
943
|
|
|
908
944
|
database = reinterpret_cast<Database*>(value);
|
|
909
945
|
NAPI_STATUS_THROWS(napi_create_external(env, database, nullptr, nullptr, &result));
|
|
@@ -1092,6 +1128,10 @@ napi_status InitOptions(napi_env env, T& columnOptions, const U& options) {
|
|
|
1092
1128
|
|
|
1093
1129
|
NAPI_STATUS_RETURN(GetProperty(env, options, "optimizeFiltersForHits", columnOptions.optimize_filters_for_hits));
|
|
1094
1130
|
NAPI_STATUS_RETURN(GetProperty(env, options, "periodicCompactionSeconds", columnOptions.periodic_compaction_seconds));
|
|
1131
|
+
// memtable_huge_page_size is a column-family option: when the DB is opened
|
|
1132
|
+
// with explicit column descriptors the copy read into dbOptions in db_open is
|
|
1133
|
+
// sliced away, so it must be settable per column to take effect at all.
|
|
1134
|
+
NAPI_STATUS_RETURN(GetProperty(env, options, "memTableHugePageSize", columnOptions.memtable_huge_page_size));
|
|
1095
1135
|
|
|
1096
1136
|
// Compat
|
|
1097
1137
|
NAPI_STATUS_RETURN(GetProperty(env, options, "enableBlobFiles", columnOptions.enable_blob_files));
|
|
@@ -1131,12 +1171,18 @@ napi_status InitOptions(napi_env env, T& columnOptions, const U& options) {
|
|
|
1131
1171
|
bool lossless;
|
|
1132
1172
|
int64_t ptr;
|
|
1133
1173
|
NAPI_STATUS_RETURN(napi_get_value_bigint_int64(env, handleValue, &ptr, &lossless));
|
|
1174
|
+
if (!lossless) {
|
|
1175
|
+
return napi_invalid_arg;
|
|
1176
|
+
}
|
|
1134
1177
|
|
|
1135
1178
|
cache = *reinterpret_cast<std::shared_ptr<rocksdb::Cache>*>(ptr);
|
|
1136
1179
|
} else if (cacheType == napi_bigint) {
|
|
1137
1180
|
bool lossless;
|
|
1138
1181
|
int64_t ptr;
|
|
1139
1182
|
NAPI_STATUS_RETURN(napi_get_value_bigint_int64(env, cacheValue, &ptr, &lossless));
|
|
1183
|
+
if (!lossless) {
|
|
1184
|
+
return napi_invalid_arg;
|
|
1185
|
+
}
|
|
1140
1186
|
|
|
1141
1187
|
cache = *reinterpret_cast<std::shared_ptr<rocksdb::Cache>*>(ptr);
|
|
1142
1188
|
} else if (cacheType != napi_undefined && cacheType != napi_null) {
|
|
@@ -1361,6 +1407,14 @@ NAPI_METHOD(db_open) {
|
|
|
1361
1407
|
NAPI_STATUS_THROWS(GetProperty(env, options, "parallelism", parallelism));
|
|
1362
1408
|
dbOptions.IncreaseParallelism(parallelism);
|
|
1363
1409
|
|
|
1410
|
+
// IncreaseParallelism sizes the (process-wide) Env LOW pool to `parallelism`
|
|
1411
|
+
// but pins the HIGH pool — where every flush of every DB sharing the default
|
|
1412
|
+
// Env runs — at a single thread, so flushes across DBs serialize behind one
|
|
1413
|
+
// thread. Both pools are process-wide: the last opened DB's value wins.
|
|
1414
|
+
int flushParallelism = std::max(1, parallelism / 4);
|
|
1415
|
+
NAPI_STATUS_THROWS(GetProperty(env, options, "flushParallelism", flushParallelism));
|
|
1416
|
+
dbOptions.env->SetBackgroundThreads(std::max(1, flushParallelism), rocksdb::Env::HIGH);
|
|
1417
|
+
|
|
1364
1418
|
NAPI_STATUS_THROWS(GetProperty(env, options, "walDir", dbOptions.wal_dir));
|
|
1365
1419
|
|
|
1366
1420
|
// 64-bit inputs: walTTL is in ms and walSizeLimit in bytes, so a 32-bit type
|
|
@@ -1427,6 +1481,34 @@ NAPI_METHOD(db_open) {
|
|
|
1427
1481
|
|
|
1428
1482
|
NAPI_STATUS_THROWS(GetProperty(env, options, "writeBufferSize", dbOptions.db_write_buffer_size));
|
|
1429
1483
|
|
|
1484
|
+
{
|
|
1485
|
+
napi_value wbmValue;
|
|
1486
|
+
NAPI_STATUS_THROWS(napi_get_named_property(env, options, "writeBufferManager", &wbmValue));
|
|
1487
|
+
|
|
1488
|
+
napi_valuetype wbmType;
|
|
1489
|
+
NAPI_STATUS_THROWS(napi_typeof(env, wbmValue, &wbmType));
|
|
1490
|
+
|
|
1491
|
+
if (wbmType == napi_object || wbmType == napi_bigint) {
|
|
1492
|
+
napi_value handleValue = wbmValue;
|
|
1493
|
+
if (wbmType == napi_object) {
|
|
1494
|
+
NAPI_STATUS_THROWS(napi_get_named_property(env, wbmValue, "handle", &handleValue));
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
bool lossless;
|
|
1498
|
+
int64_t ptr;
|
|
1499
|
+
NAPI_STATUS_THROWS(napi_get_value_bigint_int64(env, handleValue, &ptr, &lossless));
|
|
1500
|
+
if (!lossless) {
|
|
1501
|
+
napi_throw_error(env, nullptr, "invalid writeBufferManager handle");
|
|
1502
|
+
return NULL;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
dbOptions.write_buffer_manager = *reinterpret_cast<std::shared_ptr<rocksdb::WriteBufferManager>*>(ptr);
|
|
1506
|
+
} else if (wbmType != napi_undefined && wbmType != napi_null) {
|
|
1507
|
+
napi_throw_error(env, nullptr, "invalid writeBufferManager");
|
|
1508
|
+
return NULL;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1430
1512
|
NAPI_STATUS_THROWS(GetProperty(env, options, "manualWALFlush", dbOptions.manual_wal_flush));
|
|
1431
1513
|
NAPI_STATUS_THROWS(GetProperty(env, options, "walManualFlush", dbOptions.manual_wal_flush));
|
|
1432
1514
|
|
|
@@ -2444,6 +2526,128 @@ NAPI_METHOD(cache_get_handle) {
|
|
|
2444
2526
|
return result;
|
|
2445
2527
|
}
|
|
2446
2528
|
|
|
2529
|
+
NAPI_METHOD(write_buffer_manager_init) {
|
|
2530
|
+
NAPI_ARGV(1);
|
|
2531
|
+
|
|
2532
|
+
size_t bufferSize = 256 * 1024 * 1024; // 256 MiB
|
|
2533
|
+
NAPI_STATUS_THROWS(GetProperty(env, argv[0], "bufferSize", bufferSize));
|
|
2534
|
+
|
|
2535
|
+
bool allowStall = false;
|
|
2536
|
+
NAPI_STATUS_THROWS(GetProperty(env, argv[0], "allowStall", allowStall));
|
|
2537
|
+
|
|
2538
|
+
std::shared_ptr<rocksdb::Cache> cache;
|
|
2539
|
+
{
|
|
2540
|
+
napi_value cacheValue;
|
|
2541
|
+
NAPI_STATUS_THROWS(napi_get_named_property(env, argv[0], "cache", &cacheValue));
|
|
2542
|
+
|
|
2543
|
+
napi_valuetype cacheType;
|
|
2544
|
+
NAPI_STATUS_THROWS(napi_typeof(env, cacheValue, &cacheType));
|
|
2545
|
+
|
|
2546
|
+
if (cacheType == napi_object || cacheType == napi_bigint) {
|
|
2547
|
+
napi_value handleValue = cacheValue;
|
|
2548
|
+
if (cacheType == napi_object) {
|
|
2549
|
+
NAPI_STATUS_THROWS(napi_get_named_property(env, cacheValue, "handle", &handleValue));
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
bool lossless;
|
|
2553
|
+
int64_t ptr;
|
|
2554
|
+
NAPI_STATUS_THROWS(napi_get_value_bigint_int64(env, handleValue, &ptr, &lossless));
|
|
2555
|
+
if (!lossless) {
|
|
2556
|
+
napi_throw_error(env, nullptr, "invalid cache handle");
|
|
2557
|
+
return NULL;
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
cache = *reinterpret_cast<std::shared_ptr<rocksdb::Cache>*>(ptr);
|
|
2561
|
+
} else if (cacheType != napi_undefined && cacheType != napi_null) {
|
|
2562
|
+
napi_throw_error(env, nullptr, "invalid cache");
|
|
2563
|
+
return NULL;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
auto wbm = new std::shared_ptr<rocksdb::WriteBufferManager>(
|
|
2568
|
+
std::make_shared<rocksdb::WriteBufferManager>(bufferSize, cache, allowStall));
|
|
2569
|
+
|
|
2570
|
+
napi_value result;
|
|
2571
|
+
NAPI_STATUS_THROWS(
|
|
2572
|
+
napi_create_external(env, wbm, Finalize<std::shared_ptr<rocksdb::WriteBufferManager>>, wbm, &result));
|
|
2573
|
+
|
|
2574
|
+
return result;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
NAPI_METHOD(write_buffer_manager_get_handle) {
|
|
2578
|
+
NAPI_ARGV(1);
|
|
2579
|
+
|
|
2580
|
+
std::shared_ptr<rocksdb::WriteBufferManager>* wbm;
|
|
2581
|
+
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&wbm)));
|
|
2582
|
+
|
|
2583
|
+
napi_value result;
|
|
2584
|
+
NAPI_STATUS_THROWS(napi_create_bigint_int64(env, reinterpret_cast<intptr_t>(wbm), &result));
|
|
2585
|
+
|
|
2586
|
+
return result;
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
NAPI_METHOD(write_buffer_manager_get_usage) {
|
|
2590
|
+
NAPI_ARGV(1);
|
|
2591
|
+
|
|
2592
|
+
std::shared_ptr<rocksdb::WriteBufferManager>* wbm;
|
|
2593
|
+
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&wbm)));
|
|
2594
|
+
|
|
2595
|
+
napi_value result;
|
|
2596
|
+
NAPI_STATUS_THROWS(napi_create_object(env, &result));
|
|
2597
|
+
|
|
2598
|
+
napi_value memoryUsage;
|
|
2599
|
+
NAPI_STATUS_THROWS(napi_create_double(env, static_cast<double>((*wbm)->memory_usage()), &memoryUsage));
|
|
2600
|
+
NAPI_STATUS_THROWS(napi_set_named_property(env, result, "memoryUsage", memoryUsage));
|
|
2601
|
+
|
|
2602
|
+
napi_value mutableMemoryUsage;
|
|
2603
|
+
NAPI_STATUS_THROWS(
|
|
2604
|
+
napi_create_double(env, static_cast<double>((*wbm)->mutable_memtable_memory_usage()), &mutableMemoryUsage));
|
|
2605
|
+
NAPI_STATUS_THROWS(napi_set_named_property(env, result, "mutableMemoryUsage", mutableMemoryUsage));
|
|
2606
|
+
|
|
2607
|
+
napi_value bufferSize;
|
|
2608
|
+
NAPI_STATUS_THROWS(napi_create_double(env, static_cast<double>((*wbm)->buffer_size()), &bufferSize));
|
|
2609
|
+
NAPI_STATUS_THROWS(napi_set_named_property(env, result, "bufferSize", bufferSize));
|
|
2610
|
+
|
|
2611
|
+
return result;
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
// Probes whether io_uring is actually usable in this process: RocksDB gates its
|
|
2615
|
+
// async MultiGet / prefetch I/O on io_uring_setup succeeding at runtime and
|
|
2616
|
+
// falls back to serial reads SILENTLY when the syscall is denied (seccomp — the
|
|
2617
|
+
// default Docker/containerd profiles since late 2023 — or the
|
|
2618
|
+
// kernel.io_uring_disabled sysctl) or missing (ENOSYS). io_uring_setup(0, NULL)
|
|
2619
|
+
// never succeeds; a functional kernel rejects the arguments (EINVAL/EFAULT)
|
|
2620
|
+
// while a blocked one fails with EPERM/EACCES/ENOSYS before looking at them.
|
|
2621
|
+
NAPI_METHOD(io_uring_available) {
|
|
2622
|
+
#if defined(__linux__) && defined(SYS_io_uring_setup)
|
|
2623
|
+
errno = 0;
|
|
2624
|
+
const long rc = syscall(SYS_io_uring_setup, 0, nullptr);
|
|
2625
|
+
const bool available = rc >= 0 || (errno != ENOSYS && errno != EPERM && errno != EACCES);
|
|
2626
|
+
if (rc >= 0) {
|
|
2627
|
+
close(static_cast<int>(rc));
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
napi_value result;
|
|
2631
|
+
NAPI_STATUS_THROWS(napi_get_boolean(env, available, &result));
|
|
2632
|
+
|
|
2633
|
+
return result;
|
|
2634
|
+
#elif defined(__linux__)
|
|
2635
|
+
// Built without any syscall number for io_uring_setup (pre-io_uring-era
|
|
2636
|
+
// headers): this binary cannot use io_uring regardless of the running
|
|
2637
|
+
// kernel, so report it unavailable — the Linux contract stays boolean.
|
|
2638
|
+
napi_value result;
|
|
2639
|
+
NAPI_STATUS_THROWS(napi_get_boolean(env, false, &result));
|
|
2640
|
+
|
|
2641
|
+
return result;
|
|
2642
|
+
#else
|
|
2643
|
+
// Not applicable on this platform.
|
|
2644
|
+
napi_value result;
|
|
2645
|
+
NAPI_STATUS_THROWS(napi_get_null(env, &result));
|
|
2646
|
+
|
|
2647
|
+
return result;
|
|
2648
|
+
#endif
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2447
2651
|
NAPI_INIT() {
|
|
2448
2652
|
NAPI_EXPORT_FUNCTION(db_init);
|
|
2449
2653
|
NAPI_EXPORT_FUNCTION(db_open);
|
|
@@ -2486,4 +2690,10 @@ NAPI_INIT() {
|
|
|
2486
2690
|
|
|
2487
2691
|
NAPI_EXPORT_FUNCTION(cache_init);
|
|
2488
2692
|
NAPI_EXPORT_FUNCTION(cache_get_handle);
|
|
2693
|
+
|
|
2694
|
+
NAPI_EXPORT_FUNCTION(write_buffer_manager_init);
|
|
2695
|
+
NAPI_EXPORT_FUNCTION(write_buffer_manager_get_handle);
|
|
2696
|
+
NAPI_EXPORT_FUNCTION(write_buffer_manager_get_usage);
|
|
2697
|
+
|
|
2698
|
+
NAPI_EXPORT_FUNCTION(io_uring_available);
|
|
2489
2699
|
}
|
package/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const ModuleError = require('module-error')
|
|
|
6
6
|
const binding = require('./binding')
|
|
7
7
|
const { ChainedBatch } = require('./chained-batch')
|
|
8
8
|
const { RocksCache } = require('./cache')
|
|
9
|
+
const { RocksWriteBufferManager } = require('./write-buffer-manager')
|
|
9
10
|
const { Iterator } = require('./iterator')
|
|
10
11
|
const fs = require('node:fs')
|
|
11
12
|
const assert = require('node:assert')
|
|
@@ -414,3 +415,12 @@ class RocksLevel extends AbstractLevel {
|
|
|
414
415
|
|
|
415
416
|
exports.RocksLevel = RocksLevel
|
|
416
417
|
exports.RocksCache = RocksCache
|
|
418
|
+
exports.RocksWriteBufferManager = RocksWriteBufferManager
|
|
419
|
+
|
|
420
|
+
// null on platforms where io_uring does not apply (non-Linux); boolean on
|
|
421
|
+
// Linux, where `false` means RocksDB's async_io silently degrades to serial
|
|
422
|
+
// reads (seccomp, kernel.io_uring_disabled, a kernel without io_uring, or a
|
|
423
|
+
// binary built without an io_uring syscall number).
|
|
424
|
+
exports.ioUringAvailable = function ioUringAvailable () {
|
|
425
|
+
return binding.io_uring_available()
|
|
426
|
+
}
|
package/iterator.js
CHANGED
|
@@ -16,6 +16,7 @@ const kFirst = Symbol('first')
|
|
|
16
16
|
const kPosition = Symbol('position')
|
|
17
17
|
const kBusy = Symbol('busy')
|
|
18
18
|
const kPendingClose = Symbol('pendingClose')
|
|
19
|
+
const kHasFilter = Symbol('hasFilter')
|
|
19
20
|
|
|
20
21
|
const kEmpty = Object.freeze([])
|
|
21
22
|
|
|
@@ -32,6 +33,7 @@ class Iterator extends AbstractIterator {
|
|
|
32
33
|
this[kDB] = db
|
|
33
34
|
this[kBusy] = false
|
|
34
35
|
this[kPendingClose] = null
|
|
36
|
+
this[kHasFilter] = options.keyFilter != null || options.valueFilter != null
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
[Symbol.asyncDispose] () {
|
|
@@ -85,15 +87,41 @@ class Iterator extends AbstractIterator {
|
|
|
85
87
|
const size = this[kFirst] ? 1 : 1000
|
|
86
88
|
this[kFirst] = false
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
if (this[kHasFilter]) {
|
|
91
|
+
try {
|
|
92
|
+
this[kDB][kRef]()
|
|
93
|
+
this[kBusy] = true
|
|
94
|
+
binding.iterator_nextv(this[kContext], size, null, (err, result) => {
|
|
95
|
+
this[kBusy] = false
|
|
96
|
+
this[kDB][kUnref]()
|
|
97
|
+
|
|
98
|
+
if (err) {
|
|
99
|
+
callback(err)
|
|
100
|
+
} else {
|
|
101
|
+
this[kCache] = result.rows
|
|
102
|
+
this[kFinished] = result.finished
|
|
103
|
+
this[kPosition] = 0
|
|
104
|
+
this._next(callback)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this._flushPendingClose()
|
|
108
|
+
})
|
|
109
|
+
} catch (err) {
|
|
110
|
+
this[kBusy] = false
|
|
111
|
+
this[kDB][kUnref]()
|
|
112
|
+
process.nextTick(callback, err)
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
try {
|
|
116
|
+
const { rows, finished } = binding.iterator_nextv_sync(this[kContext], size, null)
|
|
117
|
+
this[kCache] = rows
|
|
118
|
+
this[kFinished] = finished
|
|
119
|
+
this[kPosition] = 0
|
|
120
|
+
|
|
121
|
+
setImmediate(() => this._next(callback))
|
|
122
|
+
} catch (err) {
|
|
123
|
+
process.nextTick(callback, err)
|
|
124
|
+
}
|
|
97
125
|
}
|
|
98
126
|
}
|
|
99
127
|
|
|
@@ -189,10 +217,25 @@ class Iterator extends AbstractIterator {
|
|
|
189
217
|
return callback[kPromise]
|
|
190
218
|
}
|
|
191
219
|
|
|
220
|
+
_nextvCached (size) {
|
|
221
|
+
const end = Math.min(this[kCache].length, this[kPosition] + size * 2)
|
|
222
|
+
const rows = this[kCache].slice(this[kPosition], end)
|
|
223
|
+
this[kPosition] = end
|
|
224
|
+
|
|
225
|
+
const finished = this[kFinished] && this[kPosition] >= this[kCache].length
|
|
226
|
+
const limited = !finished && rows.length >= size * 2
|
|
227
|
+
|
|
228
|
+
return { rows, finished, limited }
|
|
229
|
+
}
|
|
230
|
+
|
|
192
231
|
_nextvSync (size, options) {
|
|
193
232
|
assert(this[kContext])
|
|
194
233
|
assert(!this[kBusy])
|
|
195
234
|
|
|
235
|
+
if (this[kPosition] < this[kCache].length) {
|
|
236
|
+
return this._nextvCached(size)
|
|
237
|
+
}
|
|
238
|
+
|
|
196
239
|
if (this[kFinished]) {
|
|
197
240
|
return { rows: [], finished: true }
|
|
198
241
|
}
|
|
@@ -210,7 +253,9 @@ class Iterator extends AbstractIterator {
|
|
|
210
253
|
callback = fromCallback(callback, kPromise)
|
|
211
254
|
|
|
212
255
|
try {
|
|
213
|
-
if (this[
|
|
256
|
+
if (this[kPosition] < this[kCache].length) {
|
|
257
|
+
process.nextTick(callback, null, this._nextvCached(size))
|
|
258
|
+
} else if (this[kFinished]) {
|
|
214
259
|
process.nextTick(callback, null, { rows: [], finished: true })
|
|
215
260
|
} else {
|
|
216
261
|
this[kDB][kRef]()
|
|
@@ -239,6 +284,8 @@ class Iterator extends AbstractIterator {
|
|
|
239
284
|
}
|
|
240
285
|
|
|
241
286
|
_closeSync () {
|
|
287
|
+
assert(!this[kBusy])
|
|
288
|
+
|
|
242
289
|
this[kCache] = kEmpty
|
|
243
290
|
|
|
244
291
|
if (this[kContext]) {
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
package/release.sh
CHANGED
|
@@ -3,12 +3,41 @@ set -e
|
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")"
|
|
5
5
|
|
|
6
|
+
export DOCKER_HOST="${DOCKER_HOST:-ssh://nxtop@hq-test-srv1.nxt.io}"
|
|
7
|
+
|
|
8
|
+
# Fail fast: npm publish needs a valid login, so check before the slow builds.
|
|
9
|
+
if ! npm whoami --registry https://registry.npmjs.org > /dev/null 2>&1; then
|
|
10
|
+
echo "Not logged in to npm, run 'npm login' first." >&2
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
6
14
|
# Fail fast: npm version refuses a dirty tree, so check before the slow builds.
|
|
7
15
|
if [ -n "$(git status --porcelain)" ]; then
|
|
8
16
|
echo "Working tree is not clean, commit or stash changes first." >&2
|
|
9
17
|
exit 1
|
|
10
18
|
fi
|
|
11
19
|
|
|
20
|
+
# Fail fast: don't build/publish on a branch that's behind or diverged from origin.
|
|
21
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
22
|
+
echo "Fetching origin..."
|
|
23
|
+
git fetch origin "$BRANCH"
|
|
24
|
+
|
|
25
|
+
LOCAL=$(git rev-parse HEAD)
|
|
26
|
+
REMOTE=$(git rev-parse "origin/$BRANCH")
|
|
27
|
+
BASE=$(git merge-base HEAD "origin/$BRANCH")
|
|
28
|
+
|
|
29
|
+
if [ "$LOCAL" = "$REMOTE" ]; then
|
|
30
|
+
: # up to date
|
|
31
|
+
elif [ "$LOCAL" = "$BASE" ]; then
|
|
32
|
+
echo "Branch '$BRANCH' is behind origin, pull the latest changes first." >&2
|
|
33
|
+
exit 1
|
|
34
|
+
elif [ "$REMOTE" = "$BASE" ]; then
|
|
35
|
+
: # local is ahead, fine to push
|
|
36
|
+
else
|
|
37
|
+
echo "Branch '$BRANCH' has diverged from origin, reconcile before releasing." >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
12
41
|
# Keep the local arm64 build targeting the same node version as the Docker image.
|
|
13
42
|
NODE_TARGET=$(sed -n 's/^FROM node:\([0-9.]*\).*/\1/p' Dockerfile)
|
|
14
43
|
if [ -z "$NODE_TARGET" ]; then
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const binding = require('./binding')
|
|
4
|
+
|
|
5
|
+
const kWriteBufferManagerContext = Symbol('writeBufferManagerContext')
|
|
6
|
+
|
|
7
|
+
class RocksWriteBufferManager {
|
|
8
|
+
constructor (options = {}) {
|
|
9
|
+
this[kWriteBufferManagerContext] = binding.write_buffer_manager_init(options)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get handle () {
|
|
13
|
+
return binding.write_buffer_manager_get_handle(this[kWriteBufferManagerContext])
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get usage () {
|
|
17
|
+
return binding.write_buffer_manager_get_usage(this[kWriteBufferManagerContext])
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
exports.RocksWriteBufferManager = RocksWriteBufferManager
|