Merge branch 'housekeeping' of https://github.com/JamesMGreene/nedb into JamesMGreene-housekeeping

pull/2/head
Louis Chatriot 9 years ago
commit 3c5cdf9b7e
  1. 15
      lib/datastore.js
  2. 21
      lib/indexes.js
  3. 8
      lib/storage.js
  4. 12
      test/db.test.js
  5. 30
      test/executor.test.js
  6. 201
      test/indexes.test.js
  7. 8
      test/persistence.test.js

@ -109,11 +109,16 @@ Datastore.prototype.resetIndexes = function (newData) {
* @param {Function} cb Optional callback, signature: err * @param {Function} cb Optional callback, signature: err
*/ */
Datastore.prototype.ensureIndex = function (options, cb) { Datastore.prototype.ensureIndex = function (options, cb) {
var callback = cb || function () {}; var err
, callback = cb || function () {};
options = options || {}; options = options || {};
if (!options.fieldName) { return callback({ missingFieldName: true }); } if (!options.fieldName) {
err = new Error("Cannot create an index without a fieldName");
err.missingFieldName = true;
return callback(err);
}
if (this.indexes[options.fieldName]) { return callback(null); } if (this.indexes[options.fieldName]) { return callback(null); }
this.indexes[options.fieldName] = new Index(options); this.indexes[options.fieldName] = new Index(options);
@ -535,9 +540,9 @@ Datastore.prototype._update = function (query, updateQuery, options, cb) {
} }
, function () { // Perform the update , function () { // Perform the update
var modifiedDoc var modifiedDoc
, candidates = self.getCandidates(query) , candidates = self.getCandidates(query)
, modifications = [] , modifications = []
; ;
// Preparing update (if an error is thrown here neither the datafile nor // Preparing update (if an error is thrown here neither the datafile nor
// the in-memory indexes are affected) // the in-memory indexes are affected)

@ -230,28 +230,20 @@ Index.prototype.revertUpdate = function (oldDoc, newDoc) {
}; };
// Append all elements in toAppend to array
function append (array, toAppend) {
var i;
for (i = 0; i < toAppend.length; i += 1) {
array.push(toAppend[i]);
}
}
/** /**
* Get all documents in index whose key match value (if it is a Thing) or one of the elements of value (if it is an array of Things) * Get all documents in index whose key match value (if it is a Thing) or one of the elements of value (if it is an array of Things)
* @param {Thing} value Value to match the key against * @param {Thing} value Value to match the key against
* @return {Array of documents} * @return {Array of documents}
*/ */
Index.prototype.getMatching = function (value) { Index.prototype.getMatching = function (value) {
var self = this; var _res, res
, self = this;
if (!util.isArray(value)) { if (!util.isArray(value)) {
return this.tree.search(value); res = self.tree.search(value);
} else { } else {
var _res = {}, res = []; _res = {};
res = [];
value.forEach(function (v) { value.forEach(function (v) {
self.getMatching(v).forEach(function (doc) { self.getMatching(v).forEach(function (doc) {
@ -262,9 +254,8 @@ Index.prototype.getMatching = function (value) {
Object.keys(_res).forEach(function (_id) { Object.keys(_res).forEach(function (_id) {
res.push(_res[_id]); res.push(_res[_id]);
}); });
return res;
} }
return res;
}; };

@ -59,11 +59,13 @@ storage.flushToStorage = function (options, callback) {
if (err) { return callback(err); } if (err) { return callback(err); }
fs.fsync(fd, function (errFS) { fs.fsync(fd, function (errFS) {
fs.close(fd, function (errC) { fs.close(fd, function (errC) {
var e = null;
if (errFS || errC) { if (errFS || errC) {
return callback({ errorOnFsync: errFS, errorOnClose: errC }); e = new Error('Failed to flush to storage');
} else { e.errorOnFsync = errFS;
return callback(null); e.errorOnClose = errC;
} }
return callback(e);
}); });
}); });
}); });

@ -89,7 +89,7 @@ describe('Database', function () {
db = new Datastore({ filename: autoDb, autoload: true, onload: onload }) db = new Datastore({ filename: autoDb, autoload: true, onload: onload })
db.find({}, function (err, docs) { db.find({}, function (err, docs) {
done("Find should not be executed since autoload failed"); done(new Error("Find should not be executed since autoload failed"));
}); });
}); });
@ -390,7 +390,7 @@ describe('Database', function () {
* *
* Note: maybe using an in-memory only NeDB would give us an easier solution * Note: maybe using an in-memory only NeDB would give us an easier solution
*/ */
it('If the callback throws an uncaught execption, dont catch it inside findOne, this is userspace concern', function (done) { it('If the callback throws an uncaught exception, do not catch it inside findOne, this is userspace concern', function (done) {
var tryCount = 0 var tryCount = 0
, currentUncaughtExceptionHandlers = process.listeners('uncaughtException') , currentUncaughtExceptionHandlers = process.listeners('uncaughtException')
, i , i
@ -399,11 +399,13 @@ describe('Database', function () {
process.removeAllListeners('uncaughtException'); process.removeAllListeners('uncaughtException');
process.on('uncaughtException', function MINE (ex) { process.on('uncaughtException', function MINE (ex) {
process.removeAllListeners('uncaughtException');
for (i = 0; i < currentUncaughtExceptionHandlers.length; i += 1) { for (i = 0; i < currentUncaughtExceptionHandlers.length; i += 1) {
process.on('uncaughtException', currentUncaughtExceptionHandlers[i]); process.on('uncaughtException', currentUncaughtExceptionHandlers[i]);
} }
ex.should.equal('SOME EXCEPTION'); ex.message.should.equal('SOME EXCEPTION');
done(); done();
}); });
@ -411,9 +413,9 @@ describe('Database', function () {
d.findOne({ a : 5}, function (err, doc) { d.findOne({ a : 5}, function (err, doc) {
if (tryCount === 0) { if (tryCount === 0) {
tryCount += 1; tryCount += 1;
throw 'SOME EXCEPTION'; throw new Error('SOME EXCEPTION');
} else { } else {
done('Callback was called twice'); done(new Error('Callback was called twice'));
} }
}); });
}); });

@ -15,25 +15,26 @@ var should = require('chai').should()
// We prevent Mocha from catching the exception we throw on purpose by remembering all current handlers, remove them and register them back after test ends // We prevent Mocha from catching the exception we throw on purpose by remembering all current handlers, remove them and register them back after test ends
function testThrowInCallback (d, done) { function testThrowInCallback (d, done) {
var currentUncaughtExceptionHandlers = process.listeners('uncaughtException'); var currentUncaughtExceptionHandlers = process.listeners('uncaughtException');
process.removeAllListeners('uncaughtException'); process.removeAllListeners('uncaughtException');
process.on('uncaughtException', function (err) { process.on('uncaughtException', function (err) {
// Do nothing with the error which is only there to test we stay on track // Do nothing with the error which is only there to test we stay on track
}); });
d.find({}, function (err) { d.find({}, function (err) {
process.nextTick(function () { process.nextTick(function () {
d.insert({ bar: 1 }, function (err) { d.insert({ bar: 1 }, function (err) {
process.removeAllListeners('uncaughtException');
for (var i = 0; i < currentUncaughtExceptionHandlers.length; i += 1) { for (var i = 0; i < currentUncaughtExceptionHandlers.length; i += 1) {
process.on('uncaughtException', currentUncaughtExceptionHandlers[i]); process.on('uncaughtException', currentUncaughtExceptionHandlers[i]);
} }
done(); done();
}); });
}); });
throw 'Some error'; throw new Error('Some error');
}); });
} }
@ -51,29 +52,34 @@ function testRightOrder (d, done) {
d.find({}, function (err, docs) { d.find({}, function (err, docs) {
docs.length.should.equal(0); docs.length.should.equal(0);
d.insert({ a: 1 }, function () { d.insert({ a: 1 }, function () {
d.update({ a: 1 }, { a: 2 }, {}, function () { d.update({ a: 1 }, { a: 2 }, {}, function () {
d.find({}, function (err, docs) { d.find({}, function (err, docs) {
docs[0].a.should.equal(2); docs[0].a.should.equal(2);
process.nextTick(function () { process.nextTick(function () {
d.update({ a: 2 }, { a: 3 }, {}, function () { d.update({ a: 2 }, { a: 3 }, {}, function () {
d.find({}, function (err, docs) { d.find({}, function (err, docs) {
docs[0].a.should.equal(3); docs[0].a.should.equal(3);
done();
process.removeAllListeners('uncaughtException');
for (var i = 0; i < currentUncaughtExceptionHandlers.length; i += 1) {
process.on('uncaughtException', currentUncaughtExceptionHandlers[i]);
}
done();
}); });
}); });
}); });
throw 'Some error'; throw new Error('Some error');
}); });
}); });
}); });
}); });
} }
// Note: The following test does not have any assertion because it // Note: The following test does not have any assertion because it

@ -120,107 +120,108 @@ describe('Indexes', function () {
assert.deepEqual(idx.tree.search('world'), []); assert.deepEqual(idx.tree.search('world'), []);
assert.deepEqual(idx.tree.search('bloup'), []); assert.deepEqual(idx.tree.search('bloup'), []);
}); });
describe('Array fields', function () {
it('Inserts one entry per array element in the index', function () {
var obj = { tf: ['aa', 'bb'], really: 'yeah' }
, obj2 = { tf: 'normal', yes: 'indeed' }
, idx = new Index({ fieldName: 'tf' })
;
idx.insert(obj);
idx.getAll().length.should.equal(2);
idx.getAll()[0].should.equal(obj);
idx.getAll()[1].should.equal(obj);
idx.insert(obj2);
idx.getAll().length.should.equal(3);
});
it('Inserts one entry per array element in the index, type-checked', function () {
var obj = { tf: ['42', 42, new Date(42), 42], really: 'yeah' }
, idx = new Index({ fieldName: 'tf' })
;
idx.insert(obj);
idx.getAll().length.should.equal(3);
idx.getAll()[0].should.equal(obj);
idx.getAll()[1].should.equal(obj);
idx.getAll()[2].should.equal(obj);
});
it('Inserts one entry per unique array element in the index, the unique constraint only holds across documents', function () {
var obj = { tf: ['aa', 'aa'], really: 'yeah' }
, obj2 = { tf: ['cc', 'yy', 'cc'], yes: 'indeed' }
, idx = new Index({ fieldName: 'tf', unique: true })
;
idx.insert(obj);
idx.getAll().length.should.equal(1);
idx.getAll()[0].should.equal(obj);
idx.insert(obj2);
idx.getAll().length.should.equal(3);
});
it('The unique constraint holds across documents', function () {
var obj = { tf: ['aa', 'aa'], really: 'yeah' }
, obj2 = { tf: ['cc', 'aa', 'cc'], yes: 'indeed' }
, idx = new Index({ fieldName: 'tf', unique: true })
;
idx.insert(obj);
idx.getAll().length.should.equal(1);
idx.getAll()[0].should.equal(obj);
(function () { idx.insert(obj2); }).should.throw();
});
it('When removing a document, remove it from the index at all unique array elements', function () {
var obj = { tf: ['aa', 'aa'], really: 'yeah' }
, obj2 = { tf: ['cc', 'aa', 'cc'], yes: 'indeed' }
, idx = new Index({ fieldName: 'tf' })
;
idx.insert(obj);
idx.insert(obj2);
idx.getMatching('aa').length.should.equal(2);
idx.getMatching('aa').indexOf(obj).should.not.equal(-1);
idx.getMatching('aa').indexOf(obj2).should.not.equal(-1);
idx.getMatching('cc').length.should.equal(1);
idx.remove(obj2);
idx.getMatching('aa').length.should.equal(1);
idx.getMatching('aa').indexOf(obj).should.not.equal(-1);
idx.getMatching('aa').indexOf(obj2).should.equal(-1);
idx.getMatching('cc').length.should.equal(0);
});
it('If a unique constraint is violated when inserting an array key, roll back all inserts before the key', function () {
var obj = { tf: ['aa', 'bb'], really: 'yeah' }
, obj2 = { tf: ['cc', 'dd', 'aa', 'ee'], yes: 'indeed' }
, idx = new Index({ fieldName: 'tf', unique: true })
;
idx.insert(obj);
idx.getAll().length.should.equal(2); describe('Array fields', function () {
idx.getMatching('aa').length.should.equal(1);
idx.getMatching('bb').length.should.equal(1); it('Inserts one entry per array element in the index', function () {
idx.getMatching('cc').length.should.equal(0); var obj = { tf: ['aa', 'bb'], really: 'yeah' }
idx.getMatching('dd').length.should.equal(0); , obj2 = { tf: 'normal', yes: 'indeed' }
idx.getMatching('ee').length.should.equal(0); , idx = new Index({ fieldName: 'tf' })
;
(function () { idx.insert(obj2); }).should.throw();
idx.getAll().length.should.equal(2); idx.insert(obj);
idx.getMatching('aa').length.should.equal(1); idx.getAll().length.should.equal(2);
idx.getMatching('bb').length.should.equal(1); idx.getAll()[0].should.equal(obj);
idx.getMatching('cc').length.should.equal(0); idx.getAll()[1].should.equal(obj);
idx.getMatching('dd').length.should.equal(0);
idx.getMatching('ee').length.should.equal(0); idx.insert(obj2);
}); idx.getAll().length.should.equal(3);
});
}); // ==== End of 'Array fields' ==== //
it('Inserts one entry per array element in the index, type-checked', function () {
var obj = { tf: ['42', 42, new Date(42), 42], really: 'yeah' }
, idx = new Index({ fieldName: 'tf' })
;
idx.insert(obj);
idx.getAll().length.should.equal(3);
idx.getAll()[0].should.equal(obj);
idx.getAll()[1].should.equal(obj);
idx.getAll()[2].should.equal(obj);
});
it('Inserts one entry per unique array element in the index, the unique constraint only holds across documents', function () {
var obj = { tf: ['aa', 'aa'], really: 'yeah' }
, obj2 = { tf: ['cc', 'yy', 'cc'], yes: 'indeed' }
, idx = new Index({ fieldName: 'tf', unique: true })
;
idx.insert(obj);
idx.getAll().length.should.equal(1);
idx.getAll()[0].should.equal(obj);
idx.insert(obj2);
idx.getAll().length.should.equal(3);
});
it('The unique constraint holds across documents', function () {
var obj = { tf: ['aa', 'aa'], really: 'yeah' }
, obj2 = { tf: ['cc', 'aa', 'cc'], yes: 'indeed' }
, idx = new Index({ fieldName: 'tf', unique: true })
;
idx.insert(obj);
idx.getAll().length.should.equal(1);
idx.getAll()[0].should.equal(obj);
(function () { idx.insert(obj2); }).should.throw();
});
it('When removing a document, remove it from the index at all unique array elements', function () {
var obj = { tf: ['aa', 'aa'], really: 'yeah' }
, obj2 = { tf: ['cc', 'aa', 'cc'], yes: 'indeed' }
, idx = new Index({ fieldName: 'tf' })
;
idx.insert(obj);
idx.insert(obj2);
idx.getMatching('aa').length.should.equal(2);
idx.getMatching('aa').indexOf(obj).should.not.equal(-1);
idx.getMatching('aa').indexOf(obj2).should.not.equal(-1);
idx.getMatching('cc').length.should.equal(1);
idx.remove(obj2);
idx.getMatching('aa').length.should.equal(1);
idx.getMatching('aa').indexOf(obj).should.not.equal(-1);
idx.getMatching('aa').indexOf(obj2).should.equal(-1);
idx.getMatching('cc').length.should.equal(0);
});
it('If a unique constraint is violated when inserting an array key, roll back all inserts before the key', function () {
var obj = { tf: ['aa', 'bb'], really: 'yeah' }
, obj2 = { tf: ['cc', 'dd', 'aa', 'ee'], yes: 'indeed' }
, idx = new Index({ fieldName: 'tf', unique: true })
;
idx.insert(obj);
idx.getAll().length.should.equal(2);
idx.getMatching('aa').length.should.equal(1);
idx.getMatching('bb').length.should.equal(1);
idx.getMatching('cc').length.should.equal(0);
idx.getMatching('dd').length.should.equal(0);
idx.getMatching('ee').length.should.equal(0);
(function () { idx.insert(obj2); }).should.throw();
idx.getAll().length.should.equal(2);
idx.getMatching('aa').length.should.equal(1);
idx.getMatching('bb').length.should.equal(1);
idx.getMatching('cc').length.should.equal(0);
idx.getMatching('dd').length.should.equal(0);
idx.getMatching('ee').length.should.equal(0);
});
}); // ==== End of 'Array fields' ==== //
}); // ==== End of 'Insertion' ==== // }); // ==== End of 'Insertion' ==== //

@ -670,7 +670,7 @@ describe('Persistence', function () {
fs.existsSync(testDb).should.equal(true); fs.existsSync(testDb).should.equal(true);
fs.existsSync(testDb + '~').should.equal(false); fs.existsSync(testDb + '~').should.equal(false);
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) { if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
throw "Datafile contents not as expected"; throw new Error("Datafile contents not as expected");
} }
done(); done();
}); });
@ -697,7 +697,7 @@ describe('Persistence', function () {
fs.existsSync(testDb).should.equal(true); fs.existsSync(testDb).should.equal(true);
fs.existsSync(testDb + '~').should.equal(false); fs.existsSync(testDb + '~').should.equal(false);
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) { if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
throw "Datafile contents not as expected"; throw new Error("Datafile contents not as expected");
} }
done(); done();
}); });
@ -721,7 +721,7 @@ describe('Persistence', function () {
fs.existsSync(testDb).should.equal(true); fs.existsSync(testDb).should.equal(true);
fs.existsSync(testDb + '~').should.equal(false); fs.existsSync(testDb + '~').should.equal(false);
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) { if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
throw "Datafile contents not as expected"; throw new Error("Datafile contents not as expected");
} }
done(); done();
}); });
@ -743,7 +743,7 @@ describe('Persistence', function () {
fs.existsSync(dbFile).should.equal(true); fs.existsSync(dbFile).should.equal(true);
fs.existsSync(dbFile + '~').should.equal(false); fs.existsSync(dbFile + '~').should.equal(false);
if (contents != "") { if (contents != "") {
throw "Datafile contents not as expected"; throw new Error("Datafile contents not as expected");
} }
done(); done();
}); });

Loading…
Cancel
Save