Skip to content

Commit 406cc74

Browse files
committed
Handle view encoding (Uint8Array) natively
No longer need to transcode view to buffer. Needs benchmarks.
1 parent 20f4a34 commit 406cc74

File tree

3 files changed

+76
-66
lines changed

3 files changed

+76
-66
lines changed

binding.cc

Lines changed: 73 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ static void iterator_close_do (napi_env env, Iterator* iterator, napi_value cb);
4848
#define NAPI_ARGV_UTF8_NEW(name, i) \
4949
NAPI_UTF8_NEW(name, argv[i])
5050

51+
// TODO: consider using encoding options instead of type checking
5152
#define LD_STRING_OR_BUFFER_TO_COPY(env, from, to) \
5253
char* to##Ch_ = 0; \
5354
size_t to##Sz_ = 0; \
@@ -61,6 +62,18 @@ static void iterator_close_do (napi_env env, Iterator* iterator, napi_value cb);
6162
napi_get_buffer_info(env, from, (void **)&buf, &to##Sz_); \
6263
to##Ch_ = new char[to##Sz_]; \
6364
memcpy(to##Ch_, buf, to##Sz_); \
65+
} else { \
66+
char* buf = 0; \
67+
napi_typedarray_type type; \
68+
napi_status status = napi_get_typedarray_info(env, from, &type, &to##Sz_, (void **)&buf, NULL, NULL); \
69+
if (status != napi_ok || type != napi_typedarray_type::napi_uint8_array) { \
70+
/* TODO: refactor so that we can napi_throw_type_error() here */ \
71+
to##Sz_ = 0; \
72+
to##Ch_ = new char[to##Sz_]; \
73+
} else { \
74+
to##Ch_ = new char[to##Sz_]; \
75+
memcpy(to##Ch_, buf, to##Sz_); \
76+
} \
6477
}
6578

6679
/*********************************************************************
@@ -149,20 +162,32 @@ static bool BooleanProperty (napi_env env, napi_value obj, const char* key,
149162
return DEFAULT;
150163
}
151164

165+
enum Encoding { buffer, utf8, view };
166+
152167
/**
153-
* Returns true if the options object contains an encoding option that is "buffer"
168+
* Returns internal Encoding enum matching the given encoding option.
154169
*/
155-
static bool EncodingIsBuffer (napi_env env, napi_value options, const char* option) {
170+
static Encoding GetEncoding (napi_env env, napi_value options, const char* option) {
156171
napi_value value;
157172
size_t size;
173+
char* buf = new char[2];
174+
Encoding encoding = Encoding::utf8;
158175

159176
if (napi_get_named_property(env, options, option, &value) == napi_ok &&
160-
napi_get_value_string_utf8(env, value, NULL, 0, &size) == napi_ok) {
161-
// Value is either "buffer" or "utf8" so we can tell them apart just by size
162-
return size == 6;
177+
napi_get_value_string_utf8(env, value, buf, 2, &size) == napi_ok && size == 1) {
178+
// Value is either "buffer", "utf8" or "view" so we only have to read the first char
179+
switch (buf[0]) {
180+
case 'b':
181+
encoding = Encoding::buffer;
182+
break;
183+
case 'v':
184+
encoding = Encoding::view;
185+
break;
186+
}
163187
}
164188

165-
return false;
189+
delete[] buf;
190+
return encoding;
166191
}
167192

168193
/**
@@ -234,35 +259,17 @@ static leveldb::Slice ToSlice (napi_env env, napi_value from) {
234259
}
235260

236261
/**
237-
* Returns length of string or buffer
238-
*/
239-
static size_t StringOrBufferLength (napi_env env, napi_value value) {
240-
size_t size = 0;
241-
242-
if (IsString(env, value)) {
243-
napi_get_value_string_utf8(env, value, NULL, 0, &size);
244-
} else if (IsBuffer(env, value)) {
245-
char* buf;
246-
napi_get_buffer_info(env, value, (void **)&buf, &size);
247-
}
248-
249-
return size;
250-
}
251-
252-
/**
253-
* Takes a Buffer or string property 'name' from 'opts'.
254-
* Returns null if the property does not exist or is zero-length.
262+
* Takes a Buffer, string or Uint8Array property 'name' from 'opts'.
263+
* Returns null if the property does not exist.
255264
*/
256265
static std::string* RangeOption (napi_env env, napi_value opts, const char* name) {
257266
if (HasProperty(env, opts, name)) {
258267
napi_value value = GetProperty(env, opts, name);
259-
260-
if (StringOrBufferLength(env, value) >= 0) {
261-
LD_STRING_OR_BUFFER_TO_COPY(env, value, to);
262-
std::string* result = new std::string(toCh_, toSz_);
263-
delete [] toCh_;
264-
return result;
265-
}
268+
// TODO: we can avoid a copy here
269+
LD_STRING_OR_BUFFER_TO_COPY(env, value, to);
270+
std::string* result = new std::string(toCh_, toSz_);
271+
delete [] toCh_;
272+
return result;
266273
}
267274

268275
return NULL;
@@ -281,8 +288,7 @@ static std::vector<std::string>* KeyArray (napi_env env, napi_value arr) {
281288
for (uint32_t i = 0; i < length; i++) {
282289
napi_value element;
283290

284-
if (napi_get_element(env, arr, i, &element) == napi_ok &&
285-
StringOrBufferLength(env, element) >= 0) {
291+
if (napi_get_element(env, arr, i, &element) == napi_ok) {
286292
LD_STRING_OR_BUFFER_TO_COPY(env, element, to);
287293
result->emplace_back(toCh_, toSz_);
288294
delete [] toCh_;
@@ -322,29 +328,32 @@ struct Entry {
322328
: key_(key->data(), key->size()),
323329
value_(value->data(), value->size()) {}
324330

325-
void ConvertByMode (napi_env env, Mode mode, const bool keyAsBuffer, const bool valueAsBuffer, napi_value* result) {
331+
void ConvertByMode (napi_env env, Mode mode, const Encoding keyEncoding, const Encoding valueEncoding, napi_value* result) {
326332
if (mode == Mode::entries) {
327333
napi_create_array_with_length(env, 2, result);
328334

329335
napi_value keyElement;
330336
napi_value valueElement;
331337

332-
Convert(env, &key_, keyAsBuffer, &keyElement);
333-
Convert(env, &value_, valueAsBuffer, &valueElement);
338+
Convert(env, &key_, keyEncoding, &keyElement);
339+
Convert(env, &value_, valueEncoding, &valueElement);
334340

335341
napi_set_element(env, *result, 0, keyElement);
336342
napi_set_element(env, *result, 1, valueElement);
337343
} else if (mode == Mode::keys) {
338-
Convert(env, &key_, keyAsBuffer, result);
344+
Convert(env, &key_, keyEncoding, result);
339345
} else {
340-
Convert(env, &value_, valueAsBuffer, result);
346+
Convert(env, &value_, valueEncoding, result);
341347
}
342348
}
343349

344-
static void Convert (napi_env env, const std::string* s, const bool asBuffer, napi_value* result) {
350+
static void Convert (napi_env env, const std::string* s, const Encoding encoding, napi_value* result) {
345351
if (s == NULL) {
346352
napi_get_undefined(env, result);
347-
} else if (asBuffer) {
353+
} else if (encoding == Encoding::buffer) {
354+
napi_create_buffer_copy(env, s->size(), s->data(), NULL, result);
355+
} else if (encoding == Encoding::view) {
356+
// TODO: use napi_create_typedarray if performance is equal or better
348357
napi_create_buffer_copy(env, s->size(), s->data(), NULL, result);
349358
} else {
350359
napi_create_string_utf8(env, s->data(), s->size(), result);
@@ -830,15 +839,15 @@ struct Iterator final : public BaseIterator {
830839
std::string* gt,
831840
std::string* gte,
832841
const bool fillCache,
833-
const bool keyAsBuffer,
834-
const bool valueAsBuffer,
842+
const Encoding keyEncoding,
843+
const Encoding valueEncoding,
835844
const uint32_t highWaterMarkBytes)
836845
: BaseIterator(database, reverse, lt, lte, gt, gte, limit, fillCache),
837846
id_(id),
838847
keys_(keys),
839848
values_(values),
840-
keyAsBuffer_(keyAsBuffer),
841-
valueAsBuffer_(valueAsBuffer),
849+
keyEncoding_(keyEncoding),
850+
valueEncoding_(valueEncoding),
842851
highWaterMarkBytes_(highWaterMarkBytes),
843852
first_(true),
844853
nexting_(false),
@@ -897,8 +906,8 @@ struct Iterator final : public BaseIterator {
897906
const uint32_t id_;
898907
const bool keys_;
899908
const bool values_;
900-
const bool keyAsBuffer_;
901-
const bool valueAsBuffer_;
909+
const Encoding keyEncoding_;
910+
const Encoding valueEncoding_;
902911
const uint32_t highWaterMarkBytes_;
903912
bool first_;
904913
bool nexting_;
@@ -1151,11 +1160,11 @@ struct GetWorker final : public PriorityWorker {
11511160
Database* database,
11521161
napi_value callback,
11531162
leveldb::Slice key,
1154-
const bool asBuffer,
1163+
const Encoding encoding,
11551164
const bool fillCache)
11561165
: PriorityWorker(env, database, callback, "classic_level.db.get"),
11571166
key_(key),
1158-
asBuffer_(asBuffer) {
1167+
encoding_(encoding) {
11591168
options_.fill_cache = fillCache;
11601169
}
11611170

@@ -1170,15 +1179,15 @@ struct GetWorker final : public PriorityWorker {
11701179
void HandleOKCallback (napi_env env, napi_value callback) override {
11711180
napi_value argv[2];
11721181
napi_get_null(env, &argv[0]);
1173-
Entry::Convert(env, &value_, asBuffer_, &argv[1]);
1182+
Entry::Convert(env, &value_, encoding_, &argv[1]);
11741183
CallFunction(env, callback, 2, argv);
11751184
}
11761185

11771186
private:
11781187
leveldb::ReadOptions options_;
11791188
leveldb::Slice key_;
11801189
std::string value_;
1181-
const bool asBuffer_;
1190+
const Encoding encoding_;
11821191
};
11831192

11841193
/**
@@ -1190,11 +1199,11 @@ NAPI_METHOD(db_get) {
11901199

11911200
leveldb::Slice key = ToSlice(env, argv[1]);
11921201
napi_value options = argv[2];
1193-
const bool asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1202+
const Encoding encoding = GetEncoding(env, options, "valueEncoding");
11941203
const bool fillCache = BooleanProperty(env, options, "fillCache", true);
11951204
napi_value callback = argv[3];
11961205

1197-
GetWorker* worker = new GetWorker(env, database, callback, key, asBuffer,
1206+
GetWorker* worker = new GetWorker(env, database, callback, key, encoding,
11981207
fillCache);
11991208
worker->Queue(env);
12001209

@@ -1209,10 +1218,10 @@ struct GetManyWorker final : public PriorityWorker {
12091218
Database* database,
12101219
const std::vector<std::string>* keys,
12111220
napi_value callback,
1212-
const bool valueAsBuffer,
1221+
const Encoding valueEncoding,
12131222
const bool fillCache)
12141223
: PriorityWorker(env, database, callback, "classic_level.get.many"),
1215-
keys_(keys), valueAsBuffer_(valueAsBuffer) {
1224+
keys_(keys), valueEncoding_(valueEncoding) {
12161225
options_.fill_cache = fillCache;
12171226
options_.snapshot = database->NewSnapshot();
12181227
}
@@ -1254,7 +1263,7 @@ struct GetManyWorker final : public PriorityWorker {
12541263
for (size_t idx = 0; idx < size; idx++) {
12551264
std::string* value = cache_[idx];
12561265
napi_value element;
1257-
Entry::Convert(env, value, valueAsBuffer_, &element);
1266+
Entry::Convert(env, value, valueEncoding_, &element);
12581267
napi_set_element(env, array, static_cast<uint32_t>(idx), element);
12591268
if (value != NULL) delete value;
12601269
}
@@ -1268,7 +1277,7 @@ struct GetManyWorker final : public PriorityWorker {
12681277
private:
12691278
leveldb::ReadOptions options_;
12701279
const std::vector<std::string>* keys_;
1271-
const bool valueAsBuffer_;
1280+
const Encoding valueEncoding_;
12721281
std::vector<std::string*> cache_;
12731282
};
12741283

@@ -1281,12 +1290,12 @@ NAPI_METHOD(db_get_many) {
12811290

12821291
const std::vector<std::string>* keys = KeyArray(env, argv[1]);
12831292
napi_value options = argv[2];
1284-
const bool asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1293+
const Encoding valueEncoding = GetEncoding(env, options, "valueEncoding");
12851294
const bool fillCache = BooleanProperty(env, options, "fillCache", true);
12861295
napi_value callback = argv[3];
12871296

12881297
GetManyWorker* worker = new GetManyWorker(
1289-
env, database, keys, callback, asBuffer, fillCache
1298+
env, database, keys, callback, valueEncoding, fillCache
12901299
);
12911300

12921301
worker->Queue(env);
@@ -1630,8 +1639,8 @@ NAPI_METHOD(iterator_init) {
16301639
const bool keys = BooleanProperty(env, options, "keys", true);
16311640
const bool values = BooleanProperty(env, options, "values", true);
16321641
const bool fillCache = BooleanProperty(env, options, "fillCache", false);
1633-
const bool keyAsBuffer = EncodingIsBuffer(env, options, "keyEncoding");
1634-
const bool valueAsBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1642+
const Encoding keyEncoding = GetEncoding(env, options, "keyEncoding");
1643+
const Encoding valueEncoding = GetEncoding(env, options, "valueEncoding");
16351644
const int limit = Int32Property(env, options, "limit", -1);
16361645
const uint32_t highWaterMarkBytes = Uint32Property(env, options, "highWaterMarkBytes", 16 * 1024);
16371646

@@ -1643,7 +1652,7 @@ NAPI_METHOD(iterator_init) {
16431652
const uint32_t id = database->currentIteratorId_++;
16441653
Iterator* iterator = new Iterator(database, id, reverse, keys,
16451654
values, limit, lt, lte, gt, gte, fillCache,
1646-
keyAsBuffer, valueAsBuffer, highWaterMarkBytes);
1655+
keyEncoding, valueEncoding, highWaterMarkBytes);
16471656
napi_value result;
16481657

16491658
NAPI_STATUS_THROWS(napi_create_external(env, iterator,
@@ -1761,12 +1770,12 @@ struct NextWorker final : public BaseWorker {
17611770
napi_value jsArray;
17621771
napi_create_array_with_length(env, size, &jsArray);
17631772

1764-
const bool kab = iterator_->keyAsBuffer_;
1765-
const bool vab = iterator_->valueAsBuffer_;
1773+
const Encoding ke = iterator_->keyEncoding_;
1774+
const Encoding ve = iterator_->valueEncoding_;
17661775

17671776
for (uint32_t idx = 0; idx < size; idx++) {
17681777
napi_value element;
1769-
iterator_->cache_[idx].ConvertByMode(env, Mode::entries, kab, vab, &element);
1778+
iterator_->cache_[idx].ConvertByMode(env, Mode::entries, ke, ve, &element);
17701779
napi_set_element(env, jsArray, idx, element);
17711780
}
17721781

index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
* @template VDefault The default type of values if not overridden on operations.
2929
*/
3030
declare class ClassicLevel<KDefault = string, VDefault = string>
31-
extends AbstractLevel<string | Buffer, KDefault, VDefault> {
31+
extends AbstractLevel<string | Buffer | Uint8Array, KDefault, VDefault> {
3232
/**
3333
* Database constructor.
3434
*

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ class ClassicLevel extends AbstractLevel {
2828
super({
2929
encodings: {
3030
buffer: true,
31-
utf8: true
31+
utf8: true,
32+
view: true
3233
},
3334
seek: true,
3435
createIfMissing: true,

0 commit comments

Comments
 (0)