diff --git a/README.md b/README.md
index 50af8d08..6c4bad68 100644
--- a/README.md
+++ b/README.md
@@ -106,6 +106,7 @@ If you are working on `leveldown` itself and want to re-compile the C++ code, ru
- db.close()
- db.put()
- db.get()
+- db.getMany()
- db.del()
- db.batch()
_(array form)_
- db.batch()
_(chained form)_
@@ -210,7 +211,7 @@ The `callback` function will be called with no arguments if the operation is suc
### `db.get(key[, options], callback)`
-get()
is an instance method on an existing database object, used to fetch individual entries from the LevelDB store.
+Get a value from the LevelDB store by `key`.
The `key` object may either be a string or a Buffer and cannot be `undefined` or `null`. Other object types are converted to strings with the `toString()` method and the resulting string _may not_ be a zero-length. A richer set of data-types is catered for in `levelup`.
@@ -220,11 +221,20 @@ Values fetched via `get()` that are stored as zero-length character arrays (`nul
The optional `options` object may contain:
+- `asBuffer` (boolean, default: `true`): Used to determine whether to return the `value` of the entry as a string or a Buffer. Note that converting from a Buffer to a string incurs a cost so if you need a string (and the `value` can legitimately become a UTF8 string) then you should fetch it as one with `{ asBuffer: false }` and you'll avoid this conversion cost.
- `fillCache` (boolean, default: `true`): LevelDB will by default fill the in-memory LRU Cache with data from a call to get. Disabling this is done by setting `fillCache` to `false`.
-- `asBuffer` (boolean, default: `true`): Used to determine whether to return the `value` of the entry as a string or a Buffer. Note that converting from a Buffer to a string incurs a cost so if you need a string (and the `value` can legitimately become a UTF8 string) then you should fetch it as one with `{ asBuffer: false }` and you'll avoid this conversion cost.
+The `callback` function will be called with a single `error` if the operation failed for any reason, including if the key was not found. If successful the first argument will be `null` and the second argument will be the `value` as a string or Buffer depending on the `asBuffer` option.
+
+
+
+### `db.getMany(keys[, options][, callback])`
+
+Get multiple values from the store by an array of `keys`. The optional `options` object may contain `asBuffer` and `fillCache`, as described in [`get()`](#leveldown_get).
+
+The `callback` function will be called with an `Error` if the operation failed for any reason. If successful the first argument will be `null` and the second argument will be an array of values with the same order as `keys`. If a key was not found, the relevant value will be `undefined`.
-The `callback` function will be called with a single `error` if the operation failed for any reason. If successful the first argument will be `null` and the second argument will be the `value` as a string or Buffer depending on the `asBuffer` option.
+If no callback is provided, a promise is returned.
diff --git a/binding.cc b/binding.cc
index 1b50c70b..d6394c34 100644
--- a/binding.cc
+++ b/binding.cc
@@ -242,6 +242,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.
*/
@@ -1132,6 +1157,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.
*/
@@ -1916,6 +2033,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 e8d4d7b1..65bc4f36 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"prebuild-win32-x64": "prebuildify -t 8.14.0 --napi --strip"
},
"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
})