Skip to content

Commit

Permalink
Refactor: handle view encoding (Uint8Array) natively (#43)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vweevers committed Jun 19, 2022
1 parent d6437b4 commit b9fd5e9
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 67 deletions.
130 changes: 65 additions & 65 deletions binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ static void iterator_close_do (napi_env env, Iterator* iterator, napi_value cb);
#define NAPI_ARGV_UTF8_NEW(name, i) \
NAPI_UTF8_NEW(name, argv[i])

// TODO: consider using encoding options instead of type checking
#define LD_STRING_OR_BUFFER_TO_COPY(env, from, to) \
char* to##Ch_ = 0; \
size_t to##Sz_ = 0; \
Expand All @@ -61,6 +62,18 @@ static void iterator_close_do (napi_env env, Iterator* iterator, napi_value cb);
napi_get_buffer_info(env, from, (void **)&buf, &to##Sz_); \
to##Ch_ = new char[to##Sz_]; \
memcpy(to##Ch_, buf, to##Sz_); \
} else { \
char* buf = 0; \
napi_typedarray_type type; \
napi_status status = napi_get_typedarray_info(env, from, &type, &to##Sz_, (void **)&buf, NULL, NULL); \
if (status != napi_ok || type != napi_typedarray_type::napi_uint8_array) { \
/* TODO: refactor so that we can napi_throw_type_error() here */ \
to##Sz_ = 0; \
to##Ch_ = new char[to##Sz_]; \
} else { \
to##Ch_ = new char[to##Sz_]; \
memcpy(to##Ch_, buf, to##Sz_); \
} \
}

/*********************************************************************
Expand Down Expand Up @@ -149,20 +162,26 @@ static bool BooleanProperty (napi_env env, napi_value obj, const char* key,
return DEFAULT;
}

enum Encoding { buffer, utf8, view };

/**
* Returns true if the options object contains an encoding option that is "buffer"
* Returns internal Encoding enum matching the given encoding option.
*/
static bool EncodingIsBuffer (napi_env env, napi_value options, const char* option) {
static Encoding GetEncoding (napi_env env, napi_value options, const char* option) {
napi_value value;
size_t size;
size_t copied;
char buf[2];

if (napi_get_named_property(env, options, option, &value) == napi_ok &&
napi_get_value_string_utf8(env, value, NULL, 0, &size) == napi_ok) {
// Value is either "buffer" or "utf8" so we can tell them apart just by size
return size == 6;
napi_get_value_string_utf8(env, value, buf, 2, &copied) == napi_ok && copied == 1) {
// Value is either "buffer", "utf8" or "view" so we only have to read the first char
switch (buf[0]) {
case 'b': return Encoding::buffer;
case 'v': return Encoding::view;
}
}

return false;
return Encoding::utf8;
}

/**
Expand Down Expand Up @@ -234,35 +253,17 @@ static leveldb::Slice ToSlice (napi_env env, napi_value from) {
}

/**
* Returns length of string or buffer
*/
static size_t StringOrBufferLength (napi_env env, napi_value value) {
size_t size = 0;

if (IsString(env, value)) {
napi_get_value_string_utf8(env, value, NULL, 0, &size);
} else if (IsBuffer(env, value)) {
char* buf;
napi_get_buffer_info(env, value, (void **)&buf, &size);
}

return size;
}

/**
* Takes a Buffer or string property 'name' from 'opts'.
* Returns null if the property does not exist or is zero-length.
* Takes a Buffer, string or Uint8Array property 'name' from 'opts'.
* Returns null if the property does not exist.
*/
static std::string* RangeOption (napi_env env, napi_value opts, const char* name) {
if (HasProperty(env, opts, name)) {
napi_value value = GetProperty(env, opts, name);

if (StringOrBufferLength(env, value) >= 0) {
LD_STRING_OR_BUFFER_TO_COPY(env, value, to);
std::string* result = new std::string(toCh_, toSz_);
delete [] toCh_;
return result;
}
// TODO: we can avoid a copy here
LD_STRING_OR_BUFFER_TO_COPY(env, value, to);
std::string* result = new std::string(toCh_, toSz_);
delete [] toCh_;
return result;
}

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

if (napi_get_element(env, arr, i, &element) == napi_ok &&
StringOrBufferLength(env, element) >= 0) {
if (napi_get_element(env, arr, i, &element) == napi_ok) {
LD_STRING_OR_BUFFER_TO_COPY(env, element, to);
result.emplace_back(toCh_, toSz_);
delete [] toCh_;
Expand Down Expand Up @@ -322,29 +322,29 @@ struct Entry {
: key_(key.data(), key.size()),
value_(value.data(), value.size()) {}

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

napi_value keyElement;
napi_value valueElement;

Convert(env, &key_, keyAsBuffer, keyElement);
Convert(env, &value_, valueAsBuffer, valueElement);
Convert(env, &key_, keyEncoding, keyElement);
Convert(env, &value_, valueEncoding, valueElement);

napi_set_element(env, result, 0, keyElement);
napi_set_element(env, result, 1, valueElement);
} else if (mode == Mode::keys) {
Convert(env, &key_, keyAsBuffer, result);
Convert(env, &key_, keyEncoding, result);
} else {
Convert(env, &value_, valueAsBuffer, result);
Convert(env, &value_, valueEncoding, result);
}
}

static void Convert (napi_env env, const std::string* s, const bool asBuffer, napi_value& result) {
static void Convert (napi_env env, const std::string* s, const Encoding encoding, napi_value& result) {
if (s == NULL) {
napi_get_undefined(env, &result);
} else if (asBuffer) {
} else if (encoding == Encoding::buffer || encoding == Encoding::view) {
napi_create_buffer_copy(env, s->size(), s->data(), NULL, &result);
} else {
napi_create_string_utf8(env, s->data(), s->size(), &result);
Expand Down Expand Up @@ -830,15 +830,15 @@ struct Iterator final : public BaseIterator {
std::string* gt,
std::string* gte,
const bool fillCache,
const bool keyAsBuffer,
const bool valueAsBuffer,
const Encoding keyEncoding,
const Encoding valueEncoding,
const uint32_t highWaterMarkBytes)
: BaseIterator(database, reverse, lt, lte, gt, gte, limit, fillCache),
id_(id),
keys_(keys),
values_(values),
keyAsBuffer_(keyAsBuffer),
valueAsBuffer_(valueAsBuffer),
keyEncoding_(keyEncoding),
valueEncoding_(valueEncoding),
highWaterMarkBytes_(highWaterMarkBytes),
first_(true),
nexting_(false),
Expand Down Expand Up @@ -897,8 +897,8 @@ struct Iterator final : public BaseIterator {
const uint32_t id_;
const bool keys_;
const bool values_;
const bool keyAsBuffer_;
const bool valueAsBuffer_;
const Encoding keyEncoding_;
const Encoding valueEncoding_;
const uint32_t highWaterMarkBytes_;
bool first_;
bool nexting_;
Expand Down Expand Up @@ -1151,11 +1151,11 @@ struct GetWorker final : public PriorityWorker {
Database* database,
napi_value callback,
leveldb::Slice key,
const bool asBuffer,
const Encoding encoding,
const bool fillCache)
: PriorityWorker(env, database, callback, "classic_level.db.get"),
key_(key),
asBuffer_(asBuffer) {
encoding_(encoding) {
options_.fill_cache = fillCache;
}

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

private:
leveldb::ReadOptions options_;
leveldb::Slice key_;
std::string value_;
const bool asBuffer_;
const Encoding encoding_;
};

/**
Expand All @@ -1190,11 +1190,11 @@ NAPI_METHOD(db_get) {

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

GetWorker* worker = new GetWorker(env, database, callback, key, asBuffer,
GetWorker* worker = new GetWorker(env, database, callback, key, encoding,
fillCache);
worker->Queue(env);

Expand All @@ -1209,10 +1209,10 @@ struct GetManyWorker final : public PriorityWorker {
Database* database,
std::vector<std::string> keys,
napi_value callback,
const bool valueAsBuffer,
const Encoding valueEncoding,
const bool fillCache)
: PriorityWorker(env, database, callback, "classic_level.get.many"),
keys_(std::move(keys)), valueAsBuffer_(valueAsBuffer) {
keys_(std::move(keys)), valueEncoding_(valueEncoding) {
options_.fill_cache = fillCache;
options_.snapshot = database->NewSnapshot();
}
Expand Down Expand Up @@ -1250,7 +1250,7 @@ struct GetManyWorker final : public PriorityWorker {
for (size_t idx = 0; idx < size; idx++) {
std::string* value = cache_[idx];
napi_value element;
Entry::Convert(env, value, valueAsBuffer_, element);
Entry::Convert(env, value, valueEncoding_, element);
napi_set_element(env, array, static_cast<uint32_t>(idx), element);
if (value != NULL) delete value;
}
Expand All @@ -1264,7 +1264,7 @@ struct GetManyWorker final : public PriorityWorker {
private:
leveldb::ReadOptions options_;
const std::vector<std::string> keys_;
const bool valueAsBuffer_;
const Encoding valueEncoding_;
std::vector<std::string*> cache_;
};

Expand All @@ -1277,12 +1277,12 @@ NAPI_METHOD(db_get_many) {

const auto keys = KeyArray(env, argv[1]);
napi_value options = argv[2];
const bool asBuffer = EncodingIsBuffer(env, options, "valueEncoding");
const Encoding valueEncoding = GetEncoding(env, options, "valueEncoding");
const bool fillCache = BooleanProperty(env, options, "fillCache", true);
napi_value callback = argv[3];

GetManyWorker* worker = new GetManyWorker(
env, database, keys, callback, asBuffer, fillCache
env, database, keys, callback, valueEncoding, fillCache
);

worker->Queue(env);
Expand Down Expand Up @@ -1626,8 +1626,8 @@ NAPI_METHOD(iterator_init) {
const bool keys = BooleanProperty(env, options, "keys", true);
const bool values = BooleanProperty(env, options, "values", true);
const bool fillCache = BooleanProperty(env, options, "fillCache", false);
const bool keyAsBuffer = EncodingIsBuffer(env, options, "keyEncoding");
const bool valueAsBuffer = EncodingIsBuffer(env, options, "valueEncoding");
const Encoding keyEncoding = GetEncoding(env, options, "keyEncoding");
const Encoding valueEncoding = GetEncoding(env, options, "valueEncoding");
const int limit = Int32Property(env, options, "limit", -1);
const uint32_t highWaterMarkBytes = Uint32Property(env, options, "highWaterMarkBytes", 16 * 1024);

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

NAPI_STATUS_THROWS(napi_create_external(env, iterator,
Expand Down Expand Up @@ -1757,12 +1757,12 @@ struct NextWorker final : public BaseWorker {
napi_value jsArray;
napi_create_array_with_length(env, size, &jsArray);

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

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

Expand Down
2 changes: 1 addition & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
* @template VDefault The default type of values if not overridden on operations.
*/
declare class ClassicLevel<KDefault = string, VDefault = string>
extends AbstractLevel<string | Buffer, KDefault, VDefault> {
extends AbstractLevel<string | Buffer | Uint8Array, KDefault, VDefault> {
/**
* Database constructor.
*
Expand Down
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class ClassicLevel extends AbstractLevel {
super({
encodings: {
buffer: true,
utf8: true
utf8: true,
view: true
},
seek: true,
createIfMissing: true,
Expand Down

0 comments on commit b9fd5e9

Please sign in to comment.