From c1f5a19f77c0cf15017ff9d72ad43300846155d7 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Sun, 8 Sep 2019 18:29:58 +0200 Subject: [PATCH 1/5] Bump levelup, abstract-leveldown and encoding-down to prevent dedupe --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8f31eaf..f1ed300 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,11 @@ "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": { "coveralls": "^3.0.2", From 2e477d3a13724e8cbfa2071a7f41a76b78d5ad02 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Sun, 8 Sep 2019 18:33:46 +0200 Subject: [PATCH 2/5] Opt-in to new clear() tests --- test/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/index.js b/test/index.js index 255d5ab..ec4d8db 100644 --- a/test/index.js +++ b/test/index.js @@ -16,7 +16,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 +32,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 From 27b438a654a22db6eacefd109f8f8e224493c9c6 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Sun, 8 Sep 2019 18:35:48 +0200 Subject: [PATCH 3/5] Prefer optimized implementation of clear() --- leveldown.js | 29 ++++++++++++++++++++ package.json | 2 ++ test/index.js | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) 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 f1ed300..0c9aabf 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,12 @@ "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 ec4d8db..1319865 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') @@ -289,4 +291,75 @@ 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) { + // TODO: use sub.clear() once it lands in levelup + sub.db.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 +} From 3a75cb96f897b8f5c57269288b04b295b4680a42 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Sun, 8 Sep 2019 18:37:54 +0200 Subject: [PATCH 4/5] Add clear() to README example --- README.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) 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! From a6d70562adbb2e9ad880c30a39ab900ab74bb214 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Sun, 8 Sep 2019 19:34:00 +0200 Subject: [PATCH 5/5] Call clear() on levelup layer --- test/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/index.js b/test/index.js index 1319865..a59feb1 100644 --- a/test/index.js +++ b/test/index.js @@ -344,8 +344,7 @@ test('SubDb main function', function (t) { const next = after(subs.length, callback) for (const sub of subs) { - // TODO: use sub.clear() once it lands in levelup - sub.db.clear(opts, next) + sub.clear(opts, next) } }