Skip to content

Commit b9fd5e9

Browse files
authored
Refactor: handle view encoding (Uint8Array) natively (#43)
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 b9fd5e9

File tree

3 files changed

+68
-67
lines changed

3 files changed

+68
-67
lines changed

binding.cc

Lines changed: 65 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,29 @@ 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) const {
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 || encoding == Encoding::view) {
348348
napi_create_buffer_copy(env, s->size(), s->data(), NULL, &result);
349349
} else {
350350
napi_create_string_utf8(env, s->data(), s->size(), &result);
@@ -830,15 +830,15 @@ struct Iterator final : public BaseIterator {
830830
std::string* gt,
831831
std::string* gte,
832832
const bool fillCache,
833-
const bool keyAsBuffer,
834-
const bool valueAsBuffer,
833+
const Encoding keyEncoding,
834+
const Encoding valueEncoding,
835835
const uint32_t highWaterMarkBytes)
836836
: BaseIterator(database, reverse, lt, lte, gt, gte, limit, fillCache),
837837
id_(id),
838838
keys_(keys),
839839
values_(values),
840-
keyAsBuffer_(keyAsBuffer),
841-
valueAsBuffer_(valueAsBuffer),
840+
keyEncoding_(keyEncoding),
841+
valueEncoding_(valueEncoding),
842842
highWaterMarkBytes_(highWaterMarkBytes),
843843
first_(true),
844844
nexting_(false),
@@ -897,8 +897,8 @@ struct Iterator final : public BaseIterator {
897897
const uint32_t id_;
898898
const bool keys_;
899899
const bool values_;
900-
const bool keyAsBuffer_;
901-
const bool valueAsBuffer_;
900+
const Encoding keyEncoding_;
901+
const Encoding valueEncoding_;
902902
const uint32_t highWaterMarkBytes_;
903903
bool first_;
904904
bool nexting_;
@@ -1151,11 +1151,11 @@ struct GetWorker final : public PriorityWorker {
11511151
Database* database,
11521152
napi_value callback,
11531153
leveldb::Slice key,
1154-
const bool asBuffer,
1154+
const Encoding encoding,
11551155
const bool fillCache)
11561156
: PriorityWorker(env, database, callback, "classic_level.db.get"),
11571157
key_(key),
1158-
asBuffer_(asBuffer) {
1158+
encoding_(encoding) {
11591159
options_.fill_cache = fillCache;
11601160
}
11611161

@@ -1170,15 +1170,15 @@ struct GetWorker final : public PriorityWorker {
11701170
void HandleOKCallback (napi_env env, napi_value callback) override {
11711171
napi_value argv[2];
11721172
napi_get_null(env, &argv[0]);
1173-
Entry::Convert(env, &value_, asBuffer_, argv[1]);
1173+
Entry::Convert(env, &value_, encoding_, argv[1]);
11741174
CallFunction(env, callback, 2, argv);
11751175
}
11761176

11771177
private:
11781178
leveldb::ReadOptions options_;
11791179
leveldb::Slice key_;
11801180
std::string value_;
1181-
const bool asBuffer_;
1181+
const Encoding encoding_;
11821182
};
11831183

11841184
/**
@@ -1190,11 +1190,11 @@ NAPI_METHOD(db_get) {
11901190

11911191
leveldb::Slice key = ToSlice(env, argv[1]);
11921192
napi_value options = argv[2];
1193-
const bool asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1193+
const Encoding encoding = GetEncoding(env, options, "valueEncoding");
11941194
const bool fillCache = BooleanProperty(env, options, "fillCache", true);
11951195
napi_value callback = argv[3];
11961196

1197-
GetWorker* worker = new GetWorker(env, database, callback, key, asBuffer,
1197+
GetWorker* worker = new GetWorker(env, database, callback, key, encoding,
11981198
fillCache);
11991199
worker->Queue(env);
12001200

@@ -1209,10 +1209,10 @@ struct GetManyWorker final : public PriorityWorker {
12091209
Database* database,
12101210
std::vector<std::string> keys,
12111211
napi_value callback,
1212-
const bool valueAsBuffer,
1212+
const Encoding valueEncoding,
12131213
const bool fillCache)
12141214
: PriorityWorker(env, database, callback, "classic_level.get.many"),
1215-
keys_(std::move(keys)), valueAsBuffer_(valueAsBuffer) {
1215+
keys_(std::move(keys)), valueEncoding_(valueEncoding) {
12161216
options_.fill_cache = fillCache;
12171217
options_.snapshot = database->NewSnapshot();
12181218
}
@@ -1250,7 +1250,7 @@ struct GetManyWorker final : public PriorityWorker {
12501250
for (size_t idx = 0; idx < size; idx++) {
12511251
std::string* value = cache_[idx];
12521252
napi_value element;
1253-
Entry::Convert(env, value, valueAsBuffer_, element);
1253+
Entry::Convert(env, value, valueEncoding_, element);
12541254
napi_set_element(env, array, static_cast<uint32_t>(idx), element);
12551255
if (value != NULL) delete value;
12561256
}
@@ -1264,7 +1264,7 @@ struct GetManyWorker final : public PriorityWorker {
12641264
private:
12651265
leveldb::ReadOptions options_;
12661266
const std::vector<std::string> keys_;
1267-
const bool valueAsBuffer_;
1267+
const Encoding valueEncoding_;
12681268
std::vector<std::string*> cache_;
12691269
};
12701270

@@ -1277,12 +1277,12 @@ NAPI_METHOD(db_get_many) {
12771277

12781278
const auto keys = KeyArray(env, argv[1]);
12791279
napi_value options = argv[2];
1280-
const bool asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1280+
const Encoding valueEncoding = GetEncoding(env, options, "valueEncoding");
12811281
const bool fillCache = BooleanProperty(env, options, "fillCache", true);
12821282
napi_value callback = argv[3];
12831283

12841284
GetManyWorker* worker = new GetManyWorker(
1285-
env, database, keys, callback, asBuffer, fillCache
1285+
env, database, keys, callback, valueEncoding, fillCache
12861286
);
12871287

12881288
worker->Queue(env);
@@ -1626,8 +1626,8 @@ NAPI_METHOD(iterator_init) {
16261626
const bool keys = BooleanProperty(env, options, "keys", true);
16271627
const bool values = BooleanProperty(env, options, "values", true);
16281628
const bool fillCache = BooleanProperty(env, options, "fillCache", false);
1629-
const bool keyAsBuffer = EncodingIsBuffer(env, options, "keyEncoding");
1630-
const bool valueAsBuffer = EncodingIsBuffer(env, options, "valueEncoding");
1629+
const Encoding keyEncoding = GetEncoding(env, options, "keyEncoding");
1630+
const Encoding valueEncoding = GetEncoding(env, options, "valueEncoding");
16311631
const int limit = Int32Property(env, options, "limit", -1);
16321632
const uint32_t highWaterMarkBytes = Uint32Property(env, options, "highWaterMarkBytes", 16 * 1024);
16331633

@@ -1639,7 +1639,7 @@ NAPI_METHOD(iterator_init) {
16391639
const uint32_t id = database->currentIteratorId_++;
16401640
Iterator* iterator = new Iterator(database, id, reverse, keys,
16411641
values, limit, lt, lte, gt, gte, fillCache,
1642-
keyAsBuffer, valueAsBuffer, highWaterMarkBytes);
1642+
keyEncoding, valueEncoding, highWaterMarkBytes);
16431643
napi_value result;
16441644

16451645
NAPI_STATUS_THROWS(napi_create_external(env, iterator,
@@ -1757,12 +1757,12 @@ struct NextWorker final : public BaseWorker {
17571757
napi_value jsArray;
17581758
napi_create_array_with_length(env, size, &jsArray);
17591759

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

17631763
for (uint32_t idx = 0; idx < size; idx++) {
17641764
napi_value element;
1765-
iterator_->cache_[idx].ConvertByMode(env, Mode::entries, kab, vab, element);
1765+
iterator_->cache_[idx].ConvertByMode(env, Mode::entries, ke, ve, element);
17661766
napi_set_element(env, jsArray, idx, element);
17671767
}
17681768

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)