@nxtedition/rocksdb 10.2.0 → 10.2.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
@@ -435,16 +435,12 @@ struct Iterator final : public BaseIterator {
435
435
  const std::optional<std::string>& gt,
436
436
  const std::optional<std::string>& gte,
437
437
  const bool fillCache,
438
- const Encoding keyEncoding,
439
- const Encoding valueEncoding,
440
438
  const size_t highWaterMarkBytes,
441
439
  std::shared_ptr<const rocksdb::Snapshot> snapshot,
442
440
  bool tailing = false)
443
441
  : BaseIterator(database, column, reverse, lt, lte, gt, gte, limit, fillCache, snapshot, tailing),
444
442
  keys_(keys),
445
443
  values_(values),
446
- keyEncoding_(keyEncoding),
447
- valueEncoding_(valueEncoding),
448
444
  highWaterMarkBytes_(highWaterMarkBytes) {}
449
445
 
450
446
  void Seek(const rocksdb::Slice& target) override {
@@ -454,8 +450,6 @@ struct Iterator final : public BaseIterator {
454
450
 
455
451
  const bool keys_;
456
452
  const bool values_;
457
- const Encoding keyEncoding_;
458
- const Encoding valueEncoding_;
459
453
  const size_t highWaterMarkBytes_;
460
454
  bool first_ = true;
461
455
  };
@@ -957,8 +951,8 @@ NAPI_METHOD(db_get_many) {
957
951
  Database* database;
958
952
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&database)));
959
953
 
960
- uint32_t size;
961
- NAPI_STATUS_THROWS(napi_get_array_length(env, argv[1], &size));
954
+ uint32_t count;
955
+ NAPI_STATUS_THROWS(napi_get_array_length(env, argv[1], &count));
962
956
 
963
957
  const auto options = argv[2];
964
958
 
