Skip to content

Commit

Permalink
Add db.getMany(keys) (#381)
Browse files Browse the repository at this point in the history
  • Loading branch information
vweevers committed Sep 28, 2021
1 parent dcde940 commit e4445a7
Show file tree
Hide file tree
Showing 14 changed files with 598 additions and 102 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,17 @@ Get a value from the store by `key`. The optional `options` object may contain:

- `asBuffer` _(boolean, default: `true`)_: Whether to return the `value` as a Buffer. If `false`, the returned type depends on the implementation.

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 the value.
The `callback` function will be called with an `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.

### `db.getMany(keys[, options][, callback])`

Get multiple values from the store by an array of `keys`. The optional `options` object may contain:

- `asBuffer` _(boolean, default: `true`)_: Whether to return the `value` as a Buffer. If `false`, the returned type depends on the implementation.

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

If no callback is provided, a promise is returned.

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

Expand Down Expand Up @@ -435,6 +445,14 @@ Get a value by `key`. The `options` object will always have the following proper

The default `_get()` invokes `callback` on a next tick with a `NotFound` error. It must be overridden.

### `db._getMany(keys, options, callback)`

**This new method is optional for the time being. To enable its tests, set the [`getMany` option of the test suite](#excluding-tests) to `true`.**

Get multiple values by an array of `keys`. The `options` object will always have the following properties: `asBuffer`. If an error occurs, call the `callback` function with an `Error`. Otherwise call `callback` with `null` as the first argument and an array of values as the second. If a key does not exist, set the relevant value to `undefined`.

The default `_getMany()` invokes `callback` on a next tick with an array of values that is equal in length to `keys` and is filled with `undefined`. It must be overridden to support `getMany()` but this is currently an opt-in feature. If the implementation does support `getMany()` then `db.supports.getMany` must be set to true via the [constructor](#db--abstractleveldownmanifest).

### `db._put(key, value, options, callback)`

Store a new entry or overwrite an existing entry. There are no default options but `options` will always be an object. If putting failed, call the `callback` function with an `Error`. Otherwise call `callback` without any arguments.
Expand Down Expand Up @@ -581,6 +599,7 @@ This also serves as a signal to users of your implementation. The following opti
- `bufferKeys`: set to `false` if binary keys are not supported by the underlying storage
- `seek`: set to `false` if your `iterator` does not implement `_seek`
- `clear`: defaults to `false` until a next major release. Set to `true` if your implementation either implements `_clear()` itself or is suitable to use the default implementation of `_clear()` (which requires binary key support).
- `getMany`: defaults to `false` until a next major release. Set to `true` if your implementation implements `_getMany()`.
- `snapshots`: set to `false` if any of the following is true:
- Reads don't operate on a [snapshot](#iterator)
- Snapshots are created asynchronously
Expand Down
68 changes: 68 additions & 0 deletions abstract-leveldown.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

const supports = require('level-supports')
const isBuffer = require('is-buffer')
const catering = require('catering')
const AbstractIterator = require('./abstract-iterator')
const AbstractChainedBatch = require('./abstract-chained-batch')
const getCallback = require('./lib/common').getCallback
const getOptions = require('./lib/common').getOptions

const hasOwnProperty = Object.prototype.hasOwnProperty
const rangeOptions = ['lt', 'lte', 'gt', 'gte']

Expand Down Expand Up @@ -90,6 +94,51 @@ AbstractLevelDOWN.prototype._get = function (key, options, callback) {
this._nextTick(function () { callback(new Error('NotFound')) })
}

AbstractLevelDOWN.prototype.getMany = function (keys, options, callback) {
callback = getCallback(options, callback)
callback = catering.fromCallback(callback)
options = getOptions(options)

if (maybeError(this, callback)) {
return callback.promise
}

if (!Array.isArray(keys)) {
this._nextTick(callback, new Error('getMany() requires an array argument'))
return callback.promise
}

if (keys.length === 0) {
this._nextTick(callback, null, [])
return callback.promise
}

if (typeof options.asBuffer !== 'boolean') {
options = { ...options, asBuffer: true }
}

const serialized = new Array(keys.length)

for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const err = this._checkKey(key)

if (err) {
this._nextTick(callback, err)
return callback.promise
}

serialized[i] = this._serializeKey(key)
}

this._getMany(serialized, options, callback)
return callback.promise
}

AbstractLevelDOWN.prototype._getMany = function (keys, options, callback) {
this._nextTick(callback, null, new Array(keys.length).fill(undefined))
}

AbstractLevelDOWN.prototype.put = function (key, value, options, callback) {
if (typeof options === 'function') callback = options

Expand Down Expand Up @@ -315,9 +364,28 @@ AbstractLevelDOWN.prototype._checkValue = function (value) {
}
}

// TODO: docs and tests
AbstractLevelDOWN.prototype.isOperational = function () {
return this.status === 'open' || this._isOperational()
}

// Implementation may accept operations in other states too
AbstractLevelDOWN.prototype._isOperational = function () {
return false
}

// Expose browser-compatible nextTick for dependents
// TODO: rename _nextTick to _queueMicrotask
// TODO: after we drop node 10, also use queueMicrotask in node
AbstractLevelDOWN.prototype._nextTick = require('./next-tick')

module.exports = AbstractLevelDOWN

function maybeError (db, callback) {
if (!db.isOperational()) {
db._nextTick(callback, new Error('Database is not open'))
return true
}

return false
}
9 changes: 9 additions & 0 deletions lib/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict'

exports.getCallback = function (options, callback) {
return typeof options === 'function' ? options : callback
}

exports.getOptions = function (options) {
return typeof options === 'object' && options !== null ? options : {}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"abstract-iterator.js",
"abstract-leveldown.js",
"index.js",
"lib",
"next-tick-browser.js",
"next-tick.js",
"test",
Expand All @@ -30,9 +31,10 @@
],
"dependencies": {
"buffer": "^6.0.3",
"catering": "^2.0.0",
"is-buffer": "^2.0.5",
"level-concat-iterator": "^3.0.0",
"level-supports": "^2.0.0",
"level-supports": "^2.0.1",
"queue-microtask": "^1.2.3"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function testCommon (options) {
snapshots: options.snapshots !== false,
seek: options.seek !== false,
clear: !!options.clear,
getMany: !!options.getMany,

// Support running test suite on a levelup db. All options below this line
// are undocumented and should not be used by abstract-leveldown db's (yet).
Expand All @@ -42,7 +43,6 @@ function testCommon (options) {
// and that operations return strings rather than buffers by default.
encodings: !!options.encodings,

// Not yet used, only here for symmetry with levelup's test suite.
deferredOpen: !!options.deferredOpen,
streams: !!options.streams
}
Expand Down
16 changes: 0 additions & 16 deletions test/del-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,6 @@ exports.args = function (test, testCommon) {
)
t.end()
})

testCommon.serialize && test('test custom _serialize*', function (t) {
t.plan(3)
const db = testCommon.factory()
db._serializeKey = function (data) { return data }
db._del = function (key, options, callback) {
t.deepEqual(key, { foo: 'bar' })
this._nextTick(callback)
}
db.open(function () {
db.del({ foo: 'bar' }, function (err) {
t.error(err)
db.close(t.error.bind(t))
})
})
})
}

exports.del = function (test, testCommon) {
Expand Down
Loading

0 comments on commit e4445a7

Please sign in to comment.