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