lowlevelup: refactor option handling.

This commit is contained in:
Christopher Jeffrey 2017-05-14 22:13:26 -07:00
parent f0cc8eb128
commit 27f89186a5
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 347 additions and 121 deletions

View File

@ -15,50 +15,15 @@ var backends = require('./backends');
* Create a database. * Create a database.
* @alias module:db.LDB * @alias module:db.LDB
* @param {Object} options * @param {Object} options
* @param {Boolean} options.compression
* @param {Number} options.cacheSize
* @param {Number} options.writeBufferSize
* @param {Number} options.maxOpenFiles
* @param {Boolean} options.sync
* @param {Number} options.mapSize
* @param {Boolean} options.writeMap
* @param {String} options.db - Database backend (`"leveldb"` by default).
* @param {String} options.name - Database name.
* @param {String} options.location - Database location (overrides `name`).
* @returns {LowlevelUp} * @returns {LowlevelUp}
*/ */
function LDB(options) { function LDB(options) {
var target = LDB.getTarget(options); var result = LDB.getBackend(options);
var cacheSize = options.cacheSize; var backend = result.backend;
var location = result.location;
if (!cacheSize) return new LowlevelUp(backend, location, options);
cacheSize = 16 << 20;
return new LowlevelUp(target.location, {
// Generic
createIfMissing: options.createIfMissing !== false,
errorIfExists: options.errorIfExists === true,
// LevelDB
compression: options.compression === true,
cacheSize: cacheSize / 2 | 0,
writeBufferSize: cacheSize / 4 | 0,
maxOpenFiles: options.maxFiles || 64,
paranoidChecks: false,
memory: false,
// LMDB
sync: options.sync || false,
mapSize: options.mapSize || 256 * (1024 << 20),
writeMap: options.writeMap || false,
noSubdir: options.noSubdir !== false,
// Browser
bufferKeys: options.bufferKeys,
db: target.db
});
} }
/** /**
@ -67,7 +32,7 @@ function LDB(options) {
* @returns {Object} * @returns {Object}
*/ */
LDB.getBackend = function getBackend(db) { LDB.getName = function getName(db) {
var name, ext; var name, ext;
if (!db) if (!db)
@ -103,7 +68,10 @@ LDB.getBackend = function getBackend(db) {
break; break;
} }
return { name: name, ext: ext }; return {
name: name,
ext: ext
};
}; };
/** /**
@ -112,20 +80,19 @@ LDB.getBackend = function getBackend(db) {
* @returns {Object} * @returns {Object}
*/ */
LDB.getTarget = function getTarget(options) { LDB.getBackend = function getBackend(options) {
var backend = LDB.getBackend(options.db); var result = LDB.getName(options.db);
var backend = backends.get(result.name);
var location = options.location; var location = options.location;
var db = backends.get(backend.name);
if (typeof location !== 'string') { if (typeof location !== 'string') {
assert(backend.name === 'memory', 'Location required.'); assert(result.name === 'memory', 'Location required.');
location = 'memory'; location = 'memory';
} }
return { return {
db: db, backend: backend,
backend: backend.name, location: location + '.' + result.ext
location: location + '.' + backend.ext
}; };
}; };

View File

@ -1,3 +1,5 @@
'use strict';
var Level = require('level-js'); var Level = require('level-js');
function DB(location) { function DB(location) {

View File

@ -8,7 +8,6 @@
'use strict'; 'use strict';
var assert = require('assert'); var assert = require('assert');
var util = require('../utils/util');
var Lock = require('../utils/lock'); var Lock = require('../utils/lock');
var co = require('../utils/co'); var co = require('../utils/co');
var VERSION_ERROR; var VERSION_ERROR;
@ -23,43 +22,71 @@ var VERSION_ERROR;
* *
* @alias module:db.LowlevelUp * @alias module:db.LowlevelUp
* @constructor * @constructor
* @param {String} file - Location. * @param {Function} backend - Database backend.
* @param {Object} options - Leveldown options. * @param {String} location - File location.
* @param {Object?} options - Leveldown options.
*/ */
function LowlevelUp(file, options) { function LowlevelUp(backend, location, options) {
if (!(this instanceof LowlevelUp)) if (!(this instanceof LowlevelUp))
return new LowlevelUp(file, options); return new LowlevelUp(backend, location, options);
assert(typeof file === 'string', 'Filename is required.'); assert(typeof backend === 'function', 'Backend is required.');
assert(options, 'Options are required.'); assert(typeof location === 'string', 'Filename is required.');
assert(options.db, 'Database backend is required.');
this.options = options; this.options = new LLUOptions(options);
this.backend = options.db; this.backend = backend;
this.location = file; this.location = location;
this.bufferKeys = options.bufferKeys === true;
this.locker = new Lock(); this.locker = new Lock();
this.loading = false; this.loading = false;
this.closing = false; this.closing = false;
this.loaded = false; this.loaded = false;
this.db = new options.db(file); this.db = null;
this.binding = null;
// Stay as close to the metal as possible. this.init();
// We want to make calls to C++ directly.
while (this.db.db && this.db.db.put && this.db.db !== this.db)
this.db = this.db.db;
this.binding = this.db;
if (this.db.binding)
this.binding = this.db.binding;
} }
/**
* Initialize the database.
* @method
* @private
*/
LowlevelUp.prototype.init = function init() {
var backend = this.backend;
var db = new backend(this.location);
var binding = db;
// Stay as close to the metal as possible.
// We want to make calls to C++ directly.
while (db.db) {
// Not a database.
if (typeof db.db.put !== 'function')
break;
// Recursive.
if (db.db === db)
break;
// Go deeper.
db = db.db;
binding = db;
}
// A lower-level binding.
if (db.binding)
binding = db.binding;
this.db = db;
this.binding = binding;
};
/** /**
* Open the database. * Open the database.
* @method
* @returns {Promise} * @returns {Promise}
*/ */
@ -72,22 +99,9 @@ LowlevelUp.prototype.open = co(function* open() {
} }
}); });
/**
* Close the database.
* @returns {Promise}
*/
LowlevelUp.prototype.close = co(function* close() {
var unlock = yield this.locker.lock();
try {
return yield this._close();
} finally {
unlock();
}
});
/** /**
* Open the database (without a lock). * Open the database (without a lock).
* @method
* @private * @private
* @returns {Promise} * @returns {Promise}
*/ */
@ -112,8 +126,24 @@ LowlevelUp.prototype._open = co(function* open() {
this.loaded = true; this.loaded = true;
}); });
/**
* Close the database.
* @method
* @returns {Promise}
*/
LowlevelUp.prototype.close = co(function* close() {
var unlock = yield this.locker.lock();
try {
return yield this._close();
} finally {
unlock();
}
});
/** /**
* Close the database (without a lock). * Close the database (without a lock).
* @method
* @private * @private
* @returns {Promise} * @returns {Promise}
*/ */
@ -322,34 +352,10 @@ LowlevelUp.prototype.batch = function batch(ops) {
*/ */
LowlevelUp.prototype.iterator = function iterator(options) { LowlevelUp.prototype.iterator = function iterator(options) {
var opt;
if (!this.loaded) if (!this.loaded)
throw new Error('Database is closed.'); throw new Error('Database is closed.');
opt = { return new Iterator(this, options);
gte: options.gte,
lte: options.lte,
keys: options.keys !== false,
values: options.values || false,
fillCache: options.fillCache || false,
keyAsBuffer: this.bufferKeys,
valueAsBuffer: true,
reverse: options.reverse || false,
highWaterMark: options.highWaterMark || 16 * 1024
};
// Workaround for a leveldown
// bug I haven't fixed yet.
if (options.limit != null)
opt.limit = options.limit;
if (options.keyAsBuffer != null)
opt.keyAsBuffer = options.keyAsBuffer;
assert(opt.keys || opt.values, 'Keys and/or values must be chosen.');
return new Iterator(this, opt);
}; };
/** /**
@ -370,8 +376,8 @@ LowlevelUp.prototype.getProperty = function getProperty(name) {
/** /**
* Calculate approximate database size. * Calculate approximate database size.
* @param {String} start - Start key. * @param {String|Buffer} start - Start key.
* @param {String} end - End key. * @param {String|Buffer} end - End key.
* @returns {Promise} - Returns Number. * @returns {Promise} - Returns Number.
*/ */
@ -393,6 +399,31 @@ LowlevelUp.prototype.approximateSize = function approximateSize(start, end) {
}); });
}; };
/**
* Compact range of keys.
* @param {String|Buffer} start - Start key.
* @param {String|Buffer} end - End key.
* @returns {Promise}
*/
LowlevelUp.prototype.compactRange = function compactRange(start, end) {
var self = this;
return new Promise(function(resolve, reject) {
if (!self.loaded) {
reject(new Error('Database is closed.'));
return;
}
if (!self.binding.compactRange) {
reject(new Error('Cannot compact range.'));
return;
}
self.binding.compactRange(start, end, co.wrap(resolve, reject));
});
};
/** /**
* Test whether a key exists. * Test whether a key exists.
* @method * @method
@ -588,8 +619,7 @@ LowlevelUp.prototype.checkVersion = co(function* checkVersion(key, version) {
*/ */
LowlevelUp.prototype.clone = co(function* clone(path) { LowlevelUp.prototype.clone = co(function* clone(path) {
var options = util.merge({}, this.options); var options = new LLUOptions(this.options);
var opt = { keys: true, values: true };
var hwm = 256 << 20; var hwm = 256 << 20;
var total = 0; var total = 0;
var tmp, batch, iter, item; var tmp, batch, iter, item;
@ -600,12 +630,16 @@ LowlevelUp.prototype.clone = co(function* clone(path) {
options.createIfMissing = true; options.createIfMissing = true;
options.errorIfExists = true; options.errorIfExists = true;
tmp = new LowlevelUp(path, options); tmp = new LowlevelUp(this.backend, path, options);
yield tmp.open(); yield tmp.open();
batch = tmp.batch(); batch = tmp.batch();
iter = this.iterator(opt);
iter = this.iterator({
keys: true,
values: true
});
for (;;) { for (;;) {
item = yield iter.next(); item = yield iter.next();
@ -698,6 +732,9 @@ Batch.prototype.clear = function clear() {
*/ */
function Iterator(db, options) { function Iterator(db, options) {
options = new IteratorOptions(options);
options.keyAsBuffer = db.options.bufferKeys;
this.iter = db.db.iterator(options); this.iter = db.db.iterator(options);
} }
@ -722,14 +759,14 @@ Iterator.prototype.next = function() {
return; return;
} }
resolve(new KeyValue(key, value)); resolve(new IteratorItem(key, value));
}); });
}); });
}; };
/** /**
* Seek to an arbitrary key. * Seek to an arbitrary key.
* @param {String|Buffer} * @param {String|Buffer} key
*/ */
Iterator.prototype.seek = function seek(key) { Iterator.prototype.seek = function seek(key) {
@ -748,15 +785,235 @@ Iterator.prototype.end = function end() {
}); });
}; };
/* /**
* Helpers * Iterator Item
* @ignore
* @constructor
* @param {String|Buffer} key
* @param {String|Buffer} value
* @property {String|Buffer} key
* @property {String|Buffer} value
*/ */
function KeyValue(key, value) { function IteratorItem(key, value) {
this.key = key; this.key = key;
this.value = value; this.value = value;
} }
/**
* LowlevelUp Options
* @constructor
* @ignore
* @param {Object} options
*/
function LLUOptions(options) {
this.createIfMissing = true;
this.errorIfExists = false;
this.compression = true;
this.cacheSize = 8 << 20;
this.writeBufferSize = 4 << 20;
this.maxOpenFiles = 64;
this.maxFileSize = 2 << 20;
this.paranoidChecks = false;
this.memory = false;
this.sync = false;
this.mapSize = 256 * (1024 << 20);
this.writeMap = false;
this.noSubdir = true;
this.bufferKeys = true;
if (options)
this.fromOptions(options);
}
/**
* Inject properties from options.
* @private
* @param {Object} options
* @returns {LLUOptions}
*/
LLUOptions.prototype.fromOptions = function fromOptions(options) {
assert(options, 'Options are required.');
if (options.createIfMissing != null) {
assert(typeof options.createIfMissing === 'boolean',
'`createIfMissing` must be a boolean.');
this.createIfMissing = options.createIfMissing;
}
if (options.errorIfExists != null) {
assert(typeof options.errorIfExists === 'boolean',
'`errorIfExists` must be a boolean.');
this.errorIfExists = options.errorIfExists;
}
if (options.compression != null) {
assert(typeof options.compression === 'boolean',
'`compression` must be a boolean.');
this.compression = options.compression;
}
if (options.cacheSize != null) {
assert(typeof options.cacheSize === 'number',
'`cacheSize` must be a number.');
assert(options.cacheSize >= 0);
this.cacheSize = Math.floor(options.cacheSize / 2);
this.writeBufferSize = Math.floor(options.cacheSize / 4);
}
if (options.maxFiles != null) {
assert(typeof options.maxFiles === 'number',
'`maxFiles` must be a number.');
assert(options.maxFiles >= 0);
this.maxOpenFiles = options.maxFiles;
}
if (options.maxFileSize != null) {
assert(typeof options.maxFileSize === 'number',
'`maxFileSize` must be a number.');
assert(options.maxFileSize >= 0);
this.maxFileSize = options.maxFileSize;
}
if (options.paranoidChecks != null) {
assert(typeof options.paranoidChecks === 'boolean',
'`paranoidChecks` must be a boolean.');
this.paranoidChecks = options.paranoidChecks;
}
if (options.memory != null) {
assert(typeof options.memory === 'boolean',
'`memory` must be a boolean.');
this.memory = options.memory;
}
if (options.sync != null) {
assert(typeof options.sync === 'boolean',
'`sync` must be a boolean.');
this.sync = options.sync;
}
if (options.mapSize != null) {
assert(typeof options.mapSize === 'number',
'`mapSize` must be a number.');
assert(options.mapSize >= 0);
this.mapSize = options.mapSize;
}
if (options.writeMap != null) {
assert(typeof options.writeMap === 'boolean',
'`writeMap` must be a boolean.');
this.writeMap = options.writeMap;
}
if (options.noSubdir != null) {
assert(typeof options.noSubdir === 'boolean',
'`noSubdir` must be a boolean.');
this.noSubdir = options.noSubdir;
}
if (options.bufferKeys != null) {
assert(typeof options.bufferKeys === 'boolean',
'`bufferKeys` must be a boolean.');
this.bufferKeys = options.bufferKeys;
}
return this;
};
/**
* Iterator Options
* @constructor
* @ignore
* @param {Object} options
*/
function IteratorOptions(options) {
this.gte = null;
this.lte = null;
this.keys = true;
this.values = false;
this.fillCache = false;
this.keyAsBuffer = true;
this.valueAsBuffer = true;
this.reverse = false;
this.highWaterMark = 16 * 1024;
// Note: do not add this property.
// this.limit = null;
if (options)
this.fromOptions(options);
}
/**
* Inject properties from options.
* @private
* @param {Object} options
* @returns {IteratorOptions}
*/
IteratorOptions.prototype.fromOptions = function fromOptions(options) {
assert(options, 'Options are required.');
if (options.gte != null) {
assert(Buffer.isBuffer(options.gte) || typeof options.gte === 'string');
this.gte = options.gte;
}
if (options.lte != null) {
assert(Buffer.isBuffer(options.lte) || typeof options.lte === 'string');
this.lte = options.lte;
}
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.fillCache != null) {
assert(typeof options.fillCache === 'boolean');
this.fillCache = options.fillCache;
}
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');
assert(options.limit >= 0);
this.limit = options.limit;
}
if (!this.keys && !this.values)
throw new Error('Keys and/or values must be chosen.');
return this;
};
/*
* Helpers
*/
function isNotFound(err) { function isNotFound(err) {
if (!err) if (!err)
return false; return false;

View File

@ -27,7 +27,7 @@
}, },
"optionalDependencies": { "optionalDependencies": {
"bcoin-native": "0.0.16", "bcoin-native": "0.0.16",
"leveldown": "1.5.0", "leveldown": "1.7.0-0",
"secp256k1": "3.2.5", "secp256k1": "3.2.5",
"socket.io": "1.7.3", "socket.io": "1.7.3",
"socket.io-client": "1.7.3" "socket.io-client": "1.7.3"