@nxtedition/rocksdb 10.2.0 → 10.2.2

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<std::optional<size_t>> sizes;
999
989
  };
1000
990
 
1001
991
  runAsync<State>(
@@ -1008,41 +998,69 @@ 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());
1010
+
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
+ }
1016
+
1017
+ state.data.reserve(size);
1018
+
1019
+ auto push = [&](rocksdb::Slice* slice){
1020
+ if (slice) {
1021
+ state.sizes.push_back(slice->size());
1022
+ std::copy_n(slice->data(), slice->size(), std::back_inserter(state.data));
1023
+
1024
+ if (state.data.size() & 0x7) {
1025
+ state.data.resize((state.data.size() | 0x7) + 1);
1026
+ }
1027
+ } else {
1028
+ state.sizes.push_back(std::nullopt);
1029
+ }
1030
+ };
1031
+
1032
+ for (auto n = 0; n < count; n++) {
1033
+ push(statuses[n].ok() ? &values[n] : nullptr);
1034
+ }
1021
1035
 
1022
1036
  return rocksdb::Status::OK();
1023
1037
  },
1024
1038
  [=](auto& state, auto env, auto& argv) {
1025
- argv.resize(2);
1039
+ argv.resize(3);
1026
1040
 
1027
- NAPI_STATUS_RETURN(napi_create_array_with_length(env, size, &argv[1]));
1041
+ const auto count = state.sizes.size();
1028
1042
 
1029
- for (uint32_t idx = 0; idx < size; idx++) {
1030
- const auto& status = state.statuses[idx];
1031
- const auto& value = state.values[idx];
1043
+ NAPI_STATUS_RETURN(napi_create_array_with_length(env, count, &argv[1]));
1044
+
1045
+ for (uint32_t idx = 0; idx < count; idx++) {
1046
+ const auto& maybeSize = state.sizes[idx];
1032
1047
 
1033
1048
  napi_value element;
1034
- if (status.IsNotFound()) {
1035
- NAPI_STATUS_RETURN(napi_get_undefined(env, &element));
1049
+ if (maybeSize) {
1050
+ NAPI_STATUS_RETURN(napi_create_uint32(env, *maybeSize, &element));
1036
1051
  } else {
1037
- ROCKS_STATUS_RETURN_NAPI(status);
1038
- NAPI_STATUS_RETURN(Convert(env, &value, valueEncoding, element));
1052
+ NAPI_STATUS_RETURN(napi_get_undefined(env, &element));
1039
1053
  }
1040
1054
  NAPI_STATUS_RETURN(napi_set_element(env, argv[1], idx, element));
1041
1055
  }
1042
1056
 
1043
- state.statuses.clear();
1044
- state.values.clear();
1045
- state.keys.clear();
1057
+ if (state.data.size() > 0) {
1058
+ auto data = std::make_unique<std::vector<uint8_t>>(std::move(state.data));
1059
+ NAPI_STATUS_RETURN(napi_create_external_buffer(env, data->size(), data->data(), Finalize<std::vector<uint8_t>>, data.get(), &argv[2]));
1060
+ data.release();
1061
+ } else {
1062
+ NAPI_STATUS_RETURN(napi_get_undefined(env, &argv[2]));
1063
+ }
1046
1064
 
1047
1065
  return napi_ok;
1048
1066
  });
@@ -1208,12 +1226,6 @@ NAPI_METHOD(iterator_init) {
1208
1226
  bool fillCache = false;
1209
1227
  NAPI_STATUS_THROWS(GetProperty(env, options, "fillCache", fillCache));
1210
1228
 
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
1229
  int32_t limit = -1;
1218
1230
  NAPI_STATUS_THROWS(GetProperty(env, options, "limit", limit));
1219
1231
 
@@ -1244,7 +1256,7 @@ NAPI_METHOD(iterator_init) {
1244
1256
  }
1245
1257
 
1246
1258
  auto iterator = std::unique_ptr<Iterator>(new Iterator(database, column, reverse, keys, values, limit, lt, lte, gt,
1247
- gte, fillCache, keyEncoding, valueEncoding, highWaterMarkBytes,
1259
+ gte, fillCache, highWaterMarkBytes,
1248
1260
  snapshot, tailing));
1249
1261
 
1250
1262
  napi_value result;
@@ -1285,13 +1297,14 @@ NAPI_METHOD(iterator_nextv) {
1285
1297
  Iterator* iterator;
1286
1298
  NAPI_STATUS_THROWS(napi_get_value_external(env, argv[0], reinterpret_cast<void**>(&iterator)));
1287
1299
 
1288
- uint32_t size;
1289
- NAPI_STATUS_THROWS(napi_get_value_uint32(env, argv[1], &size));
1300
+ uint32_t count;
1301
+ NAPI_STATUS_THROWS(napi_get_value_uint32(env, argv[1], &count));
1290
1302
 
1291
1303
  auto callback = argv[2];
1292
1304
 
1293
1305
  struct State {
1294
- std::vector<rocksdb::PinnableSlice> cache;
1306
+ std::vector<uint8_t> data;
1307
+ std::vector<std::optional<size_t>> sizes;
1295
1308
  bool finished = false;
1296
1309
  };
1297
1310
 
@@ -1302,61 +1315,88 @@ NAPI_METHOD(iterator_nextv) {
1302
1315
  iterator->SeekToRange();
1303
1316
  }
1304
1317
 
1305
- state.cache.reserve(size * 2);
1306
- size_t bytesRead = 0;
1318
+ state.sizes.reserve(count * 2);
1319
+ state.data.reserve(iterator->highWaterMarkBytes_);
1320
+
1321
+ auto bytesRead = 0;
1322
+
1323
+ auto push = [&](const std::optional<rocksdb::Slice>& slice){
1324
+ if (slice) {
1325
+ state.sizes.push_back(slice->size());
1326
+ std::copy_n(slice->data(), slice->size(), std::back_inserter(state.data));
1307
1327
 
1328
+ if (state.data.size() & 0x7) {
1329
+ state.data.resize((state.data.size() | 0x7) + 1);
1330
+ }
1331
+
1332
+ bytesRead += slice->size();
1333
+ } else {
1334
+ state.sizes.push_back(std::nullopt);
1335
+ }
1336
+ };
1337
+
1338
+ auto status = rocksdb::Status::OK();
1308
1339
  while (true) {
1309
- if (!iterator->first_)
1340
+ if (!iterator->first_) {
1310
1341
  iterator->Next();
1311
- else
1342
+ } else {
1312
1343
  iterator->first_ = false;
1344
+ }
1313
1345
 
1314
- if (!iterator->Valid() || !iterator->Increment())
1346
+ if (!iterator->Valid() || !iterator->Increment()) {
1347
+ status = iterator->Status();
1348
+ state.finished = true;
1315
1349
  break;
1316
-
1317
- auto k = rocksdb::PinnableSlice();
1318
- auto v = rocksdb::PinnableSlice();
1350
+ }
1319
1351
 
1320
1352
  if (iterator->keys_ && iterator->values_) {
1321
- k.PinSelf(iterator->CurrentKey());
1322
- v.PinSelf(iterator->CurrentValue());
1353
+ push(iterator->CurrentKey());
1354
+ push(iterator->CurrentValue());
1323
1355
  } else if (iterator->keys_) {
1324
- k.PinSelf(iterator->CurrentKey());
1356
+ push(iterator->CurrentKey());
1357
+ push(std::nullopt);
1325
1358
  } else if (iterator->values_) {
1326
- v.PinSelf(iterator->CurrentValue());
1359
+ push(std::nullopt);
1360
+ push(iterator->CurrentValue());
1327
1361
  }
1328
1362
 
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) {
1363
+ if (bytesRead > iterator->highWaterMarkBytes_ || state.sizes.size() / 2 >= count) {
1364
+ status = rocksdb::Status::OK();
1334
1365
  state.finished = false;
1335
- return rocksdb::Status::OK();
1366
+ break;
1336
1367
  }
1337
1368
  }
1338
1369
 
1339
- state.finished = true;
1340
-
1341
- return iterator->Status();
1370
+ return status;
1342
1371
  },
1343
1372
  [=](auto& state, auto env, auto& argv) {
1344
- argv.resize(3);
1373
+ argv.resize(4);
1374
+
1375
+ const auto count = state.sizes.size();
1345
1376
 
1346
- NAPI_STATUS_RETURN(napi_create_array_with_length(env, state.cache.size(), &argv[1]));
1377
+ NAPI_STATUS_RETURN(napi_create_array_with_length(env, count, &argv[1]));
1347
1378
 
1348
- for (size_t n = 0; n < state.cache.size(); n += 2) {
1349
- napi_value key;
1350
- napi_value val;
1379
+ for (uint32_t idx = 0; idx < count; idx++) {
1380
+ const auto& maybeSize = state.sizes[idx];
1351
1381
 
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));
1382
+ napi_value element;
1383
+ if (maybeSize) {
1384
+ NAPI_STATUS_RETURN(napi_create_uint32(env, *maybeSize, &element));
1385
+ } else {
1386
+ NAPI_STATUS_RETURN(napi_get_undefined(env, &element));
1387
+ }
1388
+ NAPI_STATUS_RETURN(napi_set_element(env, argv[1], idx, element));
1389
+ }
1354
1390
 
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));
1391
+ if (state.data.size() > 0) {
1392
+ auto data = std::make_unique<std::vector<uint8_t>>(std::move(state.data));
1393
+ NAPI_STATUS_RETURN(napi_create_external_buffer(env, data->size(), data->data(), Finalize<std::vector<uint8_t>>, data.get(), &argv[2]));
1394
+ data.release();
1395
+ } else {
1396
+ NAPI_STATUS_RETURN(napi_get_undefined(env, &argv[2]));
1357
1397
  }
1358
1398
 
1359
- NAPI_STATUS_RETURN(napi_get_boolean(env, state.finished, &argv[2]));
1399
+ NAPI_STATUS_RETURN(napi_get_boolean(env, state.finished, &argv[3]));
1360
1400
 
1361
1401
  return napi_ok;
1362
1402
  });
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,35 @@ 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, buffer) => {
156
+ if (err) {
157
+ callback(err)
158
+ } else {
159
+ buffer ??= Buffer.alloc(0)
160
+ const val = []
161
+ let offset = 0
162
+ for (const size of sizes) {
163
+ if (size == null) {
164
+ val.push(undefined)
165
+ } else {
166
+ if (!valueEncoding || valueEncoding === 'buffer') {
167
+ val.push(buffer.subarray(offset, offset + size))
168
+ } else if (valueEncoding === 'slice') {
169
+ val.push({ buffer, byteOffset: offset, byteLength: size })
170
+ } else {
171
+ val.push(buffer.toString(valueEncoding, offset, offset + size))
172
+ }
173
+ offset += size
174
+ if (offset & 0x7) {
175
+ offset = (offset | 0x7) + 1
176
+ }
177
+ }
178
+ }
179
+ callback(null, val)
180
+ }
161
181
  this[kUnref]()
162
182
  })
163
183
  } catch (err) {
@@ -217,14 +237,10 @@ class RocksLevel extends AbstractLevel {
217
237
  return new ChainedBatch(this, this[kContext], (batch, context, options, callback) => {
218
238
  try {
219
239
  this[kRef]()
220
- const sync = binding.batch_write(this[kContext], context, options, (err) => {
240
+ binding.batch_write(this[kContext], context, options, (err) => {
221
241
  this[kUnref]()
222
242
  callback(err)
223
243
  })
224
- if (sync) {
225
- this[kUnref]()
226
- process.nextTick(callback, null)
227
- }
228
244
  } catch (err) {
229
245
  process.nextTick(callback, err)
230
246
  }
@@ -244,7 +260,7 @@ class RocksLevel extends AbstractLevel {
244
260
  assert(false)
245
261
  }
246
262
  }
247
- batch.write(callback)
263
+ batch._write(options, callback)
248
264
 
249
265
  return callback[kPromise]
250
266
  }
@@ -282,15 +298,17 @@ class RocksLevel extends AbstractLevel {
282
298
  const context = binding.iterator_init(this[kContext], options ?? {})
283
299
  try {
284
300
  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
- }
301
+ return await new Promise((resolve, reject) => binding.iterator_nextv(context, options.limit, (err, sizes, buffer, finished) => {
302
+ handleNextv(err, sizes, buffer, finished, options, (err, rows, finished) => {
303
+ if (err) {
304
+ reject(err)
305
+ } else {
306
+ resolve({
307
+ rows,
308
+ finished
309
+ })
310
+ }
311
+ })
294
312
  }))
295
313
  } finally {
296
314
  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.2",
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,37 @@
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
+
9
+ const { keyEncoding, valueEncoding } = options ?? {}
10
+
11
+ const rows = []
12
+ let offset = 0
13
+ for (let n = 0; n < sizes.length; n++) {
14
+ const size = sizes[n]
15
+ const encoding = n & 1 ? valueEncoding : keyEncoding
16
+ if (size == null) {
17
+ rows.push(undefined)
18
+ } else {
19
+ if (!encoding || encoding === 'buffer') {
20
+ rows.push(buffer.subarray(offset, offset + size))
21
+ } else if (encoding === 'slice') {
22
+ rows.push({ buffer, byteOffset: offset, byteLength: size })
23
+ } else {
24
+ rows.push(buffer.toString(encoding, offset, offset + size))
25
+ }
26
+ offset += size
27
+ if (offset & 0x7) {
28
+ offset = (offset | 0x7) + 1
29
+ }
30
+ }
31
+ }
32
+
33
+ callback(null, rows, finished)
34
+ }
35
+ }
36
+
37
+ exports.handleNextv = handleNextv