merkle-btree
Version:
Content-addressed b-tree
491 lines (430 loc) • 16.1 kB
JavaScript
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function lt(a, b) {
return a < b;
}
function lte(a, b) {
return a <= b;
}
function gt(a, b) {
return a > b;
}
function gte(a, b) {
return a >= b;
}
function any() {
return true;
}
function beginsWith(str, begin) {
return str.substring(0, begin.length) === begin;
}
var KeyElement = function KeyElement(key, value, targetHash) {
_classCallCheck(this, KeyElement);
this.key = key;
this.value = value;
this.targetHash = targetHash;
};
var TreeNode = function () {
function TreeNode(leftChildHash) {
var keys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var hash = arguments[2];
_classCallCheck(this, TreeNode);
this.hash = hash;
this.leftChildHash = leftChildHash;
this.keys = keys;
var zero = new KeyElement("", null, leftChildHash);
if (!this.keys.length || this.keys[0].key.length) {
this.keys.unshift(zero);
}
}
TreeNode.prototype.get = function get(key, storage) {
var nextKey = this.keys[0];
for (var _iterator = this.keys, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var k = _ref;
if (key < k.key) {
break;
}
nextKey = k;
}
if (nextKey.targetHash) {
return storage.get(nextKey.targetHash).then(function (serialized) {
return TreeNode.deserialize(serialized, nextKey.targetHash).get(key, storage);
});
}
if (nextKey.key === key) {
return Promise.resolve(nextKey.value);
}
return Promise.resolve(null); // not found
};
TreeNode.prototype.searchText = function searchText(query) {
var limit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100;
var cursor = arguments[2];
var reverse = arguments[3];
var storage = arguments[4];
var lowerBound = cursor || query;
var upperBound = undefined;
var includeLowerBound = !cursor;
var includeUpperBound = true;
if (reverse) {
lowerBound = query;
upperBound = cursor || undefined;
includeLowerBound = true;
includeUpperBound = !cursor;
}
return this.searchRange(lowerBound, upperBound, query, limit, includeLowerBound, includeUpperBound, reverse, storage);
};
TreeNode.prototype.searchRange = function searchRange(lowerBound, upperBound, queryText, limit) {
var includeLowerBound = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
var includeUpperBound = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true;
var reverse = arguments[6];
var storage = arguments[7];
var matches = [];
var _this = this;
var lowerBoundCheck = void 0,
upperBoundCheck = void 0;
if (lowerBound) {
lowerBoundCheck = includeLowerBound ? gte : gt;
} else {
lowerBoundCheck = any;
}
if (upperBound) {
upperBoundCheck = includeUpperBound ? lte : lt;
} else {
upperBoundCheck = any;
}
function iterate(i) {
if (i < 0 || i >= _this.keys.length) {
return Promise.resolve(matches);
}
if (limit && matches.length >= limit) {
matches = matches.slice(0, limit + 1);
return Promise.resolve(matches);
}
var next = reverse ? i - 1 : i + 1;
var k = _this.keys[i];
if (next >= 0 && next < _this.keys.length) {
if (!reverse && lowerBound >= _this.keys[next].key) {
// search only nodes whose keys are in the query range
return iterate(next);
}
if (reverse && upperBound < _this.keys[next].key) {
// search only nodes whose keys are in the query range
return iterate(next);
}
}
// return if search range upper / lower bound was passed
if (reverse && i + 1 < _this.keys.length && !lowerBoundCheck(_this.keys[i + 1].key, lowerBound)) {
return Promise.resolve(matches);
}
if (!reverse && !upperBoundCheck(k.key, upperBound)) {
return Promise.resolve(matches);
}
if (k.targetHash) {
// branch node
return storage.get(k.targetHash).then(function (serialized) {
var newLimit = limit ? limit - matches.length : undefined;
return TreeNode.deserialize(serialized, k.targetHash).searchRange(lowerBound, upperBound, queryText, newLimit, includeLowerBound, includeUpperBound, reverse, storage);
}).then(function (m) {
if (m) {
if (matches.length && !m.length) {
return Promise.resolve(matches); // matches were previously found but now we're out of range
}
if (queryText && !m.length) {
// text search yielded no results where they could have been
return Promise.resolve(matches);
}
Array.prototype.push.apply(matches, m);
}
return iterate(next);
});
}
// leaf node
if (k.key.length && k.value) {
if (queryText && matches.length && !beginsWith(k.key, queryText)) {
return Promise.resolve(matches); // matches were previously found but now we're out of range
}
if (lowerBoundCheck(k.key, lowerBound) && upperBoundCheck(k.key, upperBound)) {
// leaf node with matching key
if (!queryText || beginsWith(k.key, queryText)) {
matches.push({ key: k.key, value: k.value });
}
}
}
return iterate(next);
}
return iterate(reverse ? this.keys.length - 1 : 0);
};
TreeNode.prototype.save = function save(storage) {
var _this2 = this;
return storage.put(this.serialize()).then(function (hash) {
_this2.hash = hash;
return _this2.hash;
});
};
TreeNode.prototype._getLeafInsertIndex = function _getLeafInsertIndex(key) {
var _getNextSmallestIndex2 = this._getNextSmallestIndex(key),
index = _getNextSmallestIndex2.index,
exists = _getNextSmallestIndex2.exists;
var leafInsertIndex = index + 1;
if (key > this.keys[this.keys.length - 1].key) {
leafInsertIndex = this.keys.length;
}
return { leafInsertIndex: leafInsertIndex, exists: exists };
};
TreeNode.prototype._getNextSmallestIndex = function _getNextSmallestIndex(key) {
var index = 0,
exists = void 0;
for (var i = 1; i < this.keys.length; i++) {
if (key === this.keys[i].key) {
exists = true;
break;
}
if (key < this.keys[i].key) {
break;
}
index = i;
}
return { index: index, exists: exists };
};
TreeNode.prototype._splitNode = function _splitNode(storage) {
var medianIndex = Math.floor(this.keys.length / 2);
var median = this.keys[medianIndex];
var leftChild = new TreeNode(null, this.keys.slice(0, medianIndex));
var putLeftChild = storage.put(leftChild.serialize());
var rightSet = this.keys.slice(medianIndex, this.keys.length);
if (this.keys[0].targetHash) {
// branch node
rightSet.shift();
}
var rightChild = new TreeNode(this.keys[medianIndex].targetHash, rightSet);
var putRightChild = storage.put(rightChild.serialize());
return Promise.all([putLeftChild, putRightChild, this._removeIfNotEmpty(storage)]).then(function (_ref2) {
var leftChildHash = _ref2[0],
rightChildHash = _ref2[1];
var rightChildElement = new KeyElement(median.key, null, rightChildHash);
return new TreeNode(leftChildHash, [rightChildElement]);
});
};
TreeNode.prototype._removeIfNotEmpty = function _removeIfNotEmpty(storage) {
// TODO: this breaks stuff when multiple trees use the same storage
if (this.hash && this.keys.length > 0) {
return storage.remove(this.hash);
}
return Promise.resolve();
};
TreeNode.prototype._saveToLeafNode = function _saveToLeafNode(key, value, storage, maxChildren) {
var _this3 = this;
var keyElement = new KeyElement(key, value, null);
var _getLeafInsertIndex2 = this._getLeafInsertIndex(key),
leafInsertIndex = _getLeafInsertIndex2.leafInsertIndex,
exists = _getLeafInsertIndex2.exists;
if (exists) {
this.keys[leafInsertIndex] = keyElement;
return this._removeIfNotEmpty(storage).then(function () {
return storage.put(_this3.serialize());
}).then(function (hash) {
return new TreeNode(_this3.leftChildHash, _this3.keys, hash);
});
}
this.keys.splice(leafInsertIndex, 0, keyElement);
if (this.keys.length < maxChildren) {
return this._removeIfNotEmpty(storage).then(function () {
return storage.put(_this3.serialize());
}).then(function (hash) {
return new TreeNode(_this3.leftChildHash, _this3.keys, hash);
});
}
return this._splitNode(storage);
};
TreeNode.prototype._saveToBranchNode = function _saveToBranchNode(key, value, storage, maxChildren) {
var _this4 = this;
var _getNextSmallestIndex3 = this._getNextSmallestIndex(key),
index = _getNextSmallestIndex3.index;
var nextSmallest = this.keys[index];
return storage.get(nextSmallest.targetHash).then(function (serialized) {
return TreeNode.deserialize(serialized, nextSmallest.targetHash).put(key, value, storage, maxChildren);
}).then(function (modifiedChild) {
if (!modifiedChild.hash) {
// we split a child and need to add the median to our keys
_this4.keys[index] = new KeyElement(nextSmallest.key, null, modifiedChild.keys[0].targetHash);
_this4.keys.splice(index + 1, 0, modifiedChild.keys[modifiedChild.keys.length - 1]);
if (_this4.keys.length < maxChildren) {
return _this4._removeIfNotEmpty(storage).then(function () {
return storage.put(_this4.serialize());
}).then(function (hash) {
return new TreeNode(_this4.leftChildHash, _this4.keys, hash);
});
}
return _this4._splitNode(storage);
}
// The child element was not split
_this4.keys[index] = new KeyElement(_this4.keys[index].key, null, modifiedChild.hash);
storage.remove(_this4.hash);
return _this4._removeIfNotEmpty(storage).then(function () {
storage.put(_this4.serialize());
}).then(function (hash) {
return new TreeNode(_this4.leftChildHash, _this4.keys, hash);
});
});
};
TreeNode.prototype.put = function put(key, value, storage, maxChildren) {
if (!this.keys[0].targetHash) {
return this._saveToLeafNode(key, value, storage, maxChildren);
}
return this._saveToBranchNode(key, value, storage, maxChildren);
};
TreeNode.prototype.delete = function _delete(key, storage) {
var _this5 = this;
var nextKey = this.keys[0];
var i = void 0;
for (i = 0; i < this.keys.length; i++) {
if (key < this.keys[i].key) {
i = Math.max(i - 1, 0);
break;
}
nextKey = this.keys[i];
}
var q = Promise.resolve();
if (nextKey.targetHash) {
q = q.then(function () {
return storage.get(nextKey.targetHash).then(function (serialized) {
return TreeNode.deserialize(serialized, nextKey.targetHash).delete(key, storage);
}).then(function (newNode) {
var oldHash = nextKey.targetHash;
nextKey.targetHash = newNode.hash;
return storage.remove(oldHash);
});
});
} else if (nextKey.key === key) {
this.keys.splice(i, 1);
}
return q.then(function () {
return storage.put(_this5.serialize());
}).then(function (hash) {
return new TreeNode(_this5.leftChildHash, _this5.keys, hash);
});
};
TreeNode.prototype.size = function size(storage) {
return this.keys.reduce(function (promise, key) {
return promise.then(function (size) {
if (key.targetHash) {
return storage.get(key.targetHash).then(function (serialized) {
return TreeNode.deserialize(serialized).size(storage);
}).then(function (childSize) {
return childSize + size;
});
}
return size;
});
}, Promise.resolve(this.keys.length));
};
TreeNode.prototype.smallestKey = function smallestKey() {
if (this.keys.length) {
return this.keys[0];
}
return null;
};
TreeNode.prototype.smallestNonZeroKey = function smallestNonZeroKey() {
if (this.keys.length > 1) {
return this.keys[1];
}
return null;
};
TreeNode.prototype.rebalance = function rebalance() {};
TreeNode.prototype.print = function print(storage) {
var lvl = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var str = "node: " + this.hash + "\n";
var indentation = "";
for (var i = 0; i <= lvl; i++) {
indentation += "- ";
}
this.keys.forEach(function (key) {
str += indentation;
str += "key: " + key.key;
if (key.targetHash) {
str += " link: " + key.targetHash;
}
if (key.value) {
str += "\n" + indentation + " value: " + JSON.stringify(key.value);
}
str += "\n";
});
this.keys.forEach(function (key) {
if (key.targetHash) {
storage.get(key.targetHash).then(function (serialized) {
var child = TreeNode.deserialize(serialized, key.targetHash);
str += "\n";
str += indentation;
str += child.print(storage, lvl + 1);
});
}
});
return str;
};
TreeNode.prototype.serialize = function serialize() {
return JSON.stringify({ leftChildHash: this.leftChildHash, keys: this.keys });
};
TreeNode.prototype.toSortedList = function toSortedList(storage) {
var list = [];
var _this = this;
function getNextVal(i) {
if (i >= _this.keys.length) {
return Promise.resolve(list);
}
if (_this.keys[i].targetHash) {
return storage.get(_this.keys[i].targetHash).then(function (serialized) {
return TreeNode.deserialize(serialized, _this.keys[i].targetHash).toSortedList();
}).then(function (children) {
list = list.concat(children);
return getNextVal(i + 1);
});
} else if (_this.keys[i].key.length) {
list.push(_this.keys[i]);
}
return getNextVal(i + 1);
}
return getNextVal(0);
};
TreeNode.deserialize = function deserialize(data, hash) {
data = JSON.parse(data);
return new TreeNode(data.leftChildHash, data.keys, hash);
};
/*
Create a tree from a [{key,value,targetHash}, ...] list sorted in ascending order by k.
targetHash must be null for leaf nodes.
*/
TreeNode.fromSortedList = function fromSortedList(list, maxChildren, storage) {
function addNextParentNode(parentNodeList) {
if (list.length) {
var keys = list.splice(0, maxChildren);
return storage.put(new TreeNode(keys[0].targetHash, keys).serialize()).then(function (res) {
parentNodeList.push({ key: keys[1].key, targetHash: res, value: null });
return addNextParentNode(parentNodeList);
});
}
if (parentNodeList.length && parentNodeList[0].targetHash) {
parentNodeList[0].key = "";
}
return TreeNode.fromSortedList(parentNodeList, maxChildren, storage);
}
if (list.length > maxChildren) {
return addNextParentNode([]);
}
var targetHash = list.length ? list[0].targetHash : null;
var node = new TreeNode(targetHash, list);
return storage.put(node.serialize()).then(function (hash) {
node.hash = hash;
return node;
});
};
return TreeNode;
}();
export default TreeNode;