@nxtedition/rocksdb 7.0.63 → 7.0.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -120,6 +120,10 @@ Status WalManager::GetUpdatesSince(
120
120
  return s;
121
121
  }
122
122
 
123
+ std::sort(std::begin(*wal_files), std::end(*wal_files), [](auto& lhs, auto& rhs) {
124
+ return lhs->StartSequence() > rhs->StartSequence();
125
+ });
126
+
123
127
  s = RetainProbableWalFiles(*wal_files, seq);
124
128
  if (!s.ok()) {
125
129
  return s;
package/index.js CHANGED
@@ -17,6 +17,10 @@ const kColumns = Symbol('columns')
17
17
  const kLocation = Symbol('location')
18
18
  const kPromise = Symbol('promise')
19
19
  const kUpdates = Symbol('updates')
20
+ const kRef = Symbol('ref')
21
+ const kUnref = Symbol('unref')
22
+ const kRefs = Symbol('refs')
23
+ const kPendingClose = Symbol('pendingClose')
20
24
 
21
25
  const EMPTY = {}
22
26
 
@@ -66,6 +70,9 @@ class RocksLevel extends AbstractLevel {
66
70
  this[kContext] = binding.db_init()
67
71
  this[kColumns] = {}
68
72
 
73
+ this[kRefs] = 0
74
+ this[kPendingClose] = null
75
+
69
76
  // .updates(...) uses 'update' listener.
70
77
  this.setMaxListeners(100)
71
78
  }
@@ -101,8 +108,23 @@ class RocksLevel extends AbstractLevel {
101
108
  }
102
109
  }
103
110
 
111
+ [kRef] () {
112
+ this[kRefs]++
113
+ }
114
+
115
+ [kUnref] () {
116
+ this[kRefs]--
117
+ if (this[kRefs] === 0 && this[kPendingClose]) {
118
+ this[kPendingClose]()
119
+ }
120
+ }
121
+
104
122
  _close (callback) {
105
- binding.db_close(this[kContext], callback)
123
+ if (this[kRefs]) {
124
+ this[kPendingClose] = callback
125
+ } else {
126
+ binding.db_close(this[kContext], callback)
127
+ }
106
128
  }
107
129
 
108
130
  _put (key, value, options, callback) {
@@ -137,9 +159,14 @@ class RocksLevel extends AbstractLevel {
137
159
  callback = fromCallback(callback, kPromise)
138
160
 
139
161
  try {
140
- binding.db_get_many(this[kContext], keys, options ?? EMPTY, callback)
162
+ this[kRef]()
163
+ binding.db_get_many(this[kContext], keys, options ?? EMPTY, (err, val) => {
164
+ callback(err, val)
165
+ this[kUnref]()
166
+ })
141
167
  } catch (err) {
142
168
  process.nextTick(callback, err)
169
+ this[kUnref]()
143
170
  }
144
171
 
145
172
  return callback[kPromise]
@@ -222,36 +249,6 @@ class RocksLevel extends AbstractLevel {
222
249
  return binding.db_get_property(this[kContext], property)
223
250
  }
224
251
 
225
- async getCurrentWALFile () {
226
- if (this.status !== 'open') {
227
- throw new ModuleError('Database is not open', {
228
- code: 'LEVEL_DATABASE_NOT_OPEN'
229
- })
230
- }
231
-
232
- return binding.db_get_current_wal_file(this[kContext])
233
- }
234
-
235
- async getSortedWALFiles () {
236
- if (this.status !== 'open') {
237
- throw new ModuleError('Database is not open', {
238
- code: 'LEVEL_DATABASE_NOT_OPEN'
239
- })
240
- }
241
-
242
- return binding.db_get_sorted_wal_files(this[kContext])
243
- }
244
-
245
- async flushWAL (options) {
246
- if (this.status !== 'open') {
247
- throw new ModuleError('Database is not open', {
248
- code: 'LEVEL_DATABASE_NOT_OPEN'
249
- })
250
- }
251
-
252
- binding.db_flush_wal(this[kContext], options)
253
- }
254
-
255
252
  async query (options) {
256
253
  if (this.status !== 'open') {
257
254
  throw new ModuleError('Database is not open', {
@@ -259,17 +256,9 @@ class RocksLevel extends AbstractLevel {
259
256
  })
260
257
  }
261
258
 
262
- const context = binding.iterator_init(this[kContext], options)
263
- const resource = {
264
- callback: null,
265
- close (callback) {
266
- this.callback = callback
267
- }
268
- }
269
-
259
+ const context = binding.iterator_init(this[kContext], options ?? {})
270
260
  try {
271
- this.attachResource(resource)
272
-
261
+ this[kRef]()
273
262
  const limit = options.limit ?? 1000
274
263
  return await new Promise((resolve, reject) => binding.iterator_nextv(context, limit, (err, rows, finished) => {
275
264
  if (err) {
@@ -283,11 +272,8 @@ class RocksLevel extends AbstractLevel {
283
272
  }
284
273
  }))
285
274
  } finally {
286
- this.detachResource(resource)
287
275
  binding.iterator_close(context)
288
- if (resource.callback) {
289
- resource.callback()
290
- }
276
+ this[kUnref]()
291
277
  }
292
278
  }
293
279
 
@@ -347,29 +333,6 @@ class RocksLevel extends AbstractLevel {
347
333
 
348
334
  const db = this
349
335
 
350
- async function * _updates (options) {
351
- let first = true
352
- for await (const update of db[kUpdates]({ ...options, since: Math.max(0, options.since - 1024) })) {
353
- if (first) {
354
- if (update.sequence > options.since) {
355
- // HACK
356
- db.emit('warning', `Invalid update sequence ${update.sequence} > ${options.since}. Starting from 0.`)
357
- first = null
358
- break
359
- } else {
360
- first = false
361
- }
362
- }
363
- yield update
364
- }
365
-
366
- if (first === null) {
367
- for await (const update of db[kUpdates]({ ...options, since: 0 })) {
368
- yield update
369
- }
370
- }
371
- }
372
-
373
336
  try {
374
337
  let since = options.since
375
338
  while (true) {
@@ -401,15 +364,12 @@ class RocksLevel extends AbstractLevel {
401
364
  try {
402
365
  if (since <= db.sequence) {
403
366
  let first = true
404
- for await (const update of _updates({
367
+ for await (const update of db[kUpdates]({
405
368
  ...options,
369
+ signal: ac.signal,
406
370
  // HACK: https://github.com/facebook/rocksdb/issues/10476
407
- since: Math.max(0, options.since - 2048)
371
+ since: Math.max(0, options.since)
408
372
  })) {
409
- if (ac.signal.aborted) {
410
- throw new AbortError()
411
- }
412
-
413
373
  if (first) {
414
374
  if (update.sequence > since) {
415
375
  db.emit('warning', `Invalid updates sequence ${update.sequence} > ${options.since}.`)
@@ -435,10 +395,6 @@ class RocksLevel extends AbstractLevel {
435
395
 
436
396
  let first = true
437
397
  for await (const update of buffer) {
438
- if (ac.signal.aborted) {
439
- throw new AbortError()
440
- }
441
-
442
398
  if (first) {
443
399
  if (update.sequence > since) {
444
400
  db.emit('warning', `Invalid batch sequence ${update.sequence} > ${options.since}.`)
@@ -457,72 +413,32 @@ class RocksLevel extends AbstractLevel {
457
413
  }
458
414
  }
459
415
 
460
- async * [kUpdates] (options) {
461
- class Updates {
462
- constructor (db, options) {
463
- this.context = binding.updates_init(db[kContext], options)
464
- this.closed = false
465
- this.promise = null
466
- this.db = db
467
- this.db.attachResource(this)
468
- }
469
-
470
- async next () {
471
- if (this.closed) {
472
- return {}
416
+ async * [kUpdates] ({ signal, ...options }) {
417
+ const context = binding.updates_init(this[kContext], options)
418
+ this[kRef]()
419
+ try {
420
+ while (true) {
421
+ if (signal?.aborted) {
422
+ throw new AbortError()
473
423
  }
474
424
 
475
- this.promise = new Promise(resolve => binding.updates_next(this.context, (err, rows, sequence, count) => {
476
- this.promise = null
425
+ const entry = await new Promise((resolve, reject) => binding.updates_next(context, (err, rows, sequence, count) => {
477
426
  if (err) {
478
- resolve(Promise.reject(err))
427
+ reject(err)
479
428
  } else {
480
429
  resolve({ rows, sequence, count })
481
430
  }
482
431
  }))
483
432
 
484
- return this.promise
485
- }
486
-
487
- async close (callback) {
488
- try {
489
- await this.promise
490
- } catch {
491
- // Do nothing...
492
- }
493
-
494
- try {
495
- if (!this.closed) {
496
- this.closed = true
497
- binding.updates_close(this.context)
498
- }
499
-
500
- if (callback) {
501
- process.nextTick(callback)
502
- }
503
- } catch (err) {
504
- if (callback) {
505
- process.nextTick(callback, err)
506
- } else {
507
- throw err
508
- }
509
- } finally {
510
- this.db.detachResource(this)
511
- }
512
- }
513
- }
514
-
515
- const updates = new Updates(this, options)
516
- try {
517
- while (true) {
518
- const entry = await updates.next()
519
433
  if (!entry.rows) {
520
434
  return
521
435
  }
436
+
522
437
  yield entry
523
438
  }
524
439
  } finally {
525
- await updates.close()
440
+ binding.updates_close(context)
441
+ this[kUnref]()
526
442
  }
527
443
  }
528
444
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/rocksdb",
3
- "version": "7.0.63",
3
+ "version": "7.0.64",
4
4
  "description": "A low-level Node.js RocksDB binding",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
package/util.h ADDED
@@ -0,0 +1,338 @@
1
+ #pragma once
2
+
3
+ #include <assert.h>
4
+ #include <napi-macros.h>
5
+ #include <node_api.h>
6
+
7
+ #include <rocksdb/db.h>
8
+ #include <rocksdb/slice.h>
9
+ #include <rocksdb/status.h>
10
+
11
+ #include <array>
12
+ #include <optional>
13
+ #include <string>
14
+
15
+ #define NAPI_STATUS_RETURN(call) \
16
+ { \
17
+ auto _status = (call); \
18
+ if (_status != napi_ok) { \
19
+ return _status; \
20
+ } \
21
+ }
22
+
23
+ #define ROCKS_STATUS_THROWS_NAPI(call) \
24
+ { \
25
+ auto _status = (call); \
26
+ if (!_status.ok()) { \
27
+ napi_throw(env, ToError(env, _status)); \
28
+ return NULL; \
29
+ } \
30
+ }
31
+
32
+ #define ROCKS_STATUS_RETURN_NAPI(call) \
33
+ { \
34
+ auto _status = (call); \
35
+ if (!_status.ok()) { \
36
+ napi_throw(env, ToError(env, _status)); \
37
+ return napi_pending_exception; \
38
+ } \
39
+ }
40
+
41
+ template <typename T>
42
+ static void Finalize(napi_env env, void* data, void* hint) {
43
+ if (hint) {
44
+ delete reinterpret_cast<T*>(hint);
45
+ }
46
+ }
47
+
48
+ static napi_value CreateError(napi_env env, const std::optional<std::string_view>& code, const std::string_view& msg) {
49
+ napi_value codeValue = nullptr;
50
+ if (code) {
51
+ NAPI_STATUS_THROWS(napi_create_string_utf8(env, code->data(), code->size(), &codeValue));
52
+ }
53
+ napi_value msgValue;
54
+ NAPI_STATUS_THROWS(napi_create_string_utf8(env, msg.data(), msg.size(), &msgValue));
55
+ napi_value error;
56
+ NAPI_STATUS_THROWS(napi_create_error(env, codeValue, msgValue, &error));
57
+ return error;
58
+ }
59
+
60
+ static napi_value ToError(napi_env env, const rocksdb::Status& status) {
61
+ if (status.ok()) {
62
+ return 0;
63
+ }
64
+
65
+ const auto msg = status.ToString();
66
+
67
+ if (status.IsNotFound()) {
68
+ return CreateError(env, "LEVEL_NOT_FOUND", msg);
69
+ } else if (status.IsCorruption()) {
70
+ return CreateError(env, "LEVEL_CORRUPTION", msg);
71
+ } else if (status.IsTryAgain()) {
72
+ return CreateError(env, "LEVEL_TRYAGAIN", msg);
73
+ } else if (status.IsIOError()) {
74
+ if (msg.find("IO error: lock ") != std::string::npos) { // env_posix.cc
75
+ return CreateError(env, "LEVEL_LOCKED", msg);
76
+ } else if (msg.find("IO error: LockFile ") != std::string::npos) { // env_win.cc
77
+ return CreateError(env, "LEVEL_LOCKED", msg);
78
+ } else if (msg.find("IO error: While lock file") != std::string::npos) { // env_mac.cc
79
+ return CreateError(env, "LEVEL_LOCKED", msg);
80
+ } else {
81
+ return CreateError(env, "LEVEL_IO_ERROR", msg);
82
+ }
83
+ }
84
+
85
+ return CreateError(env, {}, msg);
86
+ }
87
+
88
+ static napi_status GetString(napi_env env, napi_value from, std::string& to) {
89
+ napi_valuetype type;
90
+ NAPI_STATUS_RETURN(napi_typeof(env, from, &type));
91
+
92
+ if (type == napi_string) {
93
+ size_t length = 0;
94
+ NAPI_STATUS_RETURN(napi_get_value_string_utf8(env, from, nullptr, 0, &length));
95
+ to.resize(length, '\0');
96
+ NAPI_STATUS_RETURN(napi_get_value_string_utf8(env, from, &to[0], length + 1, &length));
97
+ } else {
98
+ bool isBuffer;
99
+ NAPI_STATUS_RETURN(napi_is_buffer(env, from, &isBuffer));
100
+
101
+ if (isBuffer) {
102
+ char* buf = nullptr;
103
+ size_t length = 0;
104
+ NAPI_STATUS_RETURN(napi_get_buffer_info(env, from, reinterpret_cast<void**>(&buf), &length));
105
+ to.assign(buf, length);
106
+ } else {
107
+ return napi_invalid_arg;
108
+ }
109
+ }
110
+
111
+ return napi_ok;
112
+ }
113
+
114
+ void DestroyReference(void* arg1, void* arg2) {
115
+ auto env = reinterpret_cast<napi_env>(arg1);
116
+ auto ref = reinterpret_cast<napi_ref>(arg2);
117
+ napi_delete_reference(env, ref);
118
+ }
119
+
120
+ static napi_status GetString(napi_env env, napi_value from, rocksdb::PinnableSlice& to) {
121
+ napi_valuetype type;
122
+ NAPI_STATUS_RETURN(napi_typeof(env, from, &type));
123
+
124
+ if (type == napi_string) {
125
+ size_t length = 0;
126
+ NAPI_STATUS_RETURN(napi_get_value_string_utf8(env, from, nullptr, 0, &length));
127
+ to.GetSelf()->resize(length, '\0');
128
+ NAPI_STATUS_RETURN(napi_get_value_string_utf8(env, from, &(*to.GetSelf())[0], length + 1, &length));
129
+ to.PinSelf();
130
+ } else {
131
+ bool isBuffer;
132
+ NAPI_STATUS_RETURN(napi_is_buffer(env, from, &isBuffer));
133
+
134
+ if (isBuffer) {
135
+ char* buf = nullptr;
136
+ size_t length = 0;
137
+ NAPI_STATUS_RETURN(napi_get_buffer_info(env, from, reinterpret_cast<void**>(&buf), &length));
138
+
139
+ napi_ref ref;
140
+ NAPI_STATUS_RETURN(napi_create_reference(env, from, 1, &ref));
141
+ to.PinSlice(rocksdb::Slice(buf, length), DestroyReference, env, ref);
142
+ } else {
143
+ return napi_invalid_arg;
144
+ }
145
+ }
146
+
147
+ return napi_ok;
148
+ }
149
+
150
+ enum class Encoding { Invalid, Buffer, String };
151
+
152
+ static napi_status GetValue(napi_env env, napi_value value, bool& result) {
153
+ return napi_get_value_bool(env, value, &result);
154
+ }
155
+
156
+ static napi_status GetValue(napi_env env, napi_value value, uint32_t& result) {
157
+ return napi_get_value_uint32(env, value, &result);
158
+ }
159
+
160
+ static napi_status GetValue(napi_env env, napi_value value, int32_t& result) {
161
+ return napi_get_value_int32(env, value, &result);
162
+ }
163
+
164
+ static napi_status GetValue(napi_env env, napi_value value, int64_t& result) {
165
+ return napi_get_value_int64(env, value, &result);
166
+ }
167
+
168
+ static napi_status GetValue(napi_env env, napi_value value, uint64_t& result) {
169
+ int64_t result2;
170
+ NAPI_STATUS_RETURN(napi_get_value_int64(env, value, &result2));
171
+ result = static_cast<uint64_t>(result2);
172
+ return napi_ok;
173
+ }
174
+
175
+ static napi_status GetValue(napi_env env, napi_value value, unsigned long& result) {
176
+ int64_t result2;
177
+ NAPI_STATUS_RETURN(napi_get_value_int64(env, value, &result2));
178
+ result = static_cast<unsigned long>(result2);
179
+ return napi_ok;
180
+ }
181
+
182
+ static napi_status GetValue(napi_env env, napi_value value, std::string& result) {
183
+ return GetString(env, value, result);
184
+ }
185
+
186
+ static napi_status GetValue(napi_env env, napi_value value, rocksdb::PinnableSlice& result) {
187
+ return GetString(env, value, result);
188
+ }
189
+
190
+ static napi_status GetValue(napi_env env, napi_value value, rocksdb::ColumnFamilyHandle*& result) {
191
+ return napi_get_value_external(env, value, reinterpret_cast<void**>(&result));
192
+ }
193
+
194
+ static napi_status GetValue(napi_env env, napi_value value, Encoding& result) {
195
+ size_t size;
196
+ NAPI_STATUS_RETURN(napi_get_value_string_utf8(env, value, nullptr, 0, &size));
197
+
198
+ if (size == 6) {
199
+ result = Encoding::Buffer;
200
+ } else {
201
+ result = Encoding::String;
202
+ }
203
+
204
+ return napi_ok;
205
+ }
206
+
207
+ template <typename T>
208
+ static napi_status GetValue(napi_env env, napi_value value, std::optional<T>& result) {
209
+ result = T{};
210
+ return GetValue(env, value, *result);
211
+ }
212
+
213
+ template <typename T>
214
+ static napi_status GetProperty(napi_env env,
215
+ napi_value obj,
216
+ const std::string_view& key,
217
+ T& result,
218
+ bool required = false) {
219
+ bool has = false;
220
+ NAPI_STATUS_RETURN(napi_has_named_property(env, obj, key.data(), &has));
221
+
222
+ if (!has) {
223
+ return required ? napi_invalid_arg : napi_ok;
224
+ }
225
+
226
+ napi_value value;
227
+ NAPI_STATUS_RETURN(napi_get_named_property(env, obj, key.data(), &value));
228
+
229
+ bool nully = false;
230
+
231
+ napi_value nullVal;
232
+ NAPI_STATUS_RETURN(napi_get_null(env, &nullVal));
233
+ NAPI_STATUS_RETURN(napi_strict_equals(env, nullVal, value, &nully));
234
+ if (nully) {
235
+ return required ? napi_invalid_arg : napi_ok;
236
+ }
237
+
238
+ napi_value undefinedVal;
239
+ NAPI_STATUS_RETURN(napi_get_undefined(env, &undefinedVal));
240
+ NAPI_STATUS_RETURN(napi_strict_equals(env, undefinedVal, value, &nully));
241
+ if (nully) {
242
+ return required ? napi_invalid_arg : napi_ok;
243
+ }
244
+
245
+ return GetValue(env, value, result);
246
+ }
247
+
248
+ template <typename T>
249
+ napi_status Convert(napi_env env, T&& s, Encoding encoding, napi_value& result) {
250
+ if (!s) {
251
+ return napi_get_null(env, &result);
252
+ } else if (encoding == Encoding::Buffer) {
253
+ // napi_create_external_buffer would be nice but is unsafe since node
254
+ // buffers are not read-only.
255
+ return napi_create_buffer_copy(env, s->size(), s->data(), nullptr, &result);
256
+ } else if (encoding == Encoding::String) {
257
+ return napi_create_string_utf8(env, s->data(), s->size(), &result);
258
+ } else {
259
+ return napi_invalid_arg;
260
+ }
261
+ }
262
+
263
+ template <typename State, typename T1, typename T2>
264
+ napi_status runAsync(const std::string& name, napi_env env, napi_value callback, T1&& execute, T2&& then) {
265
+ struct Worker final {
266
+ static void Execute(napi_env env, void* data) {
267
+ auto worker = reinterpret_cast<Worker*>(data);
268
+ worker->status = worker->execute(worker->state);
269
+ }
270
+
271
+ static void Complete(napi_env env, napi_status status, void* data) {
272
+ auto worker = std::unique_ptr<Worker>(reinterpret_cast<Worker*>(data));
273
+
274
+ napi_value callback;
275
+ NAPI_STATUS_THROWS_VOID(napi_get_reference_value(env, worker->callbackRef, &callback));
276
+
277
+ napi_value global;
278
+ NAPI_STATUS_THROWS_VOID(napi_get_global(env, &global));
279
+
280
+ if (worker->status.ok()) {
281
+ std::vector<napi_value> argv;
282
+
283
+ argv.resize(1);
284
+ NAPI_STATUS_THROWS_VOID(napi_get_null(env, &argv[0]));
285
+
286
+ const auto ret = worker->then(worker->state, env, argv);
287
+
288
+ if (ret == napi_ok) {
289
+ NAPI_STATUS_THROWS_VOID(napi_call_function(env, global, callback, argv.size(), argv.data(), nullptr));
290
+ } else {
291
+ const napi_extended_error_info* errInfo = nullptr;
292
+ NAPI_STATUS_THROWS_VOID(napi_get_last_error_info(env, &errInfo));
293
+ auto err = CreateError(env, std::nullopt,
294
+ !errInfo || !errInfo->error_message ? "empty error message" : errInfo->error_message);
295
+ NAPI_STATUS_THROWS_VOID(napi_call_function(env, global, callback, 1, &err, nullptr));
296
+ }
297
+ } else {
298
+ auto err = ToError(env, worker->status);
299
+ NAPI_STATUS_THROWS_VOID(napi_call_function(env, global, callback, 1, &err, nullptr));
300
+ }
301
+ }
302
+
303
+ ~Worker() {
304
+ if (callbackRef) {
305
+ napi_delete_reference(env, callbackRef);
306
+ callbackRef = nullptr;
307
+ }
308
+ if (asyncWork) {
309
+ napi_delete_async_work(env, asyncWork);
310
+ asyncWork = nullptr;
311
+ }
312
+ }
313
+
314
+ napi_env env = nullptr;
315
+
316
+ typename std::decay<T1>::type execute;
317
+ typename std::decay<T2>::type then;
318
+
319
+ napi_ref callbackRef = nullptr;
320
+ napi_async_work asyncWork = nullptr;
321
+ rocksdb::Status status = rocksdb::Status::OK();
322
+ State state = State();
323
+ };
324
+
325
+ auto worker = std::unique_ptr<Worker>(new Worker{env, std::forward<T1>(execute), std::forward<T2>(then)});
326
+
327
+ NAPI_STATUS_RETURN(napi_create_reference(env, callback, 1, &worker->callbackRef));
328
+ napi_value asyncResourceName;
329
+ NAPI_STATUS_RETURN(napi_create_string_utf8(env, name.data(), name.size(), &asyncResourceName));
330
+ NAPI_STATUS_RETURN(napi_create_async_work(env, callback, asyncResourceName, Worker::Execute, Worker::Complete,
331
+ worker.get(), &worker->asyncWork));
332
+
333
+ NAPI_STATUS_RETURN(napi_queue_async_work(env, worker->asyncWork));
334
+
335
+ worker.release();
336
+
337
+ return napi_ok;
338
+ }