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.
* @alias module:db.LDB
* @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}
*/
function LDB(options) {
var target = LDB.getTarget(options);
var cacheSize = options.cacheSize;
var result = LDB.getBackend(options);
var backend = result.backend;
var location = result.location;
if (!cacheSize)
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
});
return new LowlevelUp(backend, location, options);
}
/**
@ -67,7 +32,7 @@ function LDB(options) {
* @returns {Object}
*/
LDB.getBackend = function getBackend(db) {
LDB.getName = function getName(db) {
var name, ext;
if (!db)
@ -103,7 +68,10 @@ LDB.getBackend = function getBackend(db) {
break;
}
return { name: name, ext: ext };
return {
name: name,
ext: ext
};
};
/**
@ -112,20 +80,19 @@ LDB.getBackend = function getBackend(db) {
* @returns {Object}
*/
LDB.getTarget = function getTarget(options) {
var backend = LDB.getBackend(options.db);
LDB.getBackend = function getBackend(options) {
var result = LDB.getName(options.db);
var backend = backends.get(result.name);
var location = options.location;
var db = backends.get(backend.name);
if (typeof location !== 'string') {
assert(backend.name === 'memory', 'Location required.');
assert(result.name === 'memory', 'Location required.');
location = 'memory';
}
return {
db: db,
backend: backend.name,
location: location + '.' + backend.ext
backend: backend,
location: location + '.' + result.ext
};
};

View File

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

View File

@ -8,7 +8,6 @@
'use strict';
var assert = require('assert');
var util = require('../utils/util');
var Lock = require('../utils/lock');
var co = require('../utils/co');
var VERSION_ERROR;
@ -23,43 +22,71 @@ var VERSION_ERROR;
*
* @alias module:db.LowlevelUp
* @constructor
* @param {String} file - Location.
* @param {Object} options - Leveldown options.
* @param {Function} backend - Database backend.
* @param {String} location - File location.
* @param {Object?} options - Leveldown options.
*/
function LowlevelUp(file, options) {
function LowlevelUp(backend, location, options) {
if (!(this instanceof LowlevelUp))
return new LowlevelUp(file, options);
return new LowlevelUp(backend, location, options);
assert(typeof file === 'string', 'Filename is required.');
assert(options, 'Options are required.');
assert(options.db, 'Database backend is required.');
assert(typeof backend === 'function', 'Backend is required.');
assert(typeof location === 'string', 'Filename is required.');
this.options = options;
this.backend = options.db;
this.location = file;
this.bufferKeys = options.bufferKeys === true;
this.options = new LLUOptions(options);
this.backend = backend;
this.location = location;
this.locker = new Lock();
this.loading = false;
this.closing = false;
this.loaded = false;
this.db = new options.db(file);
this.db = null;
this.binding = null;
// Stay as close to the metal as possible.
// 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;
this.init();
}
/**
* 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.
* @method
* @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).
* @method
* @private
* @returns {Promise}
*/
@ -112,8 +126,24 @@ LowlevelUp.prototype._open = co(function* open() {
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).
* @method
* @private
* @returns {Promise}
*/
@ -322,34 +352,10 @@ LowlevelUp.prototype.batch = function batch(ops) {
*/
LowlevelUp.prototype.iterator = function iterator(options) {
var opt;
if (!this.loaded)
throw new Error('Database is closed.');
opt = {
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);
return new Iterator(this, options);
};
/**
@ -370,8 +376,8 @@ LowlevelUp.prototype.getProperty = function getProperty(name) {
/**
* Calculate approximate database size.
* @param {String} start - Start key.
* @param {String} end - End key.
* @param {String|Buffer} start - Start key.
* @param {String|Buffer} end - End key.
* @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.
* @method
@ -588,8 +619,7 @@ LowlevelUp.prototype.checkVersion = co(function* checkVersion(key, version) {
*/
LowlevelUp.prototype.clone = co(function* clone(path) {
var options = util.merge({}, this.options);
var opt = { keys: true, values: true };
var options = new LLUOptions(this.options);
var hwm = 256 << 20;
var total = 0;
var tmp, batch, iter, item;
@ -600,12 +630,16 @@ LowlevelUp.prototype.clone = co(function* clone(path) {
options.createIfMissing = true;
options.errorIfExists = true;
tmp = new LowlevelUp(path, options);
tmp = new LowlevelUp(this.backend, path, options);
yield tmp.open();
batch = tmp.batch();
iter = this.iterator(opt);
iter = this.iterator({
keys: true,
values: true
});
for (;;) {
item = yield iter.next();
@ -698,6 +732,9 @@ Batch.prototype.clear = function clear() {
*/
function Iterator(db, options) {
options = new IteratorOptions(options);
options.keyAsBuffer = db.options.bufferKeys;
this.iter = db.db.iterator(options);
}
@ -722,14 +759,14 @@ Iterator.prototype.next = function() {
return;
}
resolve(new KeyValue(key, value));
resolve(new IteratorItem(key, value));
});
});
};
/**
* Seek to an arbitrary key.
* @param {String|Buffer}
* @param {String|Buffer} 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.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) {
if (!err)
return false;

View File

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