Skip to content

Commit

Permalink
Add db.getMany(keys) (#787)
Browse files Browse the repository at this point in the history
  • Loading branch information
vweevers committed Sep 28, 2021
1 parent 8e72ac5 commit 50dc50b
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 6 deletions.
16 changes: 13 additions & 3 deletions README.md
Expand Up @@ -106,6 +106,7 @@ If you are working on `leveldown` itself and want to re-compile the C++ code, ru
- <a href="#leveldown_close"><code>db.<b>close()</b></code></a>
- <a href="#leveldown_put"><code>db.<b>put()</b></code></a>
- <a href="#leveldown_get"><code>db.<b>get()</b></code></a>
- <a href="#leveldown_get_many"><code>db.<b>getMany()</b></code></a>
- <a href="#leveldown_del"><code>db.<b>del()</b></code></a>
- <a href="#leveldown_batch"><code>db.<b>batch()</b></code></a> _(array form)_
- <a href="#leveldown_chainedbatch"><code>db.<b>batch()</b></code></a> _(chained form)_
Expand Down Expand Up @@ -210,7 +211,7 @@ The `callback` function will be called with no arguments if the operation is suc

### `db.get(key[, options], callback)`

<code>get()</code> 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`.

Expand All @@ -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.

<a name="leveldown_get_many"></a>

### `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.

<a name="leveldown_del"></a>

Expand Down
118 changes: 118 additions & 0 deletions binding.cc
Expand Up @@ -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<std::string>* KeyArray (napi_env env, napi_value arr) {
uint32_t length;
std::vector<std::string>* result = new std::vector<std::string>();

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.
*/
Expand Down Expand Up @@ -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<std::string>* 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<uint32_t>(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<std::string>* keys_;
const bool valueAsBuffer_;
std::vector<std::string*> cache_;
};

/**
* Gets many values from a database.
*/
NAPI_METHOD(db_get_many) {
NAPI_ARGV(4);
NAPI_DB_CONTEXT();

const std::vector<std::string>* 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.
*/
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions leveldown.js
Expand Up @@ -21,6 +21,7 @@ function LevelDOWN (location) {
permanence: true,
seek: true,
clear: true,
getMany: true,
createIfMissing: true,
errorIfExists: true,
additionalMethods: {
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -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"
},
Expand Down
5 changes: 3 additions & 2 deletions test/common.js
Expand Up @@ -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
})

0 comments on commit 50dc50b

Please sign in to comment.