tingodb
Version:
Embedded Node.js database upward compatible with MongoDB
278 lines (250 loc) • 6.17 kB
JavaScript
var LeafNode = require('./leaf_node');
var default_options = {
order: 100, // Min 1
sort: 1 // 1, -1 or array
};
var BPlusTree = module.exports = function(options) {
this.options = options || default_options;
if (!this.options.order) {
this.options.order = default_options.order;
}
if (!this.options.sort) {
this.options.sort = default_options.sort;
}
var self = this;
if (Array.isArray(this.options.sort)) this._compare = function (a1, a2) {
if (a2.length > a1.length) return -self._compare(a2, a1);
for (var i = 0; i < a1.length; i++) {
if (i >= a2.length) return self.options.sort[i];
var v1 = a1[i];
var v2 = a2[i];
if (v1 < v2) return -self.options.sort[i];
else if (v1 > v2) return self.options.sort[i];
}
return 0;
};
else this._compare = function (k1, k2) {
if (k1 < k2) return -self.options.sort;
else if (k1 > k2) return self.options.sort;
else if (k1 == k2) return 0;
// else FIXME: probably different data types
};
this.root = new LeafNode(options.order, this._compare);
};
BPlusTree.prototype.set = function(key, value) {
var node = this._search(key);
var ret = node.insert(key, value);
if (ret) {
this.root = ret;
}
};
BPlusTree.prototype.get = function(key) {
var node = this._search(key);
for(var i=0; i<node.data.length; i++){
if(this._compare(node.data[i].key, key) === 0) return node.data[i].value;
}
return null;
};
BPlusTree.prototype.del = function(key) {
var node = this._search(key);
for(var i=0; i<node.data.length; i++){
if(this._compare(node.data[i].key, key) === 0) {
node.data.splice(i,1);
// TODO, NOTE SURE IF THIS IS ENOUGH
break;
}
}
return null;
};
BPlusTree.prototype.getNode = function(key) {
return this._search(key);
};
BPlusTree.prototype._search = function(key) {
var current = this.root;
var found = false;
while(current.isInternalNode){
found = false;
var len = current.data.length;
for(var i=1; i<len; i+=2){
if(this._compare(key, current.data[i]) <= 0) {
current = current.data[i-1];
found = true;
break;
}
}
// Follow infinity pointer
if(!found) current = current.data[len - 1];
}
return current;
};
// walk the tree in order
BPlusTree.prototype.each = function(callback, node) {
if (!node) {
node = this.root;
}
var current = node;
if(current.isLeafNode){
for(var i = 0; i < current.data.length; i++) {
node = current.data[i];
if (node.value) {
callback(node.key, node.value);
}
}
} else {
for(i=0; i<node.data.length; i+=2) {
this.each(callback, node.data[i]);
}
}
};
// walk the tree in order
BPlusTree.prototype.all = function(node,res) {
if (!res)
res = [];
if (!node) {
node = this.root;
}
var current = node;
if(current.isLeafNode){
for(var i = 0; i < current.data.length; i++) {
node = current.data[i];
res.push(node.value);
}
} else {
for(i=0; i<node.data.length; i+=2) {
this.all(node.data[i],res);
}
}
return res;
};
BPlusTree.prototype.each_reverse = function(callback, node) {
if (!node) {
node = this.root;
}
var current = node;
if(current.isLeafNode){
for(var i = current.data.length - 1; i >= 0 ; i--) {
node = current.data[i];
if (node.value) {
callback(node.key, node.value);
}
}
} else {
for(i=node.data.length - 1; i >= 0; i-=2) {
this.each(callback, node.data[i]);
}
}
};
// Get a range
BPlusTree.prototype.range = function(start, end, callback) {
var node = this._search(start);
if (!node) {
node = this.root;
while (!node.isLeafNode) {
node = node[0]; // smallest node
}
}
var ended = false;
while (!ended) {
for(var i = 0; i < node.data.length; i ++) {
var data = node.data[i];
var key = data.key;
if (end && this._compare(key, end) > 0) {
ended = true;
break;
} else {
if ((start === undefined || this._compare(start, key) <= 0) && (end === undefined || this._compare(end, key) >= 0) && data.value) {
callback(key, data.value);
}
}
}
node = node.nextNode;
if (!node) {
ended = true;
}
}
};
BPlusTree.prototype.rangeSync = function(start, end, exclusive_start, exclusive_end) {
var values = [];
var node = this._search(start);
if (!node) {
node = this.root;
while (!node.isLeafNode) {
node = node[0]; // smallest node
}
}
var ended = false;
var self = this;
function keyCheck(key) {
return (start === undefined
|| start === null
|| !exclusive_start && self._compare(start, key) <= 0
|| exclusive_start && self._compare(start, key) < 0
) && (
end === undefined
|| end === null
|| !exclusive_end && self._compare(end, key) >= 0
|| exclusive_end && self._compare(end, key) > 0
);
}
while (!ended) {
if (values.length && node.data.length>0 && keyCheck(node.data[0].key,node.data[0].value) &&
keyCheck(node.data[node.data.length-1].key,node.data[node.data.length-1].value))
{
// entire node is in range
for(var i = 0; i < node.data.length; i ++) {
values.push(node.data[i].value);
}
} else {
for(i = 0; i < node.data.length; i ++) {
var data = node.data[i];
var key = data.key;
if (end && this._compare(key, end) > 0) {
ended = true;
break;
} else {
if (keyCheck(key,data.value))
values.push(data.value);
}
}
}
node = node.nextNode;
if (!node) {
ended = true;
}
}
return values;
};
// B+ tree dump routines
BPlusTree.prototype.walk = function(node, level, arr) {
var current = node;
if(!arr[level]) arr[level] = [];
if(current.isLeafNode){
for(var i=0; i<current.data.length; i++){
arr[level].push("<"+current.data[i].key+">");
}
arr[level].push(" -> ");
}else{
for(i=1; i<node.data.length; i+=2){
arr[level].push("<"+node.data[i]+">");
}
arr[level].push(" -> ");
for(i=0; i<node.data.length; i+=2) {
this.walk(node.data[i], level+1, arr);
}
}
return arr;
};
BPlusTree.prototype.dump = function() {
var arr = [];
this.walk(this.root, 0, arr);
for(var i=0; i<arr.length; i++){
var s = "";
for(var j=0; j<arr[i].length; j++){
s += arr[i][j];
}
}
return s;
};
module.exports.create = function(options) {
return new BPlusTree(options);
};