diff --git a/index.js b/index.js index e0d8bae..8b46417 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,8 @@ var serialize = require('./util/serialize') var deserialize = require('./util/deserialize') var setImmediate = require('./util/immediate') var support = require('./util/support') +var clear = require('./util/clear') +var createKeyRange = require('./util/key-range') var DEFAULT_PREFIX = 'level-js-' @@ -181,6 +183,33 @@ Level.prototype._batch = function (operations, options, callback) { loop() } +Level.prototype._clear = function (options, callback) { + try { + var keyRange = createKeyRange(options) + } catch (e) { + // The lower key is greater than the upper key. + // IndexedDB throws an error, but we'll just do nothing. + return setImmediate(callback) + } + + if (options.limit >= 0) { + // IDBObjectStore#delete(range) doesn't have such an option. + // Fall back to cursor-based implementation. + return clear(this, this.location, keyRange, options, callback) + } + + try { + var store = this.store('readwrite') + var req = keyRange ? store.delete(keyRange) : store.clear() + } catch (err) { + return setImmediate(function () { + callback(err) + }) + } + + this.await(req, callback) +} + Level.prototype._close = function (callback) { this.db.close() setImmediate(callback) diff --git a/iterator.js b/iterator.js index 1bce514..7f3a5ca 100644 --- a/iterator.js +++ b/iterator.js @@ -1,10 +1,8 @@ -/* global IDBKeyRange */ - 'use strict' var inherits = require('inherits') var AbstractIterator = require('abstract-leveldown').AbstractIterator -var ltgt = require('ltgt') +var createKeyRange = require('./util/key-range') var deserialize = require('./util/deserialize') var setImmediate = require('./util/immediate') var noop = function () {} @@ -34,7 +32,7 @@ function Iterator (db, location, options) { } try { - var keyRange = this.createKeyRange(options) + var keyRange = createKeyRange(options) } catch (e) { // The lower key is greater than the upper key. // IndexedDB throws an error, but we'll just return 0 results. @@ -47,23 +45,6 @@ function Iterator (db, location, options) { inherits(Iterator, AbstractIterator) -Iterator.prototype.createKeyRange = function (options) { - var lower = ltgt.lowerBound(options) - var upper = ltgt.upperBound(options) - var lowerOpen = ltgt.lowerBoundExclusive(options) - var upperOpen = ltgt.upperBoundExclusive(options) - - if (lower !== undefined && upper !== undefined) { - return IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen) - } else if (lower !== undefined) { - return IDBKeyRange.lowerBound(lower, lowerOpen) - } else if (upper !== undefined) { - return IDBKeyRange.upperBound(upper, upperOpen) - } else { - return null - } -} - Iterator.prototype.createIterator = function (location, keyRange, reverse) { var self = this var transaction = this.db.db.transaction([location], 'readonly') diff --git a/package.json b/package.json index e23c28a..5ee328e 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "./util/immediate.js": "./util/immediate-browser.js" }, "dependencies": { - "abstract-leveldown": "~6.0.1", + "abstract-leveldown": "~6.1.1", "immediate": "~3.2.3", "inherits": "^2.0.3", "ltgt": "^2.1.2" diff --git a/test/custom-test.js b/test/custom-test.js index 97513f7..86aafc9 100644 --- a/test/custom-test.js +++ b/test/custom-test.js @@ -296,5 +296,29 @@ module.exports = function (leveljs, test, testCommon) { }) }) + // TODO: move to abstract-leveldown test suite (and add to iterator tests too) + test('clear() with lower key greater than upper key', function (t) { + var db = testCommon.factory() + + db.open(function (err) { + t.ifError(err, 'no open error') + + db.put('a', 'a', function (err) { + t.ifError(err, 'no put error') + + db.clear({ gt: 'b', lt: 'a' }, function (err) { + t.ifError(err, 'no clear error') + + db.get('a', { asBuffer: false }, function (err, value) { + t.ifError(err, 'no get error') + t.is(value, 'a') + + db.close(t.end.bind(t)) + }) + }) + }) + }) + }) + test('teardown', testCommon.tearDown) } diff --git a/test/index.js b/test/index.js index 06a3ae3..7feb50e 100644 --- a/test/index.js +++ b/test/index.js @@ -20,7 +20,10 @@ var testCommon = suite.common({ seek: false, // Support of buffer keys depends on environment - bufferKeys: leveljs(uuid()).supports.bufferKeys + bufferKeys: leveljs(uuid()).supports.bufferKeys, + + // Opt-in to new clear() tests + clear: true }) // Test abstract-leveldown compliance diff --git a/util/clear.js b/util/clear.js new file mode 100644 index 0000000..bdf57e3 --- /dev/null +++ b/util/clear.js @@ -0,0 +1,36 @@ +'use strict' + +var setImmediate = require('./immediate') + +module.exports = function clear (db, location, keyRange, options, callback) { + if (options.limit === 0) return setImmediate(callback) + + var transaction = db.db.transaction([location], 'readwrite') + var store = transaction.objectStore(location) + var count = 0 + + transaction.oncomplete = function () { + callback() + } + + transaction.onabort = function () { + callback(transaction.error || new Error('aborted by user')) + } + + // A key cursor is faster (skips reading values) but not supported by IE + var method = store.openKeyCursor ? 'openKeyCursor' : 'openCursor' + var direction = options.reverse ? 'prev' : 'next' + + store[method](keyRange, direction).onsuccess = function (ev) { + var cursor = ev.target.result + + if (cursor) { + // Wait for a request to complete before continuing, saving CPU. + store.delete(cursor.key).onsuccess = function () { + if (options.limit <= 0 || ++count < options.limit) { + cursor.continue() + } + } + } + } +} diff --git a/util/key-range.js b/util/key-range.js new file mode 100644 index 0000000..74fac14 --- /dev/null +++ b/util/key-range.js @@ -0,0 +1,23 @@ +/* global IDBKeyRange */ + +'use strict' + +var ltgt = require('ltgt') +var NONE = {} + +module.exports = function createKeyRange (options) { + var lower = ltgt.lowerBound(options, NONE) + var upper = ltgt.upperBound(options, NONE) + var lowerOpen = ltgt.lowerBoundExclusive(options, NONE) + var upperOpen = ltgt.upperBoundExclusive(options, NONE) + + if (lower !== NONE && upper !== NONE) { + return IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen) + } else if (lower !== NONE) { + return IDBKeyRange.lowerBound(lower, lowerOpen) + } else if (upper !== NONE) { + return IDBKeyRange.upperBound(upper, upperOpen) + } else { + return null + } +}