diff --git a/README.md b/README.md index 18fb61b..ce504fd 100644 --- a/README.md +++ b/README.md @@ -35,19 +35,33 @@ var sub = require('subleveldown') var level = require('level') var db = level('db') +var example = sub(db, 'example') +var nested = sub(example, 'nested') +``` -var test = sub(db, 'test') // test is just a regular levelup -var test2 = sub(db, 'test2') -var nested = sub(test, 'nested') +The `example` and `nested` db's are just regular [`levelup`][levelup] instances: -test.put('hello', 'world', function() { - nested.put('hi', 'welt', function() { +```js +example.put('hello', 'world', function () { + nested.put('hi', 'welt', function () { // will print {key:'hello', value:'world'} - test.createReadStream().on('data', console.log) + example.createReadStream().on('data', console.log) }) }) ``` +They also support `db.clear()` which is very useful to empty a bucket of stuff: + +```js +example.clear(function (err) {}) + +// Or delete a range within `example` +example.clear({ gt: 'hello' }, function (err) {}) + +// For believers +await example.clear() +``` + ## Background `subleveldown` separates a [`levelup`][levelup] database into sections - or _sublevels_ from here on out. Think SQL tables, but evented, ranged and realtime! diff --git a/leveldown.js b/leveldown.js index c14092b..a07988e 100644 --- a/leveldown.js +++ b/leveldown.js @@ -2,6 +2,9 @@ var inherits = require('inherits') var abstract = require('abstract-leveldown') var wrap = require('level-option-wrap') +var rangeOptions = 'start end gt gte lt lte'.split(' ') +var defaultClear = abstract.AbstractLevelDOWN.prototype._clear +var hasOwnProperty = Object.prototype.hasOwnProperty var END = Buffer.from([0xff]) function concat (prefix, key, force) { @@ -123,6 +126,32 @@ SubDown.prototype._batch = function (operations, opts, cb) { this.leveldown.batch(operations, opts, cb) } +SubDown.prototype._clear = function (opts, cb) { + if (typeof this.leveldown.clear === 'function') { + // Prefer optimized implementation of clear() + opts = addRestOptions(wrap(opts, this._wrap), opts) + this.leveldown.clear(opts, cb) + } else { + // Fall back to iterator-based implementation + defaultClear.call(this, opts, cb) + } +} + +function addRestOptions (target, opts) { + for (var k in opts) { + if (hasOwnProperty.call(opts, k) && !isRangeOption(k)) { + target[k] = opts[k] + } + } + + return target +} + +function isRangeOption (k) { + return rangeOptions.indexOf(k) !== -1 +} + +// TODO (refactor): use addRestOptions instead function extend (xopts, opts) { xopts.keys = opts.keys xopts.values = opts.values diff --git a/package.json b/package.json index 8f31eaf..0c9aabf 100644 --- a/package.json +++ b/package.json @@ -16,17 +16,19 @@ "test": "test" }, "dependencies": { - "abstract-leveldown": "^6.0.2", - "encoding-down": "^6.0.1", + "abstract-leveldown": "^6.1.1", + "encoding-down": "^6.2.0", "inherits": "^2.0.3", "level-option-wrap": "^1.1.0", - "levelup": "^4.0.1" + "levelup": "^4.2.0" }, "devDependencies": { + "after": "^0.8.2", "coveralls": "^3.0.2", "dependency-check": "^3.3.0", "hallmark": "^2.0.0", "level-community": "^3.0.0", + "level-concat-iterator": "^2.0.1", "memdown": "^5.0.0", "nyc": "^14.0.0", "standard": "^14.0.0", diff --git a/test/index.js b/test/index.js index 255d5ab..a59feb1 100644 --- a/test/index.js +++ b/test/index.js @@ -2,6 +2,8 @@ var test = require('tape') var suite = require('abstract-leveldown/test') var memdown = require('memdown') var encoding = require('encoding-down') +var concat = require('level-concat-iterator') +var after = require('after') var subdown = require('../leveldown') var subdb = require('..') var levelup = require('levelup') @@ -16,7 +18,10 @@ suite({ // Unsupported features seek: false, createIfMissing: false, - errorIfExists: false + errorIfExists: false, + + // Opt-in to new clear() tests + clear: true }) // Test without a user-provided levelup layer @@ -29,7 +34,10 @@ suite({ // Unsupported features seek: false, createIfMissing: false, - errorIfExists: false + errorIfExists: false, + + // Opt-in to new clear() tests + clear: true }) // Additional tests for this implementation @@ -283,4 +291,74 @@ test('SubDb main function', function (t) { }) }) }) + + t.test('clear (optimized)', function (t) { + var down = memdown() + t.is(typeof down.clear, 'function', 'has clear()') + testClear(t, down) + }) + + t.test('clear (with iterator-based fallback)', function (t) { + var down = memdown() + down.clear = undefined + testClear(t, down) + }) + + function testClear (t, down) { + const db = levelup(down) + const sub1 = subdb(db, '1') + const sub2 = subdb(db, '2') + + populate([sub1, sub2], ['a', 'b'], function (err) { + t.ifError(err, 'no populate error') + + verify(['!1!a', '!1!b', '!2!a', '!2!b'], function () { + clear([sub1], {}, function (err) { + t.ifError(err, 'no clear error') + + verify(['!2!a', '!2!b'], function () { + populate([sub1], ['a', 'b'], function (err) { + t.ifError(err, 'no populate error') + + clear([sub2], { lt: 'b' }, function (err) { + t.ifError(err, 'no clear error') + verify(['!1!a', '!1!b', '!2!b'], t.end.bind(t)) + }) + }) + }) + }) + }) + }) + + function populate (subs, items, callback) { + const next = after(subs.length, callback) + + for (const sub of subs) { + sub.batch(items.map(function (item) { + return { type: 'put', key: item, value: item } + }), next) + } + } + + function clear (subs, opts, callback) { + const next = after(subs.length, callback) + + for (const sub of subs) { + sub.clear(opts, next) + } + } + + function verify (expected, callback) { + concat(db.iterator({ keyAsBuffer: false }), function (err, entries) { + t.ifError(err, 'no concat error') + t.same(entries.map(getKey), expected) + + if (callback) callback() + }) + } + } }) + +function getKey (entry) { + return entry.key +}