@@ -968,9 +962,6 @@ NAPI_METHOD(db_get_many) {
968
962
  bool ignoreRangeDeletions = false;
969
963
  NAPI_STATUS_THROWS(GetProperty(env, options, "ignoreRangeDeletions", ignoreRangeDeletions));
970
964
 
971
- Encoding valueEncoding = Encoding::String;
972
- NAPI_STATUS_THROWS(GetProperty(env, options, "valueEncoding", valueEncoding));
973
-
974
965
  rocksdb::ColumnFamilyHandle* column = database->db->DefaultColumnFamily();
975
966
  NAPI_STATUS_THROWS(GetProperty(env, options, "column", column));
976
967
 
@@ -984,18 +975,17 @@ NAPI_METHOD(db_get_many) {
984
975
  snapshot.reset(database->db->GetSnapshot(), [=](const auto ptr) { database->db->ReleaseSnapshot(ptr); });
985
976
  }
986
977
 
987
- std::vector<rocksdb::PinnableSlice> keys{size};
978
+ std::vector<rocksdb::PinnableSlice> keys{count};
988
979
 
989
- for (uint32_t n = 0; n < size; n++) {
980
+ for (uint32_t n = 0; n < count; n++) {
990
981
  napi_value element;
991
982
  NAPI_STATUS_THROWS(napi_get_element(env, argv[1], n, &element));
992
983
  NAPI_STATUS_THROWS(GetValue(env, element, keys[n]));
993
984
  }
994
985
 
995
986
  struct State {
996
- std::vector<rocksdb::Status> statuses;
997
- std::vector<rocksdb::PinnableSlice> values;
998
- std::vector<rocksdb::Slice> keys;
987
+ std::vector<uint8_t> data;
988
+ std::vector<int32_t> sizes;
999
989
  };
1000
990
 
1001
991
  runAsync<State>(
@@ -1008,41 +998,61 @@ NAPI_METHOD(db_get_many) {
1008
998
  readOptions.ignore_range_deletions = ignoreRangeDeletions;
1009
999
  readOptions.optimize_multiget_for_io = true;
1010
1000
 
1011
- state.statuses.resize(size);
1012
- state.values.resize(size);
1013
- state.keys.resize(size);
1001
+ std::vector<rocksdb::Status> statuses{count};
1002
+ std::vector<rocksdb::PinnableSlice> values{count};
1003
+ std::vector<rocksdb::Slice> keys2{count};
1014
1004
 
1015
- for (auto n = 0; n < size; n++) {
1016
- state.keys[n] = keys[n];
1005
+ for (auto n = 0; n < count; n++) {
1006
+ keys2[n] = keys[n];
1017
1007
  }
1018
1008
 
1019
- database->db->MultiGet(readOptions, column, size, state.keys.data(), state.values.data(),
1020
- state.statuses.data());
1009
+ database->db->MultiGet(readOptions, column, count, keys2.data(), values.data(), statuses.data());
1021
1010
 
1022
- return rocksdb::Status::OK();
1023
- },
1024
- [=](auto& state, auto env, auto& argv) {
1025
- argv.resize(2);
1011
+ auto size = 0;
1012
+ for (auto n = 0; n < count; n++) {
1013
+ const auto valueSize = values[n].size();
1014
+ size += valueSize & 0x7 ? (valueSize | 0x7) + 1 : valueSize;
1015
+ }
1026
1016
 
1027
- NAPI_STATUS_RETURN(napi_create_array_with_length(env, size, &argv[1]));
1017
+ state.data.reserve(size);
1028
1018
 
1029
- for (uint32_t idx = 0; idx < size; idx++) {
1030
- const auto& status = state.statuses[idx];
1031
- const auto& value = state.values[idx];
1019
+ auto push = [&](rocksdb::Slice* slice){
1020
+ if (slice) {
1021
+ state.sizes.push_back(static_cast<int32_t>(slice->size()));
1022
+ std::copy_n(slice->data(), slice->size(), std::back_inserter(state.data));
1032
1023
 
1033
- napi_value element;
1034
- if (status.IsNotFound()) {
1035
- NAPI_STATUS_RETURN(napi_get_undefined(env, &element));
1024
+ if (state.data.size() & 0x7) {
1025
+ state.data.resize((state.data.size() | 0x7) + 1);
1026
+ }
1036
1027
  } else {
1037
- ROCKS_STATUS_RETURN_NAPI(status);
1038
- NAPI_STATUS_RETURN(Convert(env, &value, valueEncoding, element));
1028
+ state.sizes.push_back(-1);
1039
1029
  }
1040
- NAPI_STATUS_RETURN(napi_set_element(env, argv[1], idx, element));
1030
+ };
1031
+
1032
+ for (auto n = 0; n < count; n++) {
1033
+ push(statuses[n].ok() ? &values[n] : nullptr);
1034
+ }
1035
+
1036
+ return rocksdb::Status::OK();
1037
+ },
1038
+ [=](auto& state, auto env, auto& argv) {
1039
+ argv.resize(3);
1040
+
1041
+ if (state.sizes.size() > 0) {
1042
+ auto sizes = std::make_unique<std::vector<int32_t>>(std::move(state.sizes));
1043
+ NAPI_STATUS_RETURN(napi_create_external_buffer(env, sizes->size() * 4, sizes->data(), Finalize<std::vector<int32_t>>, sizes.get(), &argv[1]));
1044
+ sizes.release();
1045
+ } else {
1046
+ NAPI_STATUS_RETURN(napi_get_undefined(env, &argv[1]));
1041
1047
  }
1042
1048
 
1043
- state.statuses.clear();
1044
- state.values.clear();
1045
- state.keys.clear();
1049
+ if (state.data.size() > 0) {
1050
+ auto data = std::make_unique<std::vector<uint8_t>>(std::move(state.data));
1051
+ NAPI_STATUS_RETURN(napi_create_external_buffer(env, data->size(), data->data(), Finalize<std::vector<uint8_t>>, data.get(), &argv[2]));
1052
+ data.release();
1053
+ } else {
1054
+ NAPI_STATUS_RETURN(napi_get_undefined(env, &argv[2]));
1055
+ }
1046
1056
 
1047
1057
  return napi_ok;
1048
1058
  });
@@ -1208,12 +1218,6 @@ NAPI_METHOD(iterator_init) {
1208
1218
  bool fillCache = false;
1209
1219
  NAPI_STATUS_THROWS(GetProperty(env, options, "fillCache", fillCache));
1210
1220
 
1211
- Encoding keyEncoding = Encoding::String;
1212
- NAPI_STATUS_THROWS(GetProperty(env, options, "keyEncoding", keyEncoding));
1213
-
1214
- Encoding valueEncoding = Encoding::String;
1215
- NAPI_STATUS_THROWS(GetProperty(env, options, "valueEncoding", valueEncoding));
1216
-
1217
1221
  int32_t limit = -1;
1218
1222
  NAPI_STATUS_THROWS(GetProperty(env, options, "limit", limit));
1219
1223
 
@@ -1244,7 +1248,7 @@ NAPI_METHOD(iterator_init) {
1244
1248
  }
1245
1249
 
1246
1250
  auto iterator = std::unique_ptr<Iterator>(new Iterator(database, column, reverse, keys, values, limit, lt, lte, gt,
1247
- gte, fillCache, keyEncoding, valueEncoding, highWaterMarkBytes,
1251
+ gte, fillCache, highWaterMarkBytes,
1248
1252
  snapshot, tailing));
1249
1253
 
1250
1254
  napi_value result;
@@ -1285,13 +1289,14 @@ NAPI_METHOD(iterator_nextv) {
1285
1289
  Iterator* iterator;
1286
1290
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&iterator)));
1287
1291
 
1288
- uint32_t size;
1289
- NAPI_STATUS_THROWS(napi_get_value_uint32(env, argv[1], &size));
1292
+ uint32_t count;
1293
+ NAPI_STATUS_THROWS(napi_get_value_uint32(env, argv[1], &count));
1290
1294
 
1291
1295
  auto callback = argv[2];
1292
1296
 
1293
1297
  struct State {
1294
- std::vector<rocksdb::PinnableSlice> cache;
1298
+ std::vector<uint8_t> data;
1299
+ std::vector<int32_t> sizes;
1295
1300
  bool finished = false;
1296
1301
  };
1297
1302
 
@@ -1302,61 +1307,80 @@ NAPI_METHOD(iterator_nextv) {
1302
1307
  iterator->SeekToRange();
1303
1308
  }
1304
1309
 
1305
- state.cache.reserve(size * 2);
1306
- size_t bytesRead = 0;
1310
+ state.sizes.reserve(count * 2);
1311
+ state.data.reserve(iterator->highWaterMarkBytes_);
1312
+
1313
+ auto bytesRead = 0;
1314
+
1315
+ auto push = [&](const std::optional<rocksdb::Slice>& slice){
1316
+ if (slice) {
1317
+ state.sizes.push_back(static_cast<int32_t>(slice->size()));
1318
+ std::copy_n(slice->data(), slice->size(), std::back_inserter(state.data));
1307
1319
 
1320
+ if (state.data.size() & 0x7) {
1321
+ state.data.resize((state.data.size() | 0x7) + 1);
1322
+ }
1323
+
1324
+ bytesRead += slice->size();
1325
+ } else {
1326
+ state.sizes.push_back(-1);
1327
+ }
1328
+ };
1329
+
1330
+ auto status = rocksdb::Status::OK();
1308
1331
  while (true) {
1309
- if (!iterator->first_)
1332
+ if (!iterator->first_) {
1310
1333
  iterator->Next();
1311
- else
1334
+ } else {
1312
1335
  iterator->first_ = false;
1336
+ }
1313
1337
 
1314
- if (!iterator->Valid() || !iterator->Increment())
1338
+ if (!iterator->Valid() || !iterator->Increment()) {
1339
+ status = iterator->Status();
1340
+ state.finished = true;
1315
1341
  break;
1316
-
1317
- auto k = rocksdb::PinnableSlice();
1318
- auto v = rocksdb::PinnableSlice();
1342
+ }
1319
1343
 
1320
1344
  if (iterator->keys_ && iterator->values_) {
1321
- k.PinSelf(iterator->CurrentKey());
1322
- v.PinSelf(iterator->CurrentValue());
1345
+ push(iterator->CurrentKey());
1346
+ push(iterator->CurrentValue());
1323
1347
  } else if (iterator->keys_) {
1324
- k.PinSelf(iterator->CurrentKey());
1348
+ push(iterator->CurrentKey());
1349
+ push(std::nullopt);
1325
1350
  } else if (iterator->values_) {
1326
- v.PinSelf(iterator->CurrentValue());
1351
+ push(std::nullopt);
1352
+ push(iterator->CurrentValue());
1327
1353
  }
1328
1354
 
1329
- bytesRead += k.size() + v.size();
1330
- state.cache.push_back(std::move(k));
1331
- state.cache.push_back(std::move(v));
1332
-
1333
- if (bytesRead > iterator->highWaterMarkBytes_ || state.cache.size() / 2 >= size) {
1355
+ if (bytesRead > iterator->highWaterMarkBytes_ || state.sizes.size() / 2 >= count) {
1356
+ status = rocksdb::Status::OK();
1334
1357
  state.finished = false;
1335
- return rocksdb::Status::OK();
1358
+ break;
1336
1359
  }
1337
1360
  }
1338
1361
 
1339
- state.finished = true;
1340
-
1341
- return iterator->Status();
1362
+ return status;
1342
1363
  },
1343
1364
  [=](auto& state, auto env, auto& argv) {
1344
- argv.resize(3);
1345
-
1346
- NAPI_STATUS_RETURN(napi_create_array_with_length(env, state.cache.size(), &argv[1]));
1347
-
1348
- for (size_t n = 0; n < state.cache.size(); n += 2) {
1349
- napi_value key;
1350
- napi_value val;
1365
+ argv.resize(4);
1351
1366
 
1352
- NAPI_STATUS_RETURN(Convert(env, &state.cache[n + 0], iterator->keyEncoding_, key));
1353
- NAPI_STATUS_RETURN(Convert(env, &state.cache[n + 1], iterator->valueEncoding_, val));
1367
+ if (state.sizes.size() > 0) {
1368
+ auto sizes = std::make_unique<std::vector<int32_t>>(std::move(state.sizes));
1369
+ NAPI_STATUS_RETURN(napi_create_external_buffer(env, sizes->size() * 4, sizes->data(), Finalize<std::vector<int32_t>>, sizes.get(), &argv[1]));
1370
+ sizes.release();
1371
+ } else {
1372
+ NAPI_STATUS_RETURN(napi_get_undefined(env, &argv[1]));
1373
+ }
1354
1374
 
1355
- NAPI_STATUS_RETURN(napi_set_element(env, argv[1], static_cast<int>(n + 0), key));
1356
- NAPI_STATUS_RETURN(napi_set_element(env, argv[1], static_cast<int>(n + 1), val));
1375
+ if (state.data.size() > 0) {
1376
+ auto data = std::make_unique<std::vector<uint8_t>>(std::move(state.data));
1377
+ NAPI_STATUS_RETURN(napi_create_external_buffer(env, data->size(), data->data(), Finalize<std::vector<uint8_t>>, data.get(), &argv[2]));
1378
+ data.release();
1379
+ } else {
1380
+ NAPI_STATUS_RETURN(napi_get_undefined(env, &argv[2]));
1357
1381
  }
1358
1382
 
1359
- NAPI_STATUS_RETURN(napi_get_boolean(env, state.finished, &argv[2]));
1383
+ NAPI_STATUS_RETURN(napi_get_boolean(env, state.finished, &argv[3]));
1360
1384
 
1361
1385
  return napi_ok;
1362
1386
  });
package/index.js CHANGED
@@ -8,6 +8,7 @@ const { ChainedBatch } = require('./chained-batch')
8
8
  const { Iterator } = require('./iterator')
9
9
  const fs = require('node:fs')
10
10
  const assert = require('node:assert')
11
+ const { handleNextv } = require('./util')
11
12
 
12
13
  const kContext = Symbol('context')
13
14
  const kColumns = Symbol('columns')
@@ -148,16 +149,37 @@ class RocksLevel extends AbstractLevel {
148
149
  _getMany (keys, options, callback) {
149
150
  callback = fromCallback(callback, kPromise)
150
151
 
151
- const { keyEncoding } = options ?? EMPTY
152
-
153
- if (keyEncoding !== 'buffer') {
154
- keys = keys.map(key => typeof key === 'string' ? Buffer.from(key) : key)
155
- }
156
-
152
+ const { valueEncoding } = options ?? EMPTY
157
153
  try {
158
154
  this[kRef]()
159
- binding.db_get_many(this[kContext], keys, options ?? EMPTY, (err, val) => {
160
- callback(err, val)
155
+ binding.db_get_many(this[kContext], keys, options ?? EMPTY, (err, sizes, data) => {
156
+ if (err) {
157
+ callback(err)
158
+ } else {
159
+ data ??= Buffer.alloc(0)
160
+ sizes ??= Buffer.alloc(0)
161
+ const val = []
162
+ let offset = 0
163
+ const sizes32 = new Int32Array(sizes.buffer, sizes.byteOffset, sizes.byteLength / 4)
164
+ for (const size of sizes32) {
165
+ if (size < 0) {
166
+ val.push(undefined)
167
+ } else {
168
+ if (!valueEncoding || valueEncoding === 'buffer') {
169
+ val.push(data.subarray(offset, offset + size))
170
+ } else if (valueEncoding === 'slice') {
171
+ val.push({ buffer: data, byteOffset: offset, byteLength: size })
172
+ } else {
173
+ val.push(data.toString(valueEncoding, offset, offset + size))
174
+ }
175
+ offset += size
176
+ if (offset & 0x7) {
177
+ offset = (offset | 0x7) + 1
178
+ }
179
+ }
180
+ }
181
+ callback(null, val)
182
+ }
161
183
  this[kUnref]()
162
184
  })
163
185
  } catch (err) {
@@ -217,14 +239,10 @@ class RocksLevel extends AbstractLevel {
217
239
  return new ChainedBatch(this, this[kContext], (batch, context, options, callback) => {
218
240
  try {
219
241
  this[kRef]()
220
- const sync = binding.batch_write(this[kContext], context, options, (err) => {
242
+ binding.batch_write(this[kContext], context, options, (err) => {
221
243
  this[kUnref]()
222
244
  callback(err)
223
245
  })
224
- if (sync) {
225
- this[kUnref]()
226
- process.nextTick(callback, null)
227
- }
228
246
  } catch (err) {
229
247
  process.nextTick(callback, err)
230
248
  }
@@ -244,7 +262,7 @@ class RocksLevel extends AbstractLevel {
244
262
  assert(false)
245
263
  }
246
264
  }
247
- batch.write(callback)
265
+ batch._write(options, callback)
248
266
 
249
267
  return callback[kPromise]
250
268
  }
@@ -282,15 +300,17 @@ class RocksLevel extends AbstractLevel {
282
300
  const context = binding.iterator_init(this[kContext], options ?? {})
283
301
  try {
284
302
  this[kRef]()
285
- return await new Promise((resolve, reject) => binding.iterator_nextv(context, options.limit, (err, rows, finished) => {
286
- if (err) {
287
- reject(err)
288
- } else {
289
- resolve({
290
- rows,
291
- finished
292
- })
293
- }
303
+ return await new Promise((resolve, reject) => binding.iterator_nextv(context, options.limit, (err, sizes, buffer, finished) => {
304
+ handleNextv(err, sizes, buffer, finished, options, (err, rows, finished) => {
305
+ if (err) {
306
+ reject(err)
307
+ } else {
308
+ resolve({
309
+ rows,
310
+ finished
311
+ })
312
+ }
313
+ })
294
314
  }))
295
315
  } finally {
296
316
  binding.iterator_close(context)
package/iterator.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { fromCallback } = require('catering')
4
4
  const { AbstractIterator } = require('abstract-level')
5
+ const { handleNextv } = require('./util')
5
6
 
6
7
  const binding = require('./binding')
7
8
 
@@ -14,6 +15,7 @@ const kPosition = Symbol('position')
14
15
  const kHandleNext = Symbol('handleNext')
15
16
  const kHandleNextv = Symbol('handleNextv')
16
17
  const kCallback = Symbol('callback')
18
+ const kOptions = Symbol('options')
17
19
  const empty = []
18
20
 
19
21
  const registry = new FinalizationRegistry((context) => {
@@ -27,6 +29,7 @@ class Iterator extends AbstractIterator {
27
29
  this[kContext] = binding.iterator_init(context, options)
28
30
  registry.register(this, this[kContext], this[kContext])
29
31
 
32
+ this[kOptions] = { ...options }
30
33
  this[kHandleNext] = this[kHandleNext].bind(this)
31
34
  this[kHandleNextv] = this[kHandleNextv].bind(this)
32
35
  this[kCallback] = null
@@ -73,15 +76,19 @@ class Iterator extends AbstractIterator {
73
76
  return this
74
77
  }
75
78
 
76
- [kHandleNext] (err, items, finished) {
77
- const callback = this[kCallback]
78
- if (err) return callback(err)
79
+ [kHandleNext] (err, sizes, buffer, finished) {
80
+ handleNextv(err, sizes, buffer, finished, this[kOptions], (err, items, finished) => {
81
+ const callback = this[kCallback]
82
+ if (err) {
83
+ return callback(err)
84
+ }
79
85
 
80
- this[kCache] = items
81
- this[kFinished] = finished
82
- this[kPosition] = 0
86
+ this[kCache] = items
87
+ this[kFinished] = finished
88
+ this[kPosition] = 0
83
89
 
84
- this._next(callback)
90
+ this._next(callback)
91
+ })
85
92
  }
86
93
 
87
94
  _nextv (size, options, callback) {
@@ -98,17 +105,22 @@ class Iterator extends AbstractIterator {
98
105
  return callback[kPromise]
99
106
  }
100
107
 
101
- [kHandleNextv] (err, items, finished) {
102
- const callback = this[kCallback]
103
- if (err) return callback(err)
104
- this[kFinished] = finished
108
+ [kHandleNextv] (err, sizes, buffer, finished) {
109
+ handleNextv(err, sizes, buffer, finished, this[kOptions], (err, items, finished) => {
110
+ const callback = this[kCallback]
111
+ if (err) {
112
+ return callback(err)
113
+ }
114
+
115
+ this[kFinished] = finished
105
116
 
106
- const entries = []
107
- for (let n = 0; n < items.length; n += 2) {
108
- entries.push([items[n + 0], items[n + 1]])
109
- }
117
+ const entries = []
118
+ for (let n = 0; n < items.length; n += 2) {
119
+ entries.push([items[n + 0], items[n + 1]])
120
+ }
110
121
 
111
- callback(null, entries)
122
+ callback(null, entries)
123
+ })
112
124
  }
113
125
 
114
126
  _close (callback) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/rocksdb",
3
- "version": "10.2.0",
3
+ "version": "10.2.3",
4
4
  "description": "A low-level Node.js RocksDB binding",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
package/util.js ADDED
@@ -0,0 +1,39 @@
1
+ 'use strict'
2
+
3
+ function handleNextv (err, sizes, buffer, finished, options, callback) {
4
+ if (err) {
5
+ callback(err)
6
+ } else {
7
+ buffer ??= Buffer.alloc(0)
8
+ sizes ??= Buffer.alloc(0)
9
+
10
+ const { keyEncoding, valueEncoding } = options ?? {}
11
+
12
+ const rows = []
13
+ let offset = 0
14
+ const sizes32 = new Int32Array(sizes.buffer, sizes.byteOffset, sizes.byteLength / 4)
15
+ for (let n = 0; n < sizes32.length; n++) {
16
+ const size = sizes32[n]
17
+ const encoding = n & 1 ? valueEncoding : keyEncoding
18
+ if (size < 0) {
19
+ rows.push(undefined)
20
+ } else {
21
+ if (!encoding || encoding === 'buffer') {
22
+ rows.push(buffer.subarray(offset, offset + size))
23
+ } else if (encoding === 'slice') {
24
+ rows.push({ buffer, byteOffset: offset, byteLength: size })
25
+ } else {
26
+ rows.push(buffer.toString(encoding, offset, offset + size))
27
+ }
28
+ offset += size
29
+ if (offset & 0x7) {
30
+ offset = (offset | 0x7) + 1
31
+ }
32
+ }
33
+ }
34
+
35
+ callback(null, rows, finished)
36
+ }
37
+ }
38
+
39
+ exports.handleNextv = handleNextv