Skip to content

Commit 76253c0

Browse files
committed
Handle view encoding (Uint8Array) natively
Replaces the internal asBuffer option with an encoding option that can be one of buffer, utf8 or view, and converts data accordingly. No longer have to transcode view to buffer.
1 parent d6437b4 commit 76253c0

3 files changed

Lines changed: 71 additions & 67 deletions

File tree

binding.cc

Lines changed: 68 additions & 65 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,26 @@ 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;
157-
size_t size;
172+
size_t copied;
173+
char buf[2];
158174

159175
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;
176+
napi_get_value_string_utf8(env, value, buf, 2, &copied) == napi_ok && copied == 1) {
177+
// Value is either "buffer", "utf8" or "view" so we only have to read the first char
178+
switch (buf[0]) {
179+
case 'b': return Encoding::buffer;
180+
case 'v': return Encoding::view;
181+
}
163182
}
164183

165-
return false;
184+
return Encoding::utf8;
166185
}
167186

168187
/**
@@ -234,35 +253,17 @@ static leveldb::Slice ToSlice (napi_env env, napi_value from) {
234253
}
235254

236255
/**
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.
256+
* Takes a Buffer, string or Uint8Array property 'name' from 'opts'.
257+
* Returns null if the property does not exist.
255258
*/
256259
static std::string* RangeOption (napi_env env, napi_value opts, const char* name) {
257260
if (HasProperty(env, opts, name)) {
258261
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-
}
262+
// TODO: we can avoid a copy here
263+
LD_STRING_OR_BUFFER_TO_COPY(env, value, to);
264+
std::string* result = new std::string(toCh_, toSz_);
265+
delete [] toCh_;
266+
return result;
266267
}
267268

268269
return NULL;
@@ -281,8 +282,7 @@ static std::vector<std::string> KeyArray (napi_env env, napi_value arr) {
281282
for (uint32_t i = 0; i < length; i++) {
282283
napi_value element;
283284

284-
if (napi_get_element(env, arr, i, &element) == napi_ok &&
285-
StringOrBufferLength(env, element) >= 0) {
285+
if (napi_get_element(env, arr, i, &element) == napi_ok) {
286286
LD_STRING_OR_BUFFER_TO_COPY(env, element, to);
287287
result.emplace_back(toCh_, toSz_);
288288
delete [] toCh_;
@@ -322,29 +322,32 @@ struct Entry {
322322
: key_(key.data(), key.size()),
323323
value_(value.data(), value.size()) {}
324324

325-
void ConvertByMode (napi_env env, Mode mode, const bool keyAsBuffer, const bool valueAsBuffer, napi_value& result) {
325+
void ConvertByMode (napi_env env, Mode mode, const Encoding keyEncoding, const Encoding valueEncoding, napi_value& result) {
326326
if (mode == Mode::entries) {
327327
napi_create_array_with_length(env, 2, &result);
328328

329329
napi_value keyElement;
330330
napi_value valueElement;
331331

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

335335
napi_set_element(env, result, 0, keyElement);
336336
napi_set_element(env, result, 1, valueElement);
337337
} else if (mode == Mode::keys) {
338-
Convert(env, &key_, keyAsBuffer, result);
338+
Convert(env, &key_, keyEncoding, result);
339339
} else {
340-
Convert(env, &value_, valueAsBuffer, result);
340+
Convert(env, &value_, valueEncoding, result);
341341
}
342342
}
343343

344-
static void Convert (napi_env env, const std::string* s, const bool asBuffer, napi_value& result) {
344+
static void Convert (napi_env env, const std::string* s, const Encoding encoding, napi_value& result) {
345345
if (s == NULL) {
346346
napi_get_undefined(env, &result);
347-
} else if (asBuffer) {
347+
} else if (encoding == Encoding::buffer) {
348+
napi_create_buffer_copy(env, s->size(), s->data(), NULL, &result);
349+
} else if (encoding == Encoding::view) {
350+
// TODO: use napi_create_typedarray if performance is equal or better
348351
napi_create_buffer_copy(env, s->size(), s->data(), NULL, &result);
349352
} else {
350353
napi_create_string_utf8(env, s->data(), s->size(), &result);
@@ -830,15 +833,15 @@ struct Iterator final : public BaseIterator {
830833
std::string* gt,
831834
std::string* gte,
832835
const bool fillCache,
833-
const bool keyAsBuffer,
834-
const bool valueAsBuffer,
836+
const Encoding keyEncoding,
837+
const Encoding valueEncoding,
835838
const uint32_t highWaterMarkBytes)
836839
: BaseIterator(database, reverse, lt, lte, gt, gte, limit, fillCache),
837840
id_(id),
838841
keys_(keys),
839842
values_(values),
840-
keyAsBuffer_(keyAsBuffer),
841-
valueAsBuffer_(valueAsBuffer),
843+
keyEncoding_(keyEncoding),
844+
valueEncoding_(valueEncoding),
842845
highWaterMarkBytes_(highWaterMarkBytes),
843846
first_(true),
844847
nexting_(false),
@@ -897,8 +900,8 @@ struct Iterator final : public BaseIterator {
897900
const uint32_t id_;
898901
const bool keys_;
899902
const bool values_;
900-
const bool keyAsBuffer_;
901-
const bool valueAsBuffer_;
903+
const Encoding keyEncoding_;
904+
const Encoding valueEncoding_;
902905
const uint32_t highWaterMarkBytes_;
903906
bool first_;
904907
bool nexting_;
@@ -1151,11 +1154,11 @@ struct GetWorker final : public PriorityWorker {
11511154
Database* database,
11521155
napi_value callback,
11531156
leveldb::Slice key,
1154-
const bool asBuffer,
1157+
const Encoding encoding,
11551158
const bool fillCache)
11561159
: PriorityWorker(env, database, callback, "classic_level.db.get"),
11571160
key_(key),
1158-
asBuffer_(asBuffer) {
1161+
encoding_(encoding) {
11591162
options_.fill_cache = fillCache;
11601163
}
11611164

@@ -1170,15 +1173,15 @@ struct GetWorker final : public PriorityWorker {
11701173
void HandleOKCallback (napi_env env, napi_value callback) override {
11711174
napi_value argv[2];
11721175
napi_get_null(env, &argv[0]);
1173-
Entry::Convert(env, &value_, asBuffer_, argv[1]);
1176+
Entry::Convert(env, &value_, encoding_, argv[1]);
11741177
CallFunction(env, callback, 2, argv);
11751178
}
11761179

11771180
private:
11781181
leveldb::ReadOptions options_;
11791182
leveldb::Slice key_;
11801183
std::string value_;
1181-
const bool asBuffer_;
1184+
const Encoding encoding_;
11821185
};
11831186

11841187
/**
@@ -1190,11 +1193,11 @@ NAPI_METHOD(db_get) {
11901193

11911194
leveldb::Slice key = ToSlice(env, argv[1]);
11921195
napi_value options = argv[2];
1193-
const bool asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1196+
const Encoding encoding = GetEncoding(env, options, "valueEncoding");
11941197
const bool fillCache = BooleanProperty(env, options, "fillCache", true);
11951198
napi_value callback = argv[3];
11961199

1197-
GetWorker* worker = new GetWorker(env, database, callback, key, asBuffer,
1200+
GetWorker* worker = new GetWorker(env, database, callback, key, encoding,
11981201
fillCache);
11991202
worker->Queue(env);
12001203

@@ -1209,10 +1212,10 @@ struct GetManyWorker final : public PriorityWorker {
12091212
Database* database,
12101213
std::vector<std::string> keys,
12111214
napi_value callback,
1212-
const bool valueAsBuffer,
1215+
const Encoding valueEncoding,
12131216
const bool fillCache)
12141217
: PriorityWorker(env, database, callback, "classic_level.get.many"),
1215-
keys_(std::move(keys)), valueAsBuffer_(valueAsBuffer) {
1218+
keys_(std::move(keys)), valueEncoding_(valueEncoding) {
12161219
options_.fill_cache = fillCache;
12171220
options_.snapshot = database->NewSnapshot();
12181221
}
@@ -1250,7 +1253,7 @@ struct GetManyWorker final : public PriorityWorker {
12501253
for (size_t idx = 0; idx < size; idx++) {
12511254
std::string* value = cache_[idx];
12521255
napi_value element;
1253-
Entry::Convert(env, value, valueAsBuffer_, element);
1256+
Entry::Convert(env, value, valueEncoding_, element);
12541257
napi_set_element(env, array, static_cast<uint32_t>(idx), element);
12551258
if (value != NULL) delete value;
12561259
}
@@ -1264,7 +1267,7 @@ struct GetManyWorker final : public PriorityWorker {
12641267
private:
12651268
leveldb::ReadOptions options_;
12661269
const std::vector<std::string> keys_;
1267-
const bool valueAsBuffer_;
1270+
const Encoding valueEncoding_;
12681271
std::vector<std::string*> cache_;
12691272
};
12701273

@@ -1277,12 +1280,12 @@ NAPI_METHOD(db_get_many) {
12771280

12781281
const auto keys = KeyArray(env, argv[1]);
12791282
napi_value options = argv[2];
1280-
const bool asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1283+
const Encoding valueEncoding = GetEncoding(env, options, "valueEncoding");
12811284
const bool fillCache = BooleanProperty(env, options, "fillCache", true);
12821285
napi_value callback = argv[3];
12831286

12841287
GetManyWorker* worker = new GetManyWorker(
1285-
env, database, keys, callback, asBuffer, fillCache
1288+
env, database, keys, callback, valueEncoding, fillCache
12861289
);
12871290

12881291
worker->Queue(env);
@@ -1626,8 +1629,8 @@ NAPI_METHOD(iterator_init) {
16261629
const bool keys = BooleanProperty(env, options, "keys", true);
16271630
const bool values = BooleanProperty(env, options, "values", true);
16281631
const bool fillCache = BooleanProperty(env, options, "fillCache", false);
1629-
const bool keyAsBuffer = EncodingIsBuffer(env, options, "keyEncoding");
1630-
const bool valueAsBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1632+
const Encoding keyEncoding = GetEncoding(env, options, "keyEncoding");
1633+
const Encoding valueEncoding = GetEncoding(env, options, "valueEncoding");
16311634
const int limit = Int32Property(env, options, "limit", -1);
16321635
const uint32_t highWaterMarkBytes = Uint32Property(env, options, "highWaterMarkBytes", 16 * 1024);
16331636

@@ -1639,7 +1642,7 @@ NAPI_METHOD(iterator_init) {
16391642
const uint32_t id = database->currentIteratorId_++;
16401643
Iterator* iterator = new Iterator(database, id, reverse, keys,
16411644
values, limit, lt, lte, gt, gte, fillCache,
1642-
keyAsBuffer, valueAsBuffer, highWaterMarkBytes);
1645+
keyEncoding, valueEncoding, highWaterMarkBytes);
16431646
napi_value result;
16441647

16451648
NAPI_STATUS_THROWS(napi_create_external(env, iterator,
@@ -1757,12 +1760,12 @@ struct NextWorker final : public BaseWorker {
17571760
napi_value jsArray;
17581761
napi_create_array_with_length(env, size, &jsArray);
17591762

1760-
const bool kab = iterator_->keyAsBuffer_;
1761-
const bool vab = iterator_->valueAsBuffer_;
1763+
const Encoding ke = iterator_->keyEncoding_;
1764+
const Encoding ve = iterator_->valueEncoding_;
17621765

17631766
for (uint32_t idx = 0; idx < size; idx++) {
17641767
napi_value element;
1765-
iterator_->cache_[idx].ConvertByMode(env, Mode::entries, kab, vab, element);
1768+
iterator_->cache_[idx].ConvertByMode(env, Mode::entries, ke, ve, element);
17661769
napi_set_element(env, jsArray, idx, element);
17671770
}
17681771

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)