@nxtedition/rocksdb 15.5.0 → 16.0.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 +48 -10
- package/index.js +70 -10
- package/iterator.js +23 -1
- package/max_rev_operator.h +9 -5
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/@nxtedition+rocksdb.node +0 -0
- package/prebuilds/linux-x64/@nxtedition+rocksdb.node +0 -0
package/binding.cc
CHANGED
|
@@ -551,10 +551,10 @@ class Iterator final : public BaseIterator {
|
|
|
551
551
|
rocksdb::ColumnFamilyHandle* column = database->db->DefaultColumnFamily();
|
|
552
552
|
NAPI_STATUS_THROWS(GetProperty(env, options, "column", column));
|
|
553
553
|
|
|
554
|
-
Encoding keyEncoding;
|
|
554
|
+
Encoding keyEncoding = Encoding::Buffer;
|
|
555
555
|
NAPI_STATUS_THROWS(GetProperty(env, options, "keyEncoding", keyEncoding));
|
|
556
556
|
|
|
557
|
-
Encoding valueEncoding;
|
|
557
|
+
Encoding valueEncoding = Encoding::Buffer;
|
|
558
558
|
NAPI_STATUS_THROWS(GetProperty(env, options, "valueEncoding", valueEncoding));
|
|
559
559
|
|
|
560
560
|
rocksdb::ReadOptions readOptions;
|
|
@@ -601,6 +601,7 @@ class Iterator final : public BaseIterator {
|
|
|
601
601
|
std::vector<rocksdb::PinnableSlice> keys;
|
|
602
602
|
std::vector<rocksdb::PinnableSlice> values;
|
|
603
603
|
size_t count = 0;
|
|
604
|
+
size_t bytes = 0;
|
|
604
605
|
bool finished = false;
|
|
605
606
|
bool limited = false;
|
|
606
607
|
};
|
|
@@ -616,14 +617,16 @@ class Iterator final : public BaseIterator {
|
|
|
616
617
|
|
|
617
618
|
const auto deadline = timeout ? database_->db->GetEnv()->NowMicros() + timeout * 1000 : 0;
|
|
618
619
|
|
|
619
|
-
size_t bytesRead = 0;
|
|
620
620
|
while (true) {
|
|
621
|
-
if (state.count >= count ||
|
|
621
|
+
if (state.count >= count || state.bytes > highWaterMarkBytes_) {
|
|
622
|
+
// Batch cap (size/bytes) reached: more data may exist, so this is
|
|
623
|
+
// "limited", not "finished".
|
|
622
624
|
state.limited = true;
|
|
623
625
|
break;
|
|
624
626
|
}
|
|
625
627
|
|
|
626
628
|
if (deadline > 0 && database_->db->GetEnv()->NowMicros() > deadline) {
|
|
629
|
+
// Timed out: neither finished nor limited; the caller may retry.
|
|
627
630
|
break;
|
|
628
631
|
}
|
|
629
632
|
|
|
@@ -635,12 +638,19 @@ class Iterator final : public BaseIterator {
|
|
|
635
638
|
|
|
636
639
|
ROCKS_STATUS_RETURN(Status());
|
|
637
640
|
|
|
638
|
-
if (!Valid()
|
|
641
|
+
if (!Valid()) {
|
|
642
|
+
// Iterator naturally exhausted.
|
|
639
643
|
state.finished = true;
|
|
640
644
|
break;
|
|
641
645
|
}
|
|
642
646
|
|
|
643
|
-
|
|
647
|
+
if (!Increment()) {
|
|
648
|
+
// Hit the user's `limit` option: terminal, and flag that it was a
|
|
649
|
+
// limit rather than natural exhaustion.
|
|
650
|
+
state.finished = true;
|
|
651
|
+
state.limited = true;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
644
654
|
|
|
645
655
|
if (keyFilter_ && !re2::RE2::PartialMatch(CurrentKey().ToStringView(), *keyFilter_)) {
|
|
646
656
|
continue;
|
|
@@ -653,18 +663,22 @@ class Iterator final : public BaseIterator {
|
|
|
653
663
|
if (keys_ && values_) {
|
|
654
664
|
rocksdb::PinnableSlice k;
|
|
655
665
|
k.PinSelf(CurrentKey());
|
|
666
|
+
state.bytes += k.size();
|
|
656
667
|
state.keys.push_back(std::move(k));
|
|
657
668
|
|
|
658
669
|
rocksdb::PinnableSlice v;
|
|
659
670
|
v.PinSelf(CurrentValue());
|
|
671
|
+
state.bytes += v.size();
|
|
660
672
|
state.values.push_back(std::move(v));
|
|
661
673
|
} else if (keys_) {
|
|
662
674
|
rocksdb::PinnableSlice k;
|
|
663
675
|
k.PinSelf(CurrentKey());
|
|
676
|
+
state.bytes += k.size();
|
|
664
677
|
state.keys.push_back(std::move(k));
|
|
665
678
|
} else if (values_) {
|
|
666
679
|
rocksdb::PinnableSlice v;
|
|
667
680
|
v.PinSelf(CurrentValue());
|
|
681
|
+
state.bytes += v.size();
|
|
668
682
|
state.values.push_back(std::move(v));
|
|
669
683
|
} else {
|
|
670
684
|
assert(false);
|
|
@@ -729,14 +743,18 @@ class Iterator final : public BaseIterator {
|
|
|
729
743
|
const auto deadline = timeout ? database_->db->GetEnv()->NowMicros() + timeout * 1000 : 0;
|
|
730
744
|
|
|
731
745
|
size_t idx = 0;
|
|
732
|
-
size_t
|
|
746
|
+
size_t bytes = 0;
|
|
733
747
|
while (true) {
|
|
734
|
-
if (idx >= count * 2 ||
|
|
748
|
+
if (idx >= static_cast<size_t>(count) * 2 || bytes > highWaterMarkBytes_) {
|
|
749
|
+
// Batch cap (size/bytes) reached: more data may exist, so this is
|
|
750
|
+
// "limited", not "finished". (count is uint32_t; widen before *2 so
|
|
751
|
+
// query()'s UINT32_MAX count doesn't overflow to a small cap.)
|
|
735
752
|
NAPI_STATUS_THROWS(napi_get_boolean(env, true, &limited));
|
|
736
753
|
break;
|
|
737
754
|
}
|
|
738
755
|
|
|
739
756
|
if (deadline > 0 && database_->db->GetEnv()->NowMicros() > deadline) {
|
|
757
|
+
// Timed out: neither finished nor limited; the caller may retry.
|
|
740
758
|
break;
|
|
741
759
|
}
|
|
742
760
|
|
|
@@ -748,12 +766,19 @@ class Iterator final : public BaseIterator {
|
|
|
748
766
|
|
|
749
767
|
ROCKS_STATUS_THROWS_NAPI(Status());
|
|
750
768
|
|
|
751
|
-
if (!Valid()
|
|
769
|
+
if (!Valid()) {
|
|
770
|
+
// Iterator naturally exhausted.
|
|
752
771
|
NAPI_STATUS_THROWS(napi_get_boolean(env, true, &finished));
|
|
753
772
|
break;
|
|
754
773
|
}
|
|
755
774
|
|
|
756
|
-
|
|
775
|
+
if (!Increment()) {
|
|
776
|
+
// Hit the user's `limit` option: terminal, and flag that it was a limit
|
|
777
|
+
// rather than natural exhaustion.
|
|
778
|
+
NAPI_STATUS_THROWS(napi_get_boolean(env, true, &finished));
|
|
779
|
+
NAPI_STATUS_THROWS(napi_get_boolean(env, true, &limited));
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
757
782
|
|
|
758
783
|
if (keyFilter_ && !re2::RE2::PartialMatch(CurrentKey().ToStringView(), *keyFilter_)) {
|
|
759
784
|
continue;
|
|
@@ -767,12 +792,15 @@ class Iterator final : public BaseIterator {
|
|
|
767
792
|
napi_value val;
|
|
768
793
|
|
|
769
794
|
if (keys_ && values_) {
|
|
795
|
+
bytes += CurrentKey().size() + CurrentValue().size();
|
|
770
796
|
NAPI_STATUS_THROWS(Convert(env, CurrentKey(), keyEncoding_, key, unsafe_));
|
|
771
797
|
NAPI_STATUS_THROWS(Convert(env, CurrentValue(), valueEncoding_, val, unsafe_));
|
|
772
798
|
} else if (keys_) {
|
|
799
|
+
bytes += CurrentKey().size();
|
|
773
800
|
NAPI_STATUS_THROWS(Convert(env, CurrentKey(), keyEncoding_, key, unsafe_));
|
|
774
801
|
NAPI_STATUS_THROWS(napi_get_undefined(env, &val));
|
|
775
802
|
} else if (values_) {
|
|
803
|
+
bytes += CurrentValue().size();
|
|
776
804
|
NAPI_STATUS_THROWS(napi_get_undefined(env, &key));
|
|
777
805
|
NAPI_STATUS_THROWS(Convert(env, CurrentValue(), valueEncoding_, val, unsafe_));
|
|
778
806
|
} else {
|
|
@@ -1233,6 +1261,11 @@ NAPI_METHOD(db_get_identity) {
|
|
|
1233
1261
|
Database* database;
|
|
1234
1262
|
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
|
|
1235
1263
|
|
|
1264
|
+
if (!database->db) {
|
|
1265
|
+
napi_throw_error(env, "LEVEL_DATABASE_NOT_OPEN", "Database is not open");
|
|
1266
|
+
return NULL;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1236
1269
|
std::string identity;
|
|
1237
1270
|
ROCKS_STATUS_THROWS_NAPI(database->db->GetDbIdentity(identity));
|
|
1238
1271
|
|
|
@@ -1733,6 +1766,11 @@ NAPI_METHOD(db_get_latest_sequence) {
|
|
|
1733
1766
|
Database* database;
|
|
1734
1767
|
NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
|
|
1735
1768
|
|
|
1769
|
+
if (!database->db) {
|
|
1770
|
+
napi_throw_error(env, "LEVEL_DATABASE_NOT_OPEN", "Database is not open");
|
|
1771
|
+
return NULL;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1736
1774
|
const auto seq = database->db->GetLatestSequenceNumber();
|
|
1737
1775
|
|
|
1738
1776
|
napi_value result;
|
package/index.js
CHANGED
|
@@ -52,6 +52,12 @@ class RocksLevel extends AbstractLevel {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
get sequence () {
|
|
55
|
+
if (this.status !== 'open') {
|
|
56
|
+
throw new ModuleError('Database is not open', {
|
|
57
|
+
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
55
61
|
return binding.db_get_latest_sequence(this[kContext])
|
|
56
62
|
}
|
|
57
63
|
|
|
@@ -112,7 +118,14 @@ class RocksLevel extends AbstractLevel {
|
|
|
112
118
|
[kUnref] () {
|
|
113
119
|
this[kRefs]--
|
|
114
120
|
if (this[kRefs] === 0 && this[kPendingClose]) {
|
|
115
|
-
|
|
121
|
+
// Perform the deferred native close now that all in-flight ops have
|
|
122
|
+
// drained. Note: kPendingClose holds the abstract-level _close callback,
|
|
123
|
+
// so we must call binding.db_close here (not just the callback) or the
|
|
124
|
+
// native DB and its directory lock would leak. nextTick avoids reentering
|
|
125
|
+
// the native layer from within the completing op's own callback.
|
|
126
|
+
const callback = this[kPendingClose]
|
|
127
|
+
this[kPendingClose] = null
|
|
128
|
+
process.nextTick(() => binding.db_close(this[kContext], callback))
|
|
116
129
|
}
|
|
117
130
|
}
|
|
118
131
|
|
|
@@ -243,10 +256,22 @@ class RocksLevel extends AbstractLevel {
|
|
|
243
256
|
}
|
|
244
257
|
}
|
|
245
258
|
|
|
246
|
-
|
|
259
|
+
// Hold a db ref for the duration of the write so close() defers db_close
|
|
260
|
+
// (which frees the native db on a worker thread) until it completes. The
|
|
261
|
+
// array-form batch uses a transient WriteBatch that is not an abstract-level
|
|
262
|
+
// resource, so it is not otherwise tracked across close.
|
|
263
|
+
this[kRef]()
|
|
264
|
+
try {
|
|
265
|
+
binding.batch_write(this[kContext], batch, options ?? {}, (err, val) => {
|
|
266
|
+
this[kUnref]()
|
|
267
|
+
binding.batch_clear(batch)
|
|
268
|
+
callback(err, val)
|
|
269
|
+
})
|
|
270
|
+
} catch (err) {
|
|
271
|
+
this[kUnref]()
|
|
247
272
|
binding.batch_clear(batch)
|
|
248
|
-
callback
|
|
249
|
-
}
|
|
273
|
+
process.nextTick(callback, err)
|
|
274
|
+
}
|
|
250
275
|
|
|
251
276
|
return callback[kPromise]
|
|
252
277
|
}
|
|
@@ -256,6 +281,12 @@ class RocksLevel extends AbstractLevel {
|
|
|
256
281
|
}
|
|
257
282
|
|
|
258
283
|
get identity () {
|
|
284
|
+
if (this.status !== 'open') {
|
|
285
|
+
throw new ModuleError('Database is not open', {
|
|
286
|
+
code: 'LEVEL_DATABASE_NOT_OPEN'
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
259
290
|
return binding.db_get_identity(this[kContext])
|
|
260
291
|
}
|
|
261
292
|
|
|
@@ -305,10 +336,21 @@ class RocksLevel extends AbstractLevel {
|
|
|
305
336
|
|
|
306
337
|
const handle = binding.updates_init(this[kContext], options)
|
|
307
338
|
try {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
339
|
+
// Stop if the db is closed between yields, so we never call updates_next
|
|
340
|
+
// on a freed db.
|
|
341
|
+
while (this.status === 'open') {
|
|
342
|
+
// Hold a db ref for the duration of each updates_next so close() defers
|
|
343
|
+
// db_close (and the Database::Close() that resets this log iterator on a
|
|
344
|
+
// worker thread) until the in-flight read completes.
|
|
345
|
+
this[kRef]()
|
|
346
|
+
let value
|
|
347
|
+
try {
|
|
348
|
+
value = await new Promise((resolve, reject) => {
|
|
349
|
+
binding.updates_next(handle, (err, val) => err ? reject(err) : resolve(val))
|
|
350
|
+
})
|
|
351
|
+
} finally {
|
|
352
|
+
this[kUnref]()
|
|
353
|
+
}
|
|
312
354
|
if (!value) {
|
|
313
355
|
break
|
|
314
356
|
}
|
|
@@ -328,7 +370,16 @@ class RocksLevel extends AbstractLevel {
|
|
|
328
370
|
})
|
|
329
371
|
}
|
|
330
372
|
|
|
331
|
-
|
|
373
|
+
this[kRef]()
|
|
374
|
+
try {
|
|
375
|
+
binding.db_compact_range(this[kContext], options, (err, val) => {
|
|
376
|
+
this[kUnref]()
|
|
377
|
+
callback(err, val)
|
|
378
|
+
})
|
|
379
|
+
} catch (err) {
|
|
380
|
+
this[kUnref]()
|
|
381
|
+
process.nextTick(callback, err)
|
|
382
|
+
}
|
|
332
383
|
|
|
333
384
|
return callback[kPromise]
|
|
334
385
|
}
|
|
@@ -342,7 +393,16 @@ class RocksLevel extends AbstractLevel {
|
|
|
342
393
|
})
|
|
343
394
|
}
|
|
344
395
|
|
|
345
|
-
|
|
396
|
+
this[kRef]()
|
|
397
|
+
try {
|
|
398
|
+
binding.db_flush_wal(this[kContext], options?.sync ?? false, (err, val) => {
|
|
399
|
+
this[kUnref]()
|
|
400
|
+
callback(err, val)
|
|
401
|
+
})
|
|
402
|
+
} catch (err) {
|
|
403
|
+
this[kUnref]()
|
|
404
|
+
process.nextTick(callback, err)
|
|
405
|
+
}
|
|
346
406
|
|
|
347
407
|
return callback[kPromise]
|
|
348
408
|
}
|
package/iterator.js
CHANGED
|
@@ -15,6 +15,7 @@ const kFinished = Symbol('finished')
|
|
|
15
15
|
const kFirst = Symbol('first')
|
|
16
16
|
const kPosition = Symbol('position')
|
|
17
17
|
const kBusy = Symbol('busy')
|
|
18
|
+
const kPendingClose = Symbol('pendingClose')
|
|
18
19
|
|
|
19
20
|
const kEmpty = Object.freeze([])
|
|
20
21
|
|
|
@@ -30,6 +31,7 @@ class Iterator extends AbstractIterator {
|
|
|
30
31
|
this[kPosition] = 0
|
|
31
32
|
this[kDB] = db
|
|
32
33
|
this[kBusy] = false
|
|
34
|
+
this[kPendingClose] = null
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
[Symbol.asyncDispose] () {
|
|
@@ -41,7 +43,23 @@ class Iterator extends AbstractIterator {
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
_close (callback) {
|
|
44
|
-
|
|
46
|
+
// If an async nextv/seek is in flight on a worker thread, defer the close
|
|
47
|
+
// until it completes so we never free the native rocksdb iterator while the
|
|
48
|
+
// worker is still reading it. The pending close is flushed from the async
|
|
49
|
+
// op's completion callback (see _flushPendingClose).
|
|
50
|
+
if (this[kBusy]) {
|
|
51
|
+
this[kPendingClose] = callback
|
|
52
|
+
} else {
|
|
53
|
+
this._closeAsync(callback)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_flushPendingClose () {
|
|
58
|
+
if (!this[kBusy] && this[kPendingClose]) {
|
|
59
|
+
const callback = this[kPendingClose]
|
|
60
|
+
this[kPendingClose] = null
|
|
61
|
+
this._closeAsync(callback)
|
|
62
|
+
}
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
_end (callback) {
|
|
@@ -147,6 +165,8 @@ class Iterator extends AbstractIterator {
|
|
|
147
165
|
} else {
|
|
148
166
|
callback(null)
|
|
149
167
|
}
|
|
168
|
+
|
|
169
|
+
this._flushPendingClose()
|
|
150
170
|
})
|
|
151
171
|
} catch (err) {
|
|
152
172
|
this[kBusy] = false
|
|
@@ -193,6 +213,8 @@ class Iterator extends AbstractIterator {
|
|
|
193
213
|
this[kFinished] = result.finished
|
|
194
214
|
callback(null, result)
|
|
195
215
|
}
|
|
216
|
+
|
|
217
|
+
this._flushPendingClose()
|
|
196
218
|
})
|
|
197
219
|
}
|
|
198
220
|
} catch (err) {
|
package/max_rev_operator.h
CHANGED
|
@@ -13,10 +13,14 @@ int compareRev(const rocksdb::Slice& a, const rocksdb::Slice& b) {
|
|
|
13
13
|
return 1;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
// The first byte is a length prefix declaring the content length. Clamp it to
|
|
17
|
+
// the bytes actually available (size - 1) so malformed/truncated operands can
|
|
18
|
+
// never over-read, and cast through unsigned char so a prefix >= 0x80 is not
|
|
19
|
+
// sign-extended. endA/endB are exclusive end offsets: content is at [1, endX).
|
|
20
|
+
std::size_t indexA = 1;
|
|
21
|
+
std::size_t indexB = 1;
|
|
22
|
+
const std::size_t endA = 1 + std::min<std::size_t>(static_cast<unsigned char>(a[0]), a.size() - 1);
|
|
23
|
+
const std::size_t endB = 1 + std::min<std::size_t>(static_cast<unsigned char>(b[0]), b.size() - 1);
|
|
20
24
|
|
|
21
25
|
// Compare the revision number
|
|
22
26
|
auto result = 0;
|
|
@@ -52,7 +56,7 @@ int compareRev(const rocksdb::Slice& a, const rocksdb::Slice& b) {
|
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
return endA - endB;
|
|
59
|
+
return static_cast<int>(endA) - static_cast<int>(endB);
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
class MaxRevOperator : public rocksdb::MergeOperator {
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|