diff --git a/binding.cc b/binding.cc index 508a9cfb..b2f8660c 100644 --- a/binding.cc +++ b/binding.cc @@ -256,6 +256,31 @@ static std::string* RangeOption (napi_env env, napi_value opts, const char* name return NULL; } +/** + * Converts an array containing Buffer or string keys to a vector. + * Empty elements are skipped. + */ +static std::vector* KeyArray (napi_env env, napi_value arr) { + uint32_t length; + std::vector* result = new std::vector(); + + if (napi_get_array_length(env, arr, &length) == napi_ok) { + result->reserve(length); + + 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) { + LD_STRING_OR_BUFFER_TO_COPY(env, element, to); + result->emplace_back(toCh_, toSz_); + } + } + } + + return result; +} + /** * Calls a function. */ @@ -1184,6 +1209,98 @@ NAPI_METHOD(db_get) { NAPI_RETURN_UNDEFINED(); } +/** + * Worker class for getting many values. + */ +struct GetManyWorker final : public PriorityWorker { + GetManyWorker (napi_env env, + Database* database, + const std::vector* keys, + napi_value callback, + const bool valueAsBuffer, + const bool fillCache) + : PriorityWorker(env, database, callback, "leveldown.get.many"), + keys_(keys), valueAsBuffer_(valueAsBuffer) { + options_.fill_cache = fillCache; + options_.snapshot = database->NewSnapshot(); + } + + ~GetManyWorker() { + delete keys_; + } + + void DoExecute () override { + cache_.reserve(keys_->size()); + + for (const std::string& key: *keys_) { + std::string* value = new std::string(); + leveldb::Status status = database_->Get(options_, key, *value); + + if (status.ok()) { + cache_.push_back(value); + } else if (status.IsNotFound()) { + delete value; + cache_.push_back(NULL); + } else { + delete value; + for (const std::string* value: cache_) { + if (value != NULL) delete value; + } + SetStatus(status); + break; + } + } + + database_->ReleaseSnapshot(options_.snapshot); + } + + void HandleOKCallback (napi_env env, napi_value callback) override { + size_t size = cache_.size(); + napi_value array; + napi_create_array_with_length(env, size, &array); + + for (size_t idx = 0; idx < size; idx++) { + std::string* value = cache_[idx]; + napi_value element; + Entry::Convert(env, value, valueAsBuffer_, &element); + napi_set_element(env, array, static_cast(idx), element); + if (value != NULL) delete value; + } + + napi_value argv[2]; + napi_get_null(env, &argv[0]); + argv[1] = array; + CallFunction(env, callback, 2, argv); + } + +private: + leveldb::ReadOptions options_; + const std::vector* keys_; + const bool valueAsBuffer_; + std::vector cache_; +}; + +/** + * Gets many values from a database. + */ +NAPI_METHOD(db_get_many) { + NAPI_ARGV(4); + NAPI_DB_CONTEXT(); + + const std::vector* keys = KeyArray(env, argv[1]); + napi_value options = argv[2]; + const bool asBuffer = BooleanProperty(env, options, "asBuffer", true); + const bool fillCache = BooleanProperty(env, options, "fillCache", true); + napi_value callback = argv[3]; + + GetManyWorker* worker = new GetManyWorker( + env, database, keys, callback, asBuffer, fillCache + ); + + worker->Queue(env); + NAPI_RETURN_UNDEFINED(); +} + /** * Worker class for deleting a value from a database. */ @@ -1978,6 +2095,7 @@ NAPI_INIT() { NAPI_EXPORT_FUNCTION(db_close); NAPI_EXPORT_FUNCTION(db_put); NAPI_EXPORT_FUNCTION(db_get); + NAPI_EXPORT_FUNCTION(db_get_many); NAPI_EXPORT_FUNCTION(db_del); NAPI_EXPORT_FUNCTION(db_clear); NAPI_EXPORT_FUNCTION(db_approximate_size); diff --git a/leveldown.js b/leveldown.js index 226a518c..11b2dfc3 100644 --- a/leveldown.js +++ b/leveldown.js @@ -21,6 +21,7 @@ function LevelDOWN (location) { permanence: true, seek: true, clear: true, + getMany: true, createIfMissing: true, errorIfExists: true, additionalMethods: { @@ -59,6 +60,10 @@ LevelDOWN.prototype._get = function (key, options, callback) { binding.db_get(this.context, key, options, callback) } +LevelDOWN.prototype._getMany = function (keys, options, callback) { + binding.db_get_many(this.context, keys, options, callback) +} + LevelDOWN.prototype._del = function (key, options, callback) { binding.db_del(this.context, key, options, callback) } diff --git a/package.json b/package.json index ba98bfae..3580c1c9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "prepublishOnly": "npm run dependency-check" }, "dependencies": { - "abstract-leveldown": "^7.0.0", + "abstract-leveldown": "^7.2.0", "napi-macros": "^2.0.0", "node-gyp-build": "^4.3.0" }, diff --git a/test/common.js b/test/common.js index de2612f2..4098b136 100644 --- a/test/common.js +++ b/test/common.js @@ -9,6 +9,7 @@ module.exports = suite.common({ return leveldown(tempy.directory()) }, - // Opt-in to new clear() tests - clear: true + // Opt-in to new tests + clear: true, + getMany: true })