rbt: refactor and move.
This commit is contained in:
parent
694bab4ad0
commit
d4f5f7cd66
@ -7,10 +7,10 @@
|
||||
'use strict';
|
||||
|
||||
var level = require('./level');
|
||||
var RBT = require('./rbt');
|
||||
var MemoryDB = require('./memorydb');
|
||||
|
||||
exports.get = function get(name) {
|
||||
if (name === 'rbt')
|
||||
return RBT;
|
||||
if (name === 'memory')
|
||||
return MemoryDB;
|
||||
return level;
|
||||
};
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
'use strict';
|
||||
|
||||
exports.get = function get(name) {
|
||||
if (name === 'rbt')
|
||||
return require('./rbt');
|
||||
if (name === 'memory')
|
||||
return require('./memorydb');
|
||||
|
||||
try {
|
||||
return require(name);
|
||||
|
||||
@ -6,4 +6,4 @@
|
||||
|
||||
exports.LDB = require('./ldb');
|
||||
exports.LowlevelUp = require('./lowlevelup');
|
||||
exports.RBT = require('./rbt');
|
||||
exports.MemoryDB = require('./memorydb');
|
||||
|
||||
@ -33,7 +33,7 @@ function LDB(options) {
|
||||
var target = LDB.getTarget(options);
|
||||
var cacheSize = options.cacheSize;
|
||||
|
||||
if (target.backend !== 'rbt')
|
||||
if (target.backend !== 'memory')
|
||||
util.mkdir(target.location, true);
|
||||
|
||||
if (!cacheSize)
|
||||
@ -98,7 +98,7 @@ LDB.getBackend = function getBackend(db) {
|
||||
case 'mem':
|
||||
case 'memory':
|
||||
case 'rbt':
|
||||
name = 'rbt';
|
||||
name = 'memory';
|
||||
ext = 'mem';
|
||||
break;
|
||||
default:
|
||||
@ -122,8 +122,8 @@ LDB.getTarget = function getTarget(options) {
|
||||
var db = backends.get(backend.name);
|
||||
|
||||
if (typeof location !== 'string') {
|
||||
assert(backend.name === 'rbt', 'Location required.');
|
||||
location = 'rbt';
|
||||
assert(backend.name === 'memory', 'Location required.');
|
||||
location = 'memory';
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
667
lib/db/memorydb.js
Normal file
667
lib/db/memorydb.js
Normal file
@ -0,0 +1,667 @@
|
||||
/*!
|
||||
* memorydb.js - in-memory database for bcoin
|
||||
* Copyright (c) 2016-2017, Christopher Jeffrey (MIT License).
|
||||
* https://github.com/bcoin-org/bcoin
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var util = require('../utils/util');
|
||||
var RBT = require('../utils/rbt');
|
||||
var DUMMY = new Buffer([0]);
|
||||
|
||||
/**
|
||||
* In memory database for bcoin
|
||||
* using a red-black tree backend.
|
||||
* @alias module:db.MemoryDB
|
||||
* @constructor
|
||||
* @param {String?} location - Phony location.
|
||||
* @param {Object?} options
|
||||
* @param {Function} options.compare - Comparator.
|
||||
*/
|
||||
|
||||
function MemoryDB(location, options) {
|
||||
if (!(this instanceof MemoryDB))
|
||||
return new MemoryDB(location, options);
|
||||
|
||||
if (typeof location !== 'string') {
|
||||
options = location;
|
||||
location = null;
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
this.location = location;
|
||||
this.options = options;
|
||||
this.tree = new RBT(util.cmp, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a key lookup.
|
||||
* @private
|
||||
* @param {Buffer|String} key
|
||||
* @returns {Buffer?} value
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.search = function search(key) {
|
||||
var node;
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'utf8');
|
||||
|
||||
node = this.tree.search(key);
|
||||
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
return node.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Insert a record.
|
||||
* @private
|
||||
* @param {Buffer|String} key
|
||||
* @param {Buffer} value
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.insert = function insert(key, value) {
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'utf8');
|
||||
|
||||
if (typeof value === 'string')
|
||||
value = new Buffer(value, 'utf8');
|
||||
|
||||
return this.tree.insert(key, value) != null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a record.
|
||||
* @private
|
||||
* @param {Buffer|String} key
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.remove = function remove(key) {
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'utf8');
|
||||
|
||||
return this.tree.remove(key) != null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverse between a range of keys and collect records.
|
||||
* @private
|
||||
* @param {Buffer} gte
|
||||
* @param {Buffer} lte
|
||||
* @returns {RBTNode[]} Records.
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.range = function range(gte, lte) {
|
||||
if (typeof gte === 'string')
|
||||
gte = new Buffer(gte, 'utf8');
|
||||
|
||||
if (typeof lte === 'string')
|
||||
lte = new Buffer(lte, 'utf8');
|
||||
|
||||
return this.tree.range(gte, lte);
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the database (leveldown method).
|
||||
* @param {Object?} options
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.open = function open(options, callback) {
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
this.options = options;
|
||||
|
||||
util.nextTick(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the database (leveldown method).
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.close = function close(callback) {
|
||||
util.nextTick(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve a record (leveldown method).
|
||||
* @param {Buffer|String} key
|
||||
* @param {Object?} options
|
||||
* @param {Function} callback - Returns Bufer.
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.get = function get(key, options, callback) {
|
||||
var value, err;
|
||||
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
value = this.search(key);
|
||||
|
||||
if (!value) {
|
||||
err = new Error('MemoryDB_NOTFOUND: Key not found.');
|
||||
err.notFound = true;
|
||||
err.type = 'NotFoundError';
|
||||
util.nextTick(function() {
|
||||
callback(err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.asBuffer === false)
|
||||
value = value.toString('utf8');
|
||||
|
||||
util.nextTick(function() {
|
||||
callback(null, value);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Insert a record (leveldown method).
|
||||
* @param {Buffer|String} key
|
||||
* @param {Buffer} value
|
||||
* @param {Object?} options
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.put = function put(key, value, options, callback) {
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
this.insert(key, value);
|
||||
|
||||
util.nextTick(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a record (leveldown method).
|
||||
* @param {Buffer|String} key
|
||||
* @param {Object?} options
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.del = function del(key, options, callback) {
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
this.remove(key);
|
||||
|
||||
util.nextTick(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an atomic batch (leveldown method).
|
||||
* @see Leveldown.Batch
|
||||
* @param {Object[]?} ops
|
||||
* @param {Object?} options
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.batch = function batch(ops, options, callback) {
|
||||
var batch;
|
||||
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
batch = new Batch(this, options);
|
||||
|
||||
if (ops) {
|
||||
batch.ops = ops.slice();
|
||||
batch.write(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
return batch;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an iterator (leveldown method).
|
||||
* @param {Object} options - See {Leveldown.Iterator}.
|
||||
* @returns {Leveldown.Iterator}.
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.iterator = function iterator(options) {
|
||||
return new Iterator(this, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a database property (leveldown method) (NOP).
|
||||
* @param {String} name - Property name.
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.getProperty = function getProperty(name) {
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate approximate database size (leveldown method).
|
||||
* @param {Buffer|String} start - Start key.
|
||||
* @param {Buffer|String} end - End key.
|
||||
* @param {Function} callback - Returns Number.
|
||||
*/
|
||||
|
||||
MemoryDB.prototype.approximateSize = function approximateSize(start, end, callback) {
|
||||
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;
|
||||
}
|
||||
|
||||
util.nextTick(function() {
|
||||
callback(null, size);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the database (leveldown function) (NOP).
|
||||
* @param {String} location
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
MemoryDB.destroy = function destroy(location, callback) {
|
||||
util.nextTick(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Repair the database (leveldown function) (NOP).
|
||||
* @param {String} location
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
MemoryDB.repair = function repair(location, callback) {
|
||||
util.nextTick(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Batch
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @private
|
||||
* @param {RBT} tree
|
||||
* @param {Object?} options
|
||||
*/
|
||||
|
||||
function Batch(tree, options) {
|
||||
this.options = options || {};
|
||||
this.ops = [];
|
||||
this.tree = tree;
|
||||
this.written = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a record.
|
||||
* @param {Buffer|String} key
|
||||
* @param {Buffer} value
|
||||
*/
|
||||
|
||||
Batch.prototype.put = function(key, value) {
|
||||
assert(!this.written, 'Already written.');
|
||||
this.ops.push(new BatchOp('put', key, value));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a record.
|
||||
* @param {Buffer|String} key
|
||||
*/
|
||||
|
||||
Batch.prototype.del = function del(key) {
|
||||
assert(!this.written, 'Already written.');
|
||||
this.ops.push(new BatchOp('del', key));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Commit the batch.
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Batch.prototype.write = function write(callback) {
|
||||
var i, op;
|
||||
|
||||
if (this.written) {
|
||||
util.nextTick(function() {
|
||||
callback(new Error('Already written.'));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < this.ops.length; i++) {
|
||||
op = this.ops[i];
|
||||
switch (op.type) {
|
||||
case 'put':
|
||||
this.tree.insert(op.key, op.value);
|
||||
break;
|
||||
case 'del':
|
||||
this.tree.remove(op.key);
|
||||
break;
|
||||
default:
|
||||
util.nextTick(function() {
|
||||
callback(new Error('Bad operation: ' + op.type));
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.ops.length = 0;
|
||||
this.written = true;
|
||||
|
||||
util.nextTick(callback);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear batch of all ops.
|
||||
*/
|
||||
|
||||
Batch.prototype.clear = function clear() {
|
||||
assert(!this.written, 'Already written.');
|
||||
this.ops.length = 0;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Batch Operation
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {Buffer} key
|
||||
* @param {Buffer|null} value
|
||||
*/
|
||||
|
||||
function BatchOp(type, key, value) {
|
||||
this.type = type;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @private
|
||||
* @param {RBT} db
|
||||
* @param {Object?} options
|
||||
*/
|
||||
|
||||
function Iterator(db, options) {
|
||||
this.db = db;
|
||||
this.options = new IteratorOptions(options);
|
||||
this.iter = null;
|
||||
this.ended = false;
|
||||
this.total = 0;
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the iterator.
|
||||
*/
|
||||
|
||||
Iterator.prototype.init = function init() {
|
||||
var snapshot = this.db.tree.snapshot();
|
||||
var iter = this.db.tree.iterator(snapshot);
|
||||
|
||||
if (this.options.reverse) {
|
||||
if (this.options.end) {
|
||||
iter.seekMax(this.options.end);
|
||||
if (this.options.lt && iter.valid()) {
|
||||
if (iter.compare(this.options.end) === 0)
|
||||
iter.prev();
|
||||
}
|
||||
} else {
|
||||
iter.seekLast();
|
||||
}
|
||||
} else {
|
||||
if (this.options.start) {
|
||||
iter.seekMin(this.options.start);
|
||||
if (this.options.gt && iter.valid()) {
|
||||
if (iter.compare(this.options.start) === 0)
|
||||
iter.next();
|
||||
}
|
||||
} else {
|
||||
iter.seekFirst();
|
||||
}
|
||||
}
|
||||
|
||||
this.iter = iter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Seek to the next key.
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Iterator.prototype.next = function(callback) {
|
||||
var options = this.options;
|
||||
var iter = this.iter;
|
||||
var key, value, result;
|
||||
|
||||
if (!this.iter) {
|
||||
util.nextTick(function() {
|
||||
callback(new Error('Cannot call next after end.'));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.reverse) {
|
||||
result = iter.prev();
|
||||
|
||||
// Stop once we hit a key below our gte key.
|
||||
if (result && options.start) {
|
||||
if (options.gt) {
|
||||
if (iter.compare(options.start) <= 0)
|
||||
result = false;
|
||||
} else {
|
||||
if (iter.compare(options.start) < 0)
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = iter.next();
|
||||
|
||||
// Stop once we hit a key above our lte key.
|
||||
if (result && options.end) {
|
||||
if (options.lt) {
|
||||
if (iter.compare(options.end) >= 0)
|
||||
result = false;
|
||||
} else {
|
||||
if (iter.compare(options.end) > 0)
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
this.iter = null;
|
||||
util.nextTick(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.limit !== -1) {
|
||||
if (this.total >= options.limit) {
|
||||
this.iter = null;
|
||||
util.nextTick(callback);
|
||||
return;
|
||||
}
|
||||
this.total += 1;
|
||||
}
|
||||
|
||||
key = iter.key;
|
||||
value = iter.value;
|
||||
|
||||
if (!options.keys)
|
||||
key = DUMMY;
|
||||
|
||||
if (!options.values)
|
||||
value = DUMMY;
|
||||
|
||||
if (!options.keyAsBuffer)
|
||||
key = key.toString('utf8');
|
||||
|
||||
if (!options.valueAsBuffer)
|
||||
value = value.toString('utf8');
|
||||
|
||||
util.nextTick(function() {
|
||||
callback(null, key, value);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Seek to a key gte to `key`.
|
||||
* @param {String|Buffer} key
|
||||
*/
|
||||
|
||||
Iterator.prototype.seek = function seek(key) {
|
||||
assert(this.iter, 'Already ended.');
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'utf8');
|
||||
|
||||
if (this.options.reverse)
|
||||
this.iter.seekMax(key);
|
||||
else
|
||||
this.iter.seekMin(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* End the iterator. Free up snapshot.
|
||||
* @param {FUnction} callback
|
||||
*/
|
||||
|
||||
Iterator.prototype.end = function end(callback) {
|
||||
if (this.ended) {
|
||||
util.nextTick(function() {
|
||||
callback(new Error('Already ended.'));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.ended = true;
|
||||
this.iter = null;
|
||||
|
||||
util.nextTick(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterator Options
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
function IteratorOptions(options) {
|
||||
this.keys = true;
|
||||
this.values = true;
|
||||
this.start = null;
|
||||
this.end = null;
|
||||
this.gt = false;
|
||||
this.lt = false;
|
||||
this.keyAsBuffer = true;
|
||||
this.valueAsBuffer = true;
|
||||
this.reverse = false;
|
||||
this.limit = -1;
|
||||
|
||||
if (options)
|
||||
this.fromOptions(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject properties from options.
|
||||
* @private
|
||||
* @param {Object} options
|
||||
* @returns {IteratorOptions}
|
||||
*/
|
||||
|
||||
IteratorOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
if (options.keys != null) {
|
||||
assert(typeof options.keys === 'boolean');
|
||||
this.keys = options.keys;
|
||||
}
|
||||
|
||||
if (options.values != null) {
|
||||
assert(typeof options.values === 'boolean');
|
||||
this.values = options.values;
|
||||
}
|
||||
|
||||
if (options.start != null)
|
||||
this.start = options.start;
|
||||
|
||||
if (options.end != null)
|
||||
this.end = options.end;
|
||||
|
||||
if (options.gte != null)
|
||||
this.start = options.gte;
|
||||
|
||||
if (options.lte != null)
|
||||
this.end = options.lte;
|
||||
|
||||
if (options.gt != null) {
|
||||
this.gt = true;
|
||||
this.start = options.gt;
|
||||
}
|
||||
|
||||
if (options.lt != null) {
|
||||
this.lt = true;
|
||||
this.end = options.lt;
|
||||
}
|
||||
|
||||
if (options.keyAsBuffer != null) {
|
||||
assert(typeof options.keyAsBuffer === 'boolean');
|
||||
this.keyAsBuffer = options.keyAsBuffer;
|
||||
}
|
||||
|
||||
if (options.valueAsBuffer != null) {
|
||||
assert(typeof options.valueAsBuffer === 'boolean');
|
||||
this.valueAsBuffer = options.valueAsBuffer;
|
||||
}
|
||||
|
||||
if (options.reverse != null) {
|
||||
assert(typeof options.reverse === 'boolean');
|
||||
this.reverse = options.reverse;
|
||||
}
|
||||
|
||||
if (options.limit != null) {
|
||||
assert(typeof options.limit === 'number');
|
||||
this.limit = options.limit;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
* Expose
|
||||
*/
|
||||
|
||||
module.exports = MemoryDB;
|
||||
@ -26,6 +26,7 @@ exports.PEM = require('./pem');
|
||||
exports.protobuf = require('./protobuf');
|
||||
exports.ProtoWriter = exports.protobuf.ProtoWriter;
|
||||
exports.ProtoReader = exports.protobuf.ProtoReader;
|
||||
exports.RBT = require('./rbt');
|
||||
exports.BufferReader = require('./reader');
|
||||
exports.StaticWriter = require('./staticwriter');
|
||||
exports.util = require('./util');
|
||||
|
||||
@ -6,45 +6,38 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var util = require('../utils/util');
|
||||
var assert = require('assert');
|
||||
var DUMMY = new Buffer([0]);
|
||||
var RED = 0;
|
||||
var BLACK = 1;
|
||||
var SENTINEL;
|
||||
|
||||
/**
|
||||
* An iterative red black tree.
|
||||
* Many of its options, parameters,
|
||||
* and methods mimic the leveldown
|
||||
* interface.
|
||||
* @alias module:db.RBT
|
||||
* @alias module:utils.RBT
|
||||
* @constructor
|
||||
* @param {String?} location - Phony location.
|
||||
* @param {Object?} options
|
||||
* @param {Function} options.compare - Comparator.
|
||||
* @param {Function} compare - Comparator.
|
||||
* @param {Boolean?} unique
|
||||
*/
|
||||
|
||||
function RBT(location, options) {
|
||||
function RBT(compare, unique) {
|
||||
if (!(this instanceof RBT))
|
||||
return new RBT(location, options);
|
||||
return new RBT(compare, unique);
|
||||
|
||||
if (typeof location !== 'string') {
|
||||
options = location;
|
||||
location = null;
|
||||
}
|
||||
assert(typeof compare === 'function');
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
if (typeof options === 'function')
|
||||
options = { compare: options };
|
||||
|
||||
this.options = options;
|
||||
this.root = SENTINEL;
|
||||
this.compare = options.compare || util.cmp;
|
||||
this.compare = compare;
|
||||
this.unique = unique || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the tree.
|
||||
*/
|
||||
|
||||
RBT.prototype.reset = function reset() {
|
||||
this.root = SENTINEL;
|
||||
};
|
||||
|
||||
/**
|
||||
* Do a key lookup.
|
||||
* @param {Buffer|String} key
|
||||
@ -55,14 +48,11 @@ RBT.prototype.search = function search(key) {
|
||||
var current = this.root;
|
||||
var cmp;
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'utf8');
|
||||
|
||||
while (!current.isNull()) {
|
||||
cmp = this.compare(key, current.key);
|
||||
|
||||
if (cmp === 0)
|
||||
return current.value;
|
||||
return current;
|
||||
|
||||
if (cmp < 0)
|
||||
current = current.left;
|
||||
@ -82,16 +72,11 @@ RBT.prototype.insert = function insert(key, value) {
|
||||
var left = false;
|
||||
var parent, cmp, node;
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'utf8');
|
||||
|
||||
if (typeof value === 'string')
|
||||
value = new Buffer(value, 'utf8');
|
||||
|
||||
while (!current.isNull()) {
|
||||
cmp = this.compare(key, current.key);
|
||||
|
||||
if (cmp === 0) {
|
||||
if (this.unique && cmp === 0) {
|
||||
current.key = key;
|
||||
current.value = value;
|
||||
return;
|
||||
}
|
||||
@ -112,7 +97,7 @@ RBT.prototype.insert = function insert(key, value) {
|
||||
if (!parent) {
|
||||
this.root = node;
|
||||
this.insertFixup(node);
|
||||
return;
|
||||
return node;
|
||||
}
|
||||
|
||||
node.parent = parent;
|
||||
@ -123,6 +108,8 @@ RBT.prototype.insert = function insert(key, value) {
|
||||
parent.right = node;
|
||||
|
||||
this.insertFixup(node);
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -185,15 +172,12 @@ RBT.prototype.remove = function remove(key) {
|
||||
var current = this.root;
|
||||
var cmp;
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'utf8');
|
||||
|
||||
while (!current.isNull()) {
|
||||
cmp = this.compare(key, current.key);
|
||||
|
||||
if (cmp === 0) {
|
||||
this.removeNode(current);
|
||||
return true;
|
||||
return current;
|
||||
}
|
||||
|
||||
if (cmp < 0)
|
||||
@ -201,8 +185,6 @@ RBT.prototype.remove = function remove(key) {
|
||||
else
|
||||
current = current.right;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -440,11 +422,12 @@ RBT.prototype.predecessor = function predecessor(x) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Take a snapshot and return a cloned root node.
|
||||
* Take a snapshot and return
|
||||
* a cloned root node (iterative).
|
||||
* @returns {RBTNode}
|
||||
*/
|
||||
|
||||
RBT.prototype.snapshot = function snapshot() {
|
||||
RBT.prototype.clone = function clone() {
|
||||
var current = this.root;
|
||||
var stack = [];
|
||||
var left = true;
|
||||
@ -452,19 +435,23 @@ RBT.prototype.snapshot = function snapshot() {
|
||||
|
||||
for (;;) {
|
||||
if (!current.isNull()) {
|
||||
copy = current.clone();
|
||||
|
||||
if (parent)
|
||||
copy.parent = parent;
|
||||
|
||||
if (left) {
|
||||
copy = current.clone();
|
||||
if (parent)
|
||||
parent.left = copy;
|
||||
else
|
||||
snapshot = copy;
|
||||
} else {
|
||||
copy = current.clone();
|
||||
if (parent)
|
||||
parent.right = copy;
|
||||
else
|
||||
snapshot = copy;
|
||||
}
|
||||
|
||||
stack.push(copy);
|
||||
parent = copy;
|
||||
left = true;
|
||||
@ -485,285 +472,250 @@ RBT.prototype.snapshot = function snapshot() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverse the key and filter records.
|
||||
* @param {Function} test
|
||||
* @returns {RBTNode[]} Records.
|
||||
* Take a snapshot and return
|
||||
* a cloned root node (recursive).
|
||||
* @returns {RBTNode}
|
||||
*/
|
||||
|
||||
RBT.prototype.traverse = function traverse(test) {
|
||||
var current = this.min(this.root);
|
||||
var items = [];
|
||||
RBT.prototype.snapshot = function snapshot() {
|
||||
var node = SENTINEL;
|
||||
|
||||
while (!current.isNull()) {
|
||||
if (test(current))
|
||||
items.push(current.copy());
|
||||
current = this.successor(current);
|
||||
}
|
||||
if (this.root.isNull())
|
||||
return node;
|
||||
|
||||
return items;
|
||||
node = this.root.clone();
|
||||
|
||||
copyLeft(node, node.left);
|
||||
copyRight(node, node.right);
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dump all records.
|
||||
* @returns {RBTNode[]} Records.
|
||||
* Create an iterator.
|
||||
* @param {RBTNode?} snapshot
|
||||
* @returns {Iterator}
|
||||
*/
|
||||
|
||||
RBT.prototype.dump = function dump() {
|
||||
return this.traverse(function() { return true; });
|
||||
RBT.prototype.iterator = function iterator(snapshot) {
|
||||
return new Iterator(this, snapshot || this.root);
|
||||
};
|
||||
|
||||
/**
|
||||
* Traverse between a range of keys and collect records.
|
||||
* @param {Buffer} gte
|
||||
* @param {Buffer} lte
|
||||
* @param {Buffer} min
|
||||
* @param {Buffer} max
|
||||
* @returns {RBTNode[]} Records.
|
||||
*/
|
||||
|
||||
RBT.prototype.range = function range(gte, lte) {
|
||||
var root = this.root;
|
||||
var current = SENTINEL;
|
||||
RBT.prototype.range = function range(min, max) {
|
||||
var iter = this.iterator();
|
||||
var items = [];
|
||||
var cmp;
|
||||
|
||||
if (typeof gte === 'string')
|
||||
gte = new Buffer(gte, 'utf8');
|
||||
if (min)
|
||||
iter.seekMin(min);
|
||||
else
|
||||
iter.seekFirst();
|
||||
|
||||
if (typeof lte === 'string')
|
||||
lte = new Buffer(lte, 'utf8');
|
||||
while (iter.next()) {
|
||||
if (max && iter.compare(max) > 0)
|
||||
break;
|
||||
|
||||
if (gte) {
|
||||
// Find the node closest to our gte key.
|
||||
while (!root.isNull()) {
|
||||
cmp = this.compare(gte, root.key);
|
||||
|
||||
if (cmp === 0) {
|
||||
current = root;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmp < 0) {
|
||||
current = root;
|
||||
root = root.left;
|
||||
} else {
|
||||
root = root.right;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Descend into the left subtree.
|
||||
current = this.min(root);
|
||||
}
|
||||
|
||||
// Walk the tree in order.
|
||||
while (!current.isNull()) {
|
||||
if (lte) {
|
||||
// Stop once we hit a key above our lte key.
|
||||
cmp = this.compare(lte, current.key);
|
||||
if (cmp < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
items.push(current.copy());
|
||||
current = this.successor(current);
|
||||
items.push(iter.data());
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the database (leveldown method).
|
||||
* @param {Object?} options
|
||||
* @returns {Promise}
|
||||
* Iterator
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @param {RBT} tree
|
||||
* @param {RBTNode} snapshot
|
||||
* @property {RBT} tree
|
||||
* @property {RBTNode} current
|
||||
* @property {Object} key
|
||||
* @property {Object} value
|
||||
*/
|
||||
|
||||
RBT.prototype.open = function open(options, callback) {
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
function Iterator(tree, snapshot) {
|
||||
this.tree = tree;
|
||||
this.root = snapshot;
|
||||
this.current = snapshot;
|
||||
this.key = null;
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare keys using tree's comparator.
|
||||
* @param {Object} key
|
||||
*/
|
||||
|
||||
Iterator.prototype.compare = function compare(key) {
|
||||
assert(this.key != null, 'No key.');
|
||||
return this.tree.compare(this.key, key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether current node is valid.
|
||||
*/
|
||||
|
||||
Iterator.prototype.valid = function valid() {
|
||||
return !this.current.isNull();
|
||||
};
|
||||
|
||||
/**
|
||||
* Seek to the root.
|
||||
*/
|
||||
|
||||
Iterator.prototype.reset = function reset() {
|
||||
this.current = this.root;
|
||||
this.key = null;
|
||||
this.value = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Seek to the start of the tree.
|
||||
*/
|
||||
|
||||
Iterator.prototype.seekFirst = function seekFirst() {
|
||||
this.current = this.tree.min(this.root);
|
||||
this.key = this.current.key;
|
||||
this.value = this.current.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Seek to the end of the tree.
|
||||
*/
|
||||
|
||||
Iterator.prototype.seekLast = function seekLast() {
|
||||
this.current = this.tree.max(this.root);
|
||||
this.key = this.current.key;
|
||||
this.value = this.current.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Seek to a key from the current node (gte).
|
||||
* @param {String} key
|
||||
*/
|
||||
|
||||
Iterator.prototype.seek = function seek(key) {
|
||||
return this.seekMin(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Seek to a key from the current node (gte).
|
||||
* @param {String} key
|
||||
*/
|
||||
|
||||
Iterator.prototype.seekMin = function seekMin(key) {
|
||||
var root = this.current;
|
||||
var current = SENTINEL;
|
||||
var cmp;
|
||||
|
||||
assert(key != null, 'No key passed to seek.');
|
||||
|
||||
while (!root.isNull()) {
|
||||
cmp = this.tree.compare(root.key, key);
|
||||
|
||||
if (cmp === 0) {
|
||||
current = root;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmp > 0) {
|
||||
current = root;
|
||||
root = root.left;
|
||||
} else {
|
||||
root = root.right;
|
||||
}
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
this.options = options;
|
||||
|
||||
util.nextTick(callback);
|
||||
this.current = current;
|
||||
this.key = current.key;
|
||||
this.value = current.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the database (leveldown method).
|
||||
* @returns {Promise}
|
||||
* Seek to a key from the current node (lte).
|
||||
* @param {String} key
|
||||
*/
|
||||
|
||||
RBT.prototype.close = function close(callback) {
|
||||
util.nextTick(callback);
|
||||
};
|
||||
Iterator.prototype.seekMax = function seekMax(key) {
|
||||
var root = this.current;
|
||||
var current = SENTINEL;
|
||||
var cmp;
|
||||
|
||||
/**
|
||||
* Retrieve a record (leveldown method).
|
||||
* @param {Buffer|String} key
|
||||
* @param {Object?} options
|
||||
* @returns {Promise} - Returns Buffer.
|
||||
*/
|
||||
assert(key != null, 'No key passed to seek.');
|
||||
|
||||
RBT.prototype.get = function get(key, options, callback) {
|
||||
var value, err;
|
||||
while (!root.isNull()) {
|
||||
cmp = this.tree.compare(root.key, key);
|
||||
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
if (cmp === 0) {
|
||||
current = root;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmp < 0) {
|
||||
current = root;
|
||||
root = root.right;
|
||||
} else {
|
||||
root = root.left;
|
||||
}
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
this.current = current;
|
||||
this.key = current.key;
|
||||
this.value = current.value;
|
||||
};
|
||||
|
||||
value = this.search(key);
|
||||
/**
|
||||
* Seek to previous node.
|
||||
* @param {String} key
|
||||
*/
|
||||
|
||||
if (!value) {
|
||||
err = new Error('RBT_NOTFOUND: Key not found.');
|
||||
err.notFound = true;
|
||||
err.type = 'NotFoundError';
|
||||
util.nextTick(function() {
|
||||
callback(err);
|
||||
});
|
||||
return;
|
||||
Iterator.prototype.prev = function prev() {
|
||||
if (this.current.isNull()) {
|
||||
this.key = null;
|
||||
this.value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.asBuffer === false)
|
||||
value = value.toString('utf8');
|
||||
this.key = this.current.key;
|
||||
this.value = this.current.value;
|
||||
this.current = this.tree.predecessor(this.current);
|
||||
|
||||
util.nextTick(function() {
|
||||
callback(null, value);
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Insert a record (leveldown method).
|
||||
* @param {Buffer|String} key
|
||||
* @param {Buffer} value
|
||||
* @param {Object?} options
|
||||
* @returns {Promise}
|
||||
* Seek to next node.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
RBT.prototype.put = function put(key, value, options, callback) {
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
Iterator.prototype.next = function next() {
|
||||
if (this.current.isNull()) {
|
||||
this.key = null;
|
||||
this.value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.insert(key, value);
|
||||
this.key = this.current.key;
|
||||
this.value = this.current.value;
|
||||
this.current = this.tree.successor(this.current);
|
||||
|
||||
util.nextTick(callback);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a record (leveldown method).
|
||||
* @param {Buffer|String} key
|
||||
* @param {Object?} options
|
||||
* @returns {Promise}
|
||||
* Return the current key/value pair.
|
||||
* @returns {RBTData}
|
||||
*/
|
||||
|
||||
RBT.prototype.del = function del(key, options, callback) {
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
this.remove(key);
|
||||
|
||||
util.nextTick(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an atomic batch (leveldown method).
|
||||
* @see Leveldown.Batch
|
||||
* @param {Object[]?} ops
|
||||
* @param {Object?} options
|
||||
* @returns {Promise}
|
||||
* @returns {Leveldown.Batch}
|
||||
*/
|
||||
|
||||
RBT.prototype.batch = function batch(ops, options, callback) {
|
||||
var batch;
|
||||
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
batch = new Batch(this, options);
|
||||
|
||||
if (ops) {
|
||||
batch.ops = ops.slice();
|
||||
return batch.write(callback);
|
||||
}
|
||||
|
||||
return batch;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an iterator (leveldown method).
|
||||
* @param {Object} options - See {Leveldown.Iterator}.
|
||||
* @returns {Leveldown.Iterator}.
|
||||
*/
|
||||
|
||||
RBT.prototype.iterator = function iterator(options) {
|
||||
return new Iterator(this, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a database property (leveldown method) (NOP).
|
||||
* @param {String} name - Property name.
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
RBT.prototype.getProperty = function getProperty(name) {
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate approximate database size (leveldown method).
|
||||
* @param {Buffer|String} start - Start key.
|
||||
* @param {Buffer|String} end - End key.
|
||||
* @returns {Promise} - Returns Number.
|
||||
*/
|
||||
|
||||
RBT.prototype.approximateSize = function approximateSize(start, end, callback) {
|
||||
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;
|
||||
}
|
||||
|
||||
util.nextTick(function() {
|
||||
callback(null, size);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the database (leveldown function) (NOP).
|
||||
* @param {String} location
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
RBT.destroy = function destroy(location, callback) {
|
||||
util.nextTick(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Repair the database (leveldown function) (NOP).
|
||||
* @param {String} location
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
RBT.repair = function repair(location, callback) {
|
||||
util.nextTick(callback);
|
||||
Iterator.prototype.data = function data() {
|
||||
assert(this.key != null, 'No data available.');
|
||||
return new RBTData(this.key, this.value);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -820,8 +772,8 @@ RBTNode.prototype.copy = function copy() {
|
||||
|
||||
RBTNode.prototype.inspect = function inspect() {
|
||||
return {
|
||||
key: stringify(this.key),
|
||||
value: this.value.toString('hex'),
|
||||
key: this.key,
|
||||
value: this.value,
|
||||
color: this.color === RED ? 'red' : 'black',
|
||||
left: this.left,
|
||||
right: this.right
|
||||
@ -900,266 +852,33 @@ function RBTData(key, value) {
|
||||
|
||||
RBTData.prototype.inspect = function inspect() {
|
||||
return {
|
||||
key: stringify(this.key),
|
||||
value: this.value.toString('hex')
|
||||
key: this.key,
|
||||
value: this.value
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Batch
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @private
|
||||
* @param {RBT} tree
|
||||
* @param {Object?} options
|
||||
*/
|
||||
|
||||
function Batch(tree, options) {
|
||||
this.options = options || {};
|
||||
this.ops = [];
|
||||
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(new BatchOp('put', key, value));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a record.
|
||||
* @param {Buffer|String} key
|
||||
*/
|
||||
|
||||
Batch.prototype.del = function del(key) {
|
||||
assert(this.tree, 'Already written.');
|
||||
this.ops.push(new BatchOp('del', key));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Commit the batch.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Batch.prototype.write = function write(callback) {
|
||||
var i, op;
|
||||
|
||||
if (!this.tree) {
|
||||
util.nextTick(function() {
|
||||
callback(new Error('Already written.'));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < this.ops.length; i++) {
|
||||
op = this.ops[i];
|
||||
switch (op.type) {
|
||||
case 'put':
|
||||
this.tree.insert(op.key, op.value);
|
||||
break;
|
||||
case 'del':
|
||||
this.tree.remove(op.key);
|
||||
break;
|
||||
default:
|
||||
util.nextTick(function() {
|
||||
callback(new Error('Bad operation: ' + op.type));
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.ops.length = 0;
|
||||
this.ops = null;
|
||||
this.options = null;
|
||||
this.tree = null;
|
||||
|
||||
util.nextTick(callback);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear batch of all ops.
|
||||
*/
|
||||
|
||||
Batch.prototype.clear = function clear() {
|
||||
assert(this.tree, 'Already written.');
|
||||
this.ops.length = 0;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Batch Operation
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @private
|
||||
* @param {String} type
|
||||
* @param {Buffer} key
|
||||
* @param {Buffer|null} value
|
||||
*/
|
||||
|
||||
function BatchOp(type, key, value) {
|
||||
this.type = type;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator
|
||||
* @constructor
|
||||
* @ignore
|
||||
* @private
|
||||
* @param {RBT} tree
|
||||
* @param {Object?} options
|
||||
*/
|
||||
|
||||
function Iterator(tree, options) {
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
assert(!options.lt, 'LT is not implemented.');
|
||||
assert(!options.gt, 'GT is not implemented.');
|
||||
|
||||
this.options = {
|
||||
keys: options.keys,
|
||||
values: options.values,
|
||||
gte: options.gte || options.start,
|
||||
lte: options.lte || options.end,
|
||||
keyAsBuffer: options.keyAsBuffer,
|
||||
valueAsBuffer: options.valueAsBuffer,
|
||||
reverse: options.reverse,
|
||||
limit: options.limit
|
||||
};
|
||||
|
||||
this.tree = tree;
|
||||
this.ended = false;
|
||||
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.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Iterator.prototype.next = function(callback) {
|
||||
var item, key, value;
|
||||
|
||||
if (this.ended) {
|
||||
util.nextTick(function() {
|
||||
callback(new Error('Cannot call next after end.'));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.reverse)
|
||||
item = this.snapshot[this.index--];
|
||||
else
|
||||
item = this.snapshot[this.index++];
|
||||
|
||||
if (this.options.limit != null) {
|
||||
if (this.total++ >= this.options.limit) {
|
||||
this._end();
|
||||
util.nextTick(callback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!item) {
|
||||
this._end();
|
||||
util.nextTick(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
key = item.key;
|
||||
value = item.value;
|
||||
|
||||
if (this.options.keys === false)
|
||||
key = DUMMY;
|
||||
|
||||
if (this.options.values === false)
|
||||
value = DUMMY;
|
||||
|
||||
if (this.options.keyAsBuffer === false)
|
||||
key = stringify(key);
|
||||
|
||||
if (this.options.valueAsBuffer === false)
|
||||
value = value.toString('utf8');
|
||||
|
||||
util.nextTick(function() {
|
||||
callback(null, key, value);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Seek to a key gte to `key`.
|
||||
* @param {String|Buffer} key
|
||||
*/
|
||||
|
||||
Iterator.prototype.seek = function seek(key) {
|
||||
var self = this;
|
||||
|
||||
assert(!this.ended, 'Already ended.');
|
||||
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'utf8');
|
||||
|
||||
this.index = util.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.snapshot.length = 0;
|
||||
this.snapshot = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* End the iterator. Free up snapshot.
|
||||
* @param {Buffer} callback
|
||||
*/
|
||||
|
||||
Iterator.prototype.end = function end(callback) {
|
||||
if (this.ended) {
|
||||
util.nextTick(function() {
|
||||
callback(new Error('Already ended.'));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.ended = true;
|
||||
this._end();
|
||||
|
||||
util.nextTick(callback);
|
||||
};
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
SENTINEL = new RBTSentinel();
|
||||
|
||||
function stringify(value) {
|
||||
if (Buffer.isBuffer(value))
|
||||
return value.toString('utf8');
|
||||
return value;
|
||||
function copyLeft(parent, node) {
|
||||
if (!node.isNull()) {
|
||||
parent.left = node.clone();
|
||||
parent.left.parent = parent;
|
||||
copyLeft(parent.left, node.left);
|
||||
copyRight(parent.left, node.right);
|
||||
}
|
||||
}
|
||||
|
||||
function copyRight(parent, node) {
|
||||
if (!node.isNull()) {
|
||||
parent.right = node.clone();
|
||||
parent.right.parent = parent;
|
||||
copyLeft(parent.right, node.left);
|
||||
copyRight(parent.right, node.right);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Loading…
Reference in New Issue
Block a user