diff --git a/lib/bcoin/bst.js b/lib/bcoin/bst.js index 861c7e65..e0550c79 100644 --- a/lib/bcoin/bst.js +++ b/lib/bcoin/bst.js @@ -1,7 +1,6 @@ /*! * bst.js - iterative binary search tree for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * Copyright (c) 2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ @@ -12,8 +11,10 @@ var assert = utils.assert; var DUMMY = new Buffer([0]); /** - * An iterative in-memory binary search tree. Used for the mempool. - * Many of its options, parameters and methods mimic the leveldown interface. + * An iterative binary search tree. + * Used for the mempool. Many of its + * options, parameters, and methods + * mimic the leveldown interface. * @exports BST * @constructor * @param {String?} location - Phony location. @@ -35,7 +36,7 @@ function BST(location, options) { /** * Do a key lookup. - * @param {String|Buffer} key + * @param {Buffer|String} key * @returns {Buffer?} value */ @@ -59,14 +60,14 @@ BST.prototype.search = function search(key) { /** * Insert a record. - * @param {String|Buffer} key + * @param {Buffer|String} key * @param {Buffer} value */ BST.prototype.insert = function insert(key, value) { var current = this.root; var left = false; - var parent, cmp; + var parent, cmp, node; if (typeof key === 'string') key = new Buffer(key, 'ascii'); @@ -93,15 +94,17 @@ BST.prototype.insert = function insert(key, value) { } } + node = new BSTNode(key, value); + if (!parent) { - this.root = { key: key, value: value }; + this.root = node; return; } if (left) - parent.left = { key: key, value: value }; + parent.left = node; else - parent.right = { key: key, value: value }; + parent.right = node; }; /** @@ -191,7 +194,7 @@ BST.prototype.remove = function remove(key) { /** * Take a snapshot and return a cloned root node. - * @return {Object} + * @returns {BSTNode} */ BST.prototype.snapshot = function snapshot() { @@ -203,13 +206,13 @@ BST.prototype.snapshot = function snapshot() { for (;;) { if (current) { if (left) { - copy = clone(current); + copy = current.clone(); if (parent) parent.left = copy; else snapshot = copy; } else { - copy = clone(current); + copy = current.clone(); if (parent) parent.right = copy; else @@ -237,7 +240,7 @@ BST.prototype.snapshot = function snapshot() { /** * Traverse the key and filter records. * @param {Function} test - * @returns {Object[]} Matched records. + * @returns {BSTNode[]} Records. */ BST.prototype.traverse = function traverse(test) { @@ -247,12 +250,8 @@ BST.prototype.traverse = function traverse(test) { for (;;) { if (current) { - if (test(current)) { - items.push({ - key: current.key, - value: current.value - }); - } + if (test(current)) + items.push(current.copy()); stack.push(current); current = current.left; continue; @@ -270,7 +269,7 @@ BST.prototype.traverse = function traverse(test) { /** * Dump all records. - * @returns {Object[]} Records. + * @returns {BSTNode[]} Records. */ BST.prototype.dump = function dump() { @@ -281,7 +280,7 @@ BST.prototype.dump = function dump() { * Traverse between a range of keys and collect records. * @param {Buffer} gte * @param {Buffer} lte - * @returns {Object[]} records + * @returns {BSTNode[]} Records. */ BST.prototype.range = function range(gte, lte) { @@ -300,10 +299,7 @@ BST.prototype.range = function range(gte, lte) { if (current) { cmp = this.rangeCompare(current.key, gte, lte); if (cmp === 0) { - items.push({ - key: current.key, - value: current.value - }); + items.push(current.copy()); stack.push(current); } if (cmp <= 0) @@ -513,15 +509,22 @@ BST.prototype.getProperty = function getProperty(name) { /** * Calculate approximate database size (leveldown method). - * @param {String} start - Start key. - * @param {String} end - End key. + * @param {Buffer|String} start - Start key. + * @param {Buffer|String} end - End key. * @param {Function} callback - Returns [Error, Number]. */ BST.prototype.approximateSize = function approximateSize(start, end, callback) { - var size = this.range(start, end).reduce(function(total, item) { - return total + item.key.length + item.value.length; - }, 0); + var items = this.range(start, end); + var size = 0; + var i, item; + + for (i = 0; i < items.length; i++) { + item = items[i]; + size += item.key.length; + size += item.value.length; + } + return utils.asyncify(callback)(null, size); }; @@ -545,8 +548,52 @@ BST.repair = function repair(location, callback) { return utils.nextTick(callback); }; -/* +/** + * BST Node + * @constructor + * @private + * @param {Buffer} key + * @param {Buffer} value + * @property {Buffer} key + * @property {Buffer} value + * @property {BSTNode|null} left + * @property {BSTNode|null} right + */ + +function BSTNode(key, value) { + this.key = key; + this.value = value; + this.left = null; + this.right = null; +} + +/** + * Clone the node. + * @returns {BSTNode} + */ + +BSTNode.prototype.clone = function clone() { + var node = new BSTNode(this.key, this.value); + node.left = this.left; + node.right = this.right; + return node; +}; + +/** + * Clone the node (key/value only). + * @returns {BSTNode} + */ + +BSTNode.prototype.copy = function copy() { + return new BSTNode(this.key, this.value); +}; + +/** * Batch + * @constructor + * @private + * @param {BST} tree + * @param {Object?} options */ function Batch(tree, options) { @@ -555,32 +602,49 @@ function Batch(tree, options) { this.tree = tree; } +/** + * Insert a record. + * @param {Buffer|String} key + * @param {Buffer} value + */ + Batch.prototype.put = function(key, value) { assert(this.tree, 'Already written.'); this.ops.push({ type: 'put', key: key, value: value }); return this; }; +/** + * Remove a record. + * @param {Buffer|String} key + */ + Batch.prototype.del = function del(key) { assert(this.tree, 'Already written.'); this.ops.push({ type: 'del', key: key }); return this; }; +/** + * Commit the batch. + * @param {Function} callback + */ + Batch.prototype.write = function write(callback) { - var self = this; + var i, op; if (!this.tree) - return utils.asyncify(callback)(new Error('Already written.')); + return callback(new Error('Already written.')); - this.ops.forEach(function(op) { + for (i = 0; i < this.ops.length; i++) { + op = this.ops[i]; if (op.type === 'put') - self.tree.insert(op.key, op.value); + this.tree.insert(op.key, op.value); else if (op.type === 'del') - self.tree.remove(op.key); + this.tree.remove(op.key); else assert(false); - }); + } this.ops.length = 0; this.ops = null; @@ -592,14 +656,22 @@ Batch.prototype.write = function write(callback) { return this; }; +/** + * Clear batch of all ops. + */ + Batch.prototype.clear = function clear() { assert(this.tree, 'Already written.'); this.ops.length = 0; return this; }; -/* +/** * Iterator + * @constructor + * @private + * @param {BST} tree + * @param {Object?} options */ function Iterator(tree, options) { @@ -622,11 +694,16 @@ function Iterator(tree, options) { this.tree = tree; this.ended = false; - this.items = this.tree.range(this.options.gte, this.options.lte); - this.index = this.options.reverse ? this.items.length - 1 : 0; + this.snapshot = this.tree.range(this.options.gte, this.options.lte); + this.index = this.options.reverse ? this.snapshot.length - 1 : 0; this.total = 0; } +/** + * Seek to the next key. + * @param {Function} callback + */ + Iterator.prototype.next = function(callback) { var item, key, value; @@ -634,9 +711,9 @@ Iterator.prototype.next = function(callback) { return utils.asyncify(callback)(new Error('Cannot call next after end.')); if (this.options.reverse) - item = this.items[this.index--]; + item = this.snapshot[this.index--]; else - item = this.items[this.index++]; + item = this.snapshot[this.index++]; if (this.options.limit != null) { if (this.total++ >= this.options.limit) { @@ -668,6 +745,11 @@ Iterator.prototype.next = function(callback) { utils.asyncify(callback)(null, key, value); }; +/** + * Seek to a key gte to `key`. + * @param {String|Buffer} key + */ + Iterator.prototype.seek = function seek(key) { var self = this; @@ -676,20 +758,30 @@ Iterator.prototype.seek = function seek(key) { if (typeof key === 'string') key = new Buffer(key, 'ascii'); - this.index = utils.binarySearch(this.items, key, function(a, b) { + this.index = utils.binarySearch(this.snapshot, key, function(a, b) { return self.tree.compare(a.key, b); }, true); }; +/** + * Clean up the iterator. + * @private + */ + Iterator.prototype._end = function end() { if (!this.tree) return; this.tree = null; - this.items.length = 0; - this.items = null; + this.snapshot.length = 0; + this.snapshot = null; }; +/** + * End the iterator. Free up snapshot. + * @param {Buffer} callback + */ + Iterator.prototype.end = function end(callback) { if (this.ended) return utils.asyncify(callback)(new Error('Already ended.')); @@ -700,19 +792,6 @@ Iterator.prototype.end = function end(callback) { return utils.nextTick(callback); }; -/* - * Helpers - */ - -function clone(node) { - return { - key: node.key, - value: node.value, - left: node.left, - right: node.right - }; -} - /* * Expose */