Skip to content

Commit

Permalink
Polish clear() (#74)
Browse files Browse the repository at this point in the history
* Bump levelup, abstract-leveldown and encoding-down to prevent dedupe
* Opt-in to new clear() tests
* Prefer optimized implementation of clear()
* Add clear() to README example
* Call clear() on levelup layer
  • Loading branch information
vweevers authored Sep 12, 2019
1 parent 3ef72c9 commit b888384
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 11 deletions.
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
29 changes: 29 additions & 0 deletions leveldown.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
82 changes: 80 additions & 2 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}

0 comments on commit b888384

Please sign in to comment.