Comparing version 0.5.3 to 0.5.4
@@ -34,13 +34,18 @@ /** | ||
* Return an array with the numbers from 0 to n-1, in a random order | ||
* Uses Fisher Yates algorithm | ||
* Useful to get fair tests | ||
*/ | ||
function getRandomArray (n) { | ||
var res, next; | ||
var res = [] | ||
, i, j, temp | ||
; | ||
if (n === 0) { return []; } | ||
if (n === 1) { return [0]; } | ||
for (i = 0; i < n; i += 1) { res[i] = i; } | ||
res = getRandomArray(n - 1); | ||
next = Math.floor(Math.random() * n); | ||
res.splice(next, 0, n - 1); // Add n-1 at a random position in the array | ||
for (i = n - 1; i >= 1; i -= 1) { | ||
j = Math.floor((i + 1) * Math.random()); | ||
temp = res[i]; | ||
res[i] = res[j]; | ||
res[j] = temp; | ||
} | ||
@@ -57,2 +62,3 @@ return res; | ||
var beg = new Date() | ||
, order = getRandomArray(n) | ||
; | ||
@@ -69,3 +75,3 @@ | ||
d.insert({ docNumber: i }, function (err) { | ||
d.insert({ docNumber: order[i] }, function (err) { | ||
executeAsap(function () { | ||
@@ -154,3 +160,4 @@ runFrom(i + 1); | ||
d.update({ docNumber: order[i] }, { newDocNumber: i }, options, function (err, nr) { | ||
// Will not actually modify the document but will take the same time | ||
d.update({ docNumber: order[i] }, { docNumber: order[i] }, options, function (err, nr) { | ||
if (nr !== 1) { return cb('One update didnt work'); } | ||
@@ -179,3 +186,3 @@ executeAsap(function () { | ||
if (i === n) { // Finished | ||
console.log("Average time for one remove in a collection of " + n + " docs: " + (profiler.elapsedSinceLastStep() / n) + "ms"); | ||
console.log("Average time for one remove and one insert in a collection of " + n + " docs: " + (profiler.elapsedSinceLastStep() / n) + "ms"); | ||
profiler.step('Finished removing ' + n + ' docs'); | ||
@@ -182,0 +189,0 @@ return cb(); |
@@ -10,11 +10,26 @@ var Datastore = require('../lib/datastore') | ||
, d = new Datastore(benchDb) | ||
, n = 10000 | ||
, program = require('commander') | ||
, n | ||
; | ||
if (process.argv[2]) { n = parseInt(process.argv[2], 10); } | ||
program | ||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) | ||
.option('-i --with-index', 'Test with an index') | ||
.parse(process.argv); | ||
n = program.number || 10000; | ||
console.log("----------------------------"); | ||
console.log("Test with " + n + " documents"); | ||
console.log(program.withIndex ? "Use an index" : "Don't use an index"); | ||
console.log("----------------------------"); | ||
async.waterfall([ | ||
async.apply(commonUtilities.prepareDb, benchDb) | ||
, function (cb) { | ||
d.loadDatabase(cb); | ||
d.loadDatabase(function (err) { | ||
if (err) { return cb(err); } | ||
if (program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }); } | ||
cb(); | ||
}); | ||
} | ||
@@ -21,0 +36,0 @@ , function (cb) { profiler.beginProfiling(); return cb(); } |
@@ -10,11 +10,26 @@ var Datastore = require('../lib/datastore') | ||
, d = new Datastore(benchDb) | ||
, n = 10000 | ||
, program = require('commander') | ||
, n | ||
; | ||
if (process.argv[2]) { n = parseInt(process.argv[2], 10); } | ||
program | ||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) | ||
.option('-i --with-index', 'Test with an index') | ||
.parse(process.argv); | ||
n = program.number || 10000; | ||
console.log("----------------------------"); | ||
console.log("Test with " + n + " documents"); | ||
console.log(program.withIndex ? "Use an index" : "Don't use an index"); | ||
console.log("----------------------------"); | ||
async.waterfall([ | ||
async.apply(commonUtilities.prepareDb, benchDb) | ||
, function (cb) { | ||
d.loadDatabase(cb); | ||
d.loadDatabase(function (err) { | ||
if (err) { return cb(err); } | ||
if (program.withIndex) { d.ensureIndex({ fieldName: 'docNumber' }); } | ||
cb(); | ||
}); | ||
} | ||
@@ -21,0 +36,0 @@ , function (cb) { profiler.beginProfiling(); return cb(); } |
@@ -7,12 +7,33 @@ var Datastore = require('../lib/datastore') | ||
, profiler = new execTime('INSERT BENCH') | ||
, n = 10000 | ||
, d = new Datastore(benchDb) | ||
, program = require('commander') | ||
, n | ||
; | ||
if (process.argv[2]) { n = parseInt(process.argv[2], 10); } | ||
program | ||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) | ||
.option('-i --with-index', 'Test with an index') | ||
.parse(process.argv); | ||
n = program.number || 10000; | ||
console.log("----------------------------"); | ||
console.log("Test with " + n + " documents"); | ||
console.log(program.withIndex ? "Use an index" : "Don't use an index"); | ||
console.log("----------------------------"); | ||
async.waterfall([ | ||
async.apply(commonUtilities.prepareDb, benchDb) | ||
, function (cb) { | ||
d.loadDatabase(cb); | ||
d.loadDatabase(function (err) { | ||
if (err) { return cb(err); } | ||
if (program.withIndex) { | ||
d.ensureIndex({ fieldName: 'docNumber' }); | ||
n = 2 * n; // We will actually insert twice as many documents | ||
// because the index is slower when the collection is already | ||
// big. So the result given by the algorithm will be a bit worse than | ||
// actual performance | ||
} | ||
cb(); | ||
}); | ||
} | ||
@@ -19,0 +40,0 @@ , function (cb) { profiler.beginProfiling(); return cb(); } |
@@ -10,10 +10,29 @@ var Datastore = require('../lib/datastore') | ||
, d = new Datastore(benchDb) | ||
, n = 10000 | ||
, program = require('commander') | ||
, n | ||
; | ||
if (process.argv[2]) { n = parseInt(process.argv[2], 10); } | ||
program | ||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) | ||
.option('-i --with-index', 'Test with an index') | ||
.parse(process.argv); | ||
n = program.number || 10000; | ||
console.log("----------------------------"); | ||
console.log("Test with " + n + " documents"); | ||
console.log(program.withIndex ? "Use an index" : "Don't use an index"); | ||
console.log("----------------------------"); | ||
async.waterfall([ | ||
async.apply(commonUtilities.prepareDb, benchDb) | ||
, function (cb) { d.loadDatabase(cb); } | ||
, function (cb) { | ||
d.loadDatabase(function (err) { | ||
if (err) { return cb(err); } | ||
if (program.withIndex) { | ||
d.ensureIndex({ fieldName: 'docNumber' }); | ||
} | ||
cb(); | ||
}); | ||
} | ||
, function (cb) { profiler.beginProfiling(); return cb(); } | ||
@@ -20,0 +39,0 @@ , async.apply(commonUtilities.insertDocs, d, n, profiler) |
@@ -10,10 +10,29 @@ var Datastore = require('../lib/datastore') | ||
, d = new Datastore(benchDb) | ||
, n = 10000 | ||
, program = require('commander') | ||
, n | ||
; | ||
if (process.argv[2]) { n = parseInt(process.argv[2], 10); } | ||
program | ||
.option('-n --number [number]', 'Size of the collection to test on', parseInt) | ||
.option('-i --with-index', 'Test with an index') | ||
.parse(process.argv); | ||
n = program.number || 10000; | ||
console.log("----------------------------"); | ||
console.log("Test with " + n + " documents"); | ||
console.log(program.withIndex ? "Use an index" : "Don't use an index"); | ||
console.log("----------------------------"); | ||
async.waterfall([ | ||
async.apply(commonUtilities.prepareDb, benchDb) | ||
, function (cb) { d.loadDatabase(cb); } | ||
, function (cb) { | ||
d.loadDatabase(function (err) { | ||
if (err) { return cb(err); } | ||
if (program.withIndex) { | ||
d.ensureIndex({ fieldName: 'docNumber' }); | ||
} | ||
cb(); | ||
}); | ||
} | ||
, function (cb) { profiler.beginProfiling(); return cb(); } | ||
@@ -20,0 +39,0 @@ , async.apply(commonUtilities.insertDocs, d, n, profiler) |
@@ -25,2 +25,4 @@ var fs = require('fs') | ||
* that's not an issue here | ||
* The probability of a collision is extremely small (need 3*10^12 documents to have one chance in a million of a collision) | ||
* See http://en.wikipedia.org/wiki/Birthday_problem | ||
*/ | ||
@@ -27,0 +29,0 @@ function uid (len) { |
@@ -7,2 +7,5 @@ var fs = require('fs') | ||
, Executor = require('./executor') | ||
, Index = require('./indexes') | ||
, util = require('util') | ||
, _ = require('underscore') | ||
; | ||
@@ -22,2 +25,5 @@ | ||
this.datafileSize = 0; | ||
// Indexed by field name, dot notation can be used | ||
this.indexes = {}; | ||
} | ||
@@ -27,2 +33,102 @@ | ||
/** | ||
* Reset all currently defined indexes | ||
*/ | ||
Datastore.prototype.resetIndexes = function (newData) { | ||
var self = this; | ||
Object.keys(this.indexes).forEach(function (i) { | ||
self.indexes[i].reset(newData); | ||
}); | ||
}; | ||
/** | ||
* Ensure an index is kept for this field. Same parameters as lib/indexes | ||
* For now this function is synchronous, we need to test how much time it takes | ||
* @param {String} options.fieldName | ||
* @param {Boolean} options.unique | ||
* @param {Boolean} options.sparse | ||
* @return {Boolean} true if index was created or already exists, false otherwise | ||
*/ | ||
Datastore.prototype.ensureIndex = function (options) { | ||
options = options || {}; | ||
if (!options.fieldName) { return false; } | ||
if (this.indexes[options.fieldName]) { return true; } | ||
options.datastore = this; | ||
this.indexes[options.fieldName] = new Index(options); | ||
this.indexes[options.fieldName].insert(this.data); | ||
return true; | ||
}; | ||
/** | ||
* Add one or several document(s) to all indexes | ||
*/ | ||
Datastore.prototype.addToIndexes = function (doc) { | ||
var self = this; | ||
Object.keys(this.indexes).forEach(function (i) { | ||
self.indexes[i].insert(doc); | ||
}); | ||
}; | ||
/** | ||
* Remove one or several document(s) from all indexes | ||
*/ | ||
Datastore.prototype.removeFromIndexes = function (doc) { | ||
var self = this; | ||
Object.keys(this.indexes).forEach(function (i) { | ||
self.indexes[i].remove(doc); | ||
}); | ||
}; | ||
/** | ||
* Update a document in all indexes | ||
*/ | ||
Datastore.prototype.removeFromIndexes = function (doc, newDoc) { | ||
var self = this; | ||
Object.keys(this.indexes).forEach(function (i) { | ||
self.indexes[i].update(doc, newDoc); | ||
}); | ||
}; | ||
/** | ||
* Return the list of candidates for a given query | ||
* Very crude implementation for now, we return the candidates given by the first usable index if any | ||
* Also indexes can only be used for direct matches (no $lt, $gt or array yet) | ||
* This still gives a huge performance boost to finds (800x on a collection with 10k documents) | ||
*/ | ||
Datastore.prototype.getCandidates = function (query) { | ||
var indexNames = Object.keys(this.indexes) | ||
, usableQueryKeys; | ||
if (indexNames.length === 0) { return this.data; } // No index defined, no specific candidate | ||
// Usable query keys are the ones corresponding to a basic query (no use of $operators or arrays) | ||
usableQueryKeys = []; | ||
Object.keys(query).forEach(function (k) { | ||
if (typeof query[k] === 'string' || typeof query[k] === 'number' || typeof query[k] === 'boolean' || util.isDate(query[k]) || query[k] === null) { | ||
usableQueryKeys.push(k); | ||
} | ||
}); | ||
usableQueryKeys = _.intersection(usableQueryKeys, indexNames); | ||
if (usableQueryKeys.length > 0) { | ||
return this.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]]); | ||
} else { | ||
return this.data; | ||
} | ||
}; | ||
/** | ||
* Load the database | ||
@@ -52,2 +158,3 @@ * This means pulling data out of the data file or creating it if it doesn't exist | ||
self.datafileSize = self.data.length; | ||
self.resetIndexes(self.data); | ||
self.persistCachedDatabase(callback); | ||
@@ -131,6 +238,8 @@ }); | ||
, persistableNewDoc | ||
, insertedDoc | ||
; | ||
// Ensure the document has the right format | ||
try { | ||
newDoc._id = newDoc._id || customUtils.uid(16); | ||
newDoc._id = customUtils.uid(16); | ||
persistableNewDoc = model.serialize(newDoc); | ||
@@ -141,7 +250,9 @@ } catch (e) { | ||
insertedDoc = model.deserialize(persistableNewDoc); | ||
fs.appendFile(self.filename, persistableNewDoc + '\n', 'utf8', function (err) { | ||
if (err) { return callback(err); } | ||
var insertedDoc = model.deserialize(persistableNewDoc); | ||
self.data.push(insertedDoc); | ||
self.addToIndexes(insertedDoc); | ||
self.datafileSize += 1; | ||
@@ -164,2 +275,3 @@ return callback(null, model.deepCopy(insertedDoc)); | ||
, self = this | ||
, candidates = this.getCandidates(query) | ||
, i | ||
@@ -169,5 +281,5 @@ ; | ||
try { | ||
for (i = 0; i < self.data.length; i += 1) { | ||
if (model.match(self.data[i], query)) { | ||
res.push(model.deepCopy(self.data[i])); | ||
for (i = 0; i < candidates.length; i += 1) { | ||
if (model.match(candidates[i], query)) { | ||
res.push(model.deepCopy(candidates[i])); | ||
} | ||
@@ -189,2 +301,3 @@ } | ||
var self = this | ||
, candidates = this.getCandidates(query) | ||
, i | ||
@@ -194,5 +307,5 @@ ; | ||
try { | ||
for (i = 0; i < self.data.length; i += 1) { | ||
if (model.match(self.data[i], query)) { | ||
return callback(null, model.deepCopy(self.data[i])); | ||
for (i = 0; i < candidates.length; i += 1) { | ||
if (model.match(candidates[i], query)) { | ||
return callback(null, model.deepCopy(candidates[i])); | ||
} | ||
@@ -252,2 +365,3 @@ } | ||
, updatedDocs = [] | ||
, candidates | ||
, i | ||
@@ -280,8 +394,10 @@ ; | ||
, function () { // Perform the update | ||
candidates = self.getCandidates(query) | ||
try { | ||
for (i = 0; i < self.data.length; i += 1) { | ||
if (model.match(self.data[i], query) && (multi || numReplaced === 0)) { | ||
for (i = 0; i < candidates.length; i += 1) { | ||
if (model.match(candidates[i], query) && (multi || numReplaced === 0)) { | ||
numReplaced += 1; | ||
self.data[i] = model.modify(self.data[i], updateQuery); | ||
updatedDocs.push(self.data[i]); | ||
candidates[i] = model.modify(candidates[i], updateQuery); | ||
updatedDocs.push(candidates[i]); | ||
} | ||
@@ -318,2 +434,3 @@ } | ||
, self = this | ||
//, candidates = this.getCandidates(query) | ||
, numRemoved = 0 | ||
@@ -334,2 +451,3 @@ , multi | ||
removedDocs.push({ $$deleted: true, _id: d._id }); | ||
self.removeFromIndexes(d); | ||
} else { | ||
@@ -354,2 +472,4 @@ newData.push(d); | ||
module.exports = Datastore; |
@@ -133,2 +133,81 @@ /** | ||
/** | ||
* Utility functions for comparing things | ||
* Assumes type checking was already done (a and b already have the same type) | ||
* compareNSB works for numbers, strings and booleans | ||
*/ | ||
function compareNSB (a, b) { | ||
if (a < b) { return -1; } | ||
if (a > b) { return 1; } | ||
return 0; | ||
} | ||
function compareArrays (a, b) { | ||
var i, comp; | ||
for (i = 0; i < Math.min(a.length, b.length); i += 1) { | ||
comp = compareThings(a[i], b[i]); | ||
if (comp !== 0) { return comp; } | ||
} | ||
// Common section was identical, longest one wins | ||
return compareNSB(a.length, b.length); | ||
} | ||
/** | ||
* Compare { things U undefined } | ||
* Things are defined as any native types (string, number, boolean, null, date) and objects | ||
* We need to compare with undefined as it will be used in indexes | ||
* In the case of objects and arrays, we compare the serialized versions | ||
* If two objects dont have the same type, the (arbitrary) type hierarchy is: undefined, null, number, strings, boolean, dates, arrays, objects | ||
* Return -1 if a < b, 1 if a > b and 0 if a = b (note that equality here is NOT the same as defined in areThingsEqual!) | ||
*/ | ||
function compareThings (a, b) { | ||
var aKeys, bKeys, comp, i; | ||
// undefined | ||
if (a === undefined) { return b === undefined ? 0 : -1; } | ||
if (b === undefined) { return a === undefined ? 0 : 1; } | ||
// null | ||
if (a === null) { return b === null ? 0 : -1; } | ||
if (b === null) { return a === null ? 0 : 1; } | ||
// Numbers | ||
if (typeof a === 'number') { return typeof b === 'number' ? compareNSB(a, b) : -1; } | ||
if (typeof b === 'number') { return typeof a === 'number' ? compareNSB(a, b) : 1; } | ||
// Strings | ||
if (typeof a === 'string') { return typeof b === 'string' ? compareNSB(a, b) : -1; } | ||
if (typeof b === 'string') { return typeof a === 'string' ? compareNSB(a, b) : 1; } | ||
// Booleans | ||
if (typeof a === 'boolean') { return typeof b === 'boolean' ? compareNSB(a, b) : -1; } | ||
if (typeof b === 'boolean') { return typeof a === 'boolean' ? compareNSB(a, b) : 1; } | ||
// Dates | ||
if (util.isDate(a)) { return util.isDate(b) ? compareNSB(a.getTime(), b.getTime()) : -1; } | ||
if (util.isDate(b)) { return util.isDate(a) ? compareNSB(a.getTime(), b.getTime()) : 1; } | ||
// Arrays (first element is most significant and so on) | ||
if (util.isArray(a)) { return util.isArray(b) ? compareArrays(a, b) : -1; } | ||
if (util.isArray(b)) { return util.isArray(a) ? compareArrays(a, b) : 1; } | ||
// Objects | ||
aKeys = Object.keys(a).sort(); | ||
bKeys = Object.keys(b).sort(); | ||
for (i = 0; i < Math.min(aKeys.length, bKeys.length); i += 1) { | ||
comp = compareThings(a[aKeys[i]], b[bKeys[i]]); | ||
if (comp !== 0) { return comp; } | ||
} | ||
return compareNSB(aKeys.length, bKeys.length); | ||
} | ||
// ============================================================== | ||
@@ -230,2 +309,3 @@ // Updating documents | ||
checkObject(newDoc); | ||
if (obj._id !== newDoc._id) { throw "You can't change a document's _id"; } | ||
return newDoc; | ||
@@ -483,1 +563,2 @@ }; | ||
module.exports.areThingsEqual = areThingsEqual; | ||
module.exports.compareThings = compareThings; |
{ | ||
"name": "nedb", | ||
"version": "0.5.3", | ||
"version": "0.5.4", | ||
"author": { | ||
@@ -24,3 +24,4 @@ "name": "tldr.io", | ||
"async": "~0.2.8", | ||
"underscore": "~1.4.4" | ||
"underscore": "~1.4.4", | ||
"binary-search-tree": "0.1.2" | ||
}, | ||
@@ -32,3 +33,4 @@ "devDependencies": { | ||
"sinon": "1.3.x", | ||
"exec-time": "0.0.2" | ||
"exec-time": "0.0.2", | ||
"commander": "1.1.1" | ||
}, | ||
@@ -35,0 +37,0 @@ "scripts": { |
@@ -46,4 +46,3 @@ # NeDB (Node embedded database) | ||
If you specify an `_id` field, it will be used as the document's id, otherwise nedb will generate one randomly. | ||
Note that the generated `_id` is a simple string, not an `ObjectId`. | ||
An `_id` field will be automatically generated by NeDB. It's a 16-characters alphanumerical string that cannot be modified once it has been generated. Unlike with MongoDB, you cannot specify it (that shouldn't be a problem anyway). | ||
@@ -193,2 +192,4 @@ Field names cannot begin by '$' or contain a '.'. | ||
**Note**: you can't change a document's _id. | ||
```javascript | ||
@@ -275,8 +276,8 @@ // Let's use the same example collection as in the "finding document" part | ||
As such, it was not designed for speed. That said, it is still pretty fast on the expected datasets (10,000 | ||
documents max). On my machine (3 years old, no SSD), with a collection | ||
containing 10,000 documents and with no index (they are not implemented yet): | ||
* An insert takes 0.1 ms | ||
* A read takes 6.4 ms | ||
* An update takes 9.2 ms | ||
* A deletion takes 8.1 ms | ||
documents). On my machine (3 years old, no SSD), with a collection | ||
containing 10,000 documents: | ||
* An insert takes **0.14 ms** (or **0.16 ms** with indexing) | ||
* A read takes **6.4 ms** (or **0.02 ms** with indexing) | ||
* An update takes **9.2 ms** (or **0.2 ms** with indexing) | ||
* A deletion takes 8.1 ms (no speed boost with indexes currently due to the underlying data structure which I will change) | ||
@@ -283,0 +284,0 @@ You can run the simple benchmarks I use by executing the scripts in the `benchmarks` folder. They all take an optional parameter which is the size of the dataset to use (default is 10,000). |
@@ -15,5 +15,7 @@ var Datastore = require('../lib/datastore') | ||
describe('Database', function () { | ||
var d = new Datastore(testDb); | ||
var d; | ||
beforeEach(function (done) { | ||
d = new Datastore(testDb); | ||
async.waterfall([ | ||
@@ -293,3 +295,3 @@ function (cb) { | ||
it('If an _id is already given when we insert a document, use it and not the default uid', function (done) { | ||
it('If an _id is already given when we insert a document, dont use it but use an automatic one', function (done) { | ||
d.insert({ _id: 'test', stuff: true }, function (err, newDoc) { | ||
@@ -299,3 +301,3 @@ if (err) { return done(err); } | ||
newDoc.stuff.should.equal(true); | ||
newDoc._id.should.equal('test'); | ||
newDoc._id.should.not.equal('test'); | ||
@@ -823,3 +825,3 @@ done(); | ||
d.insert({ a: 2 }, function (err, newDoc) { | ||
d.update({ a: 2 }, { _id: 'nope' }, {}, function (err) { | ||
d.update({ a: 2 }, { a: 2, _id: 'nope' }, {}, function (err) { | ||
assert.isDefined(err); | ||
@@ -833,3 +835,14 @@ | ||
done(); | ||
d.update({ a: 2 }, { $set: { _id: 'nope' } }, {}, function (err) { | ||
assert.isDefined(err); | ||
d.find({}, function (err, docs) { | ||
docs.length.should.equal(1); | ||
Object.keys(docs[0]).length.should.equal(2); | ||
docs[0].a.should.equal(2); | ||
docs[0]._id.should.equal(newDoc._id); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -1104,2 +1117,160 @@ }); | ||
describe('Using indexes', function () { | ||
describe('ensureIndex', function () { | ||
it('ensureIndex can be called right after a loadDatabase and be initialized and filled correctly', function (done) { | ||
var now = new Date() | ||
, rawData = model.serialize({ _id: "aaa", z: "1", a: 2, ages: [1, 5, 12] }) + '\n' + | ||
model.serialize({ _id: "bbb", z: "2", hello: 'world' }) + '\n' + | ||
model.serialize({ _id: "ccc", z: "3", nested: { today: now } }) | ||
; | ||
d.data.length.should.equal(0); | ||
d.datafileSize.should.equal(0); | ||
fs.writeFile(testDb, rawData, 'utf8', function () { | ||
d.loadDatabase(function () { | ||
d.data.length.should.equal(3); | ||
d.datafileSize.should.equal(3); | ||
assert.deepEqual(d.indexes, {}); | ||
d.ensureIndex({ fieldName: 'z' }); | ||
d.indexes.z.fieldName.should.equal('z'); | ||
d.indexes.z.unique.should.equal(false); | ||
d.indexes.z.sparse.should.equal(false); | ||
d.indexes.z.tree.getNumberOfKeys().should.equal(3); | ||
d.indexes.z.tree.search('1')[0].should.equal(d.data[0]); | ||
d.indexes.z.tree.search('2')[0].should.equal(d.data[1]); | ||
d.indexes.z.tree.search('3')[0].should.equal(d.data[2]); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('ensureIndex can be called after the data set was modified and still be correct', function (done) { | ||
var rawData = model.serialize({ _id: "aaa", z: "1", a: 2, ages: [1, 5, 12] }) + '\n' + | ||
model.serialize({ _id: "bbb", z: "2", hello: 'world' }) | ||
; | ||
d.data.length.should.equal(0); | ||
d.datafileSize.should.equal(0); | ||
fs.writeFile(testDb, rawData, 'utf8', function () { | ||
d.loadDatabase(function () { | ||
d.data.length.should.equal(2); | ||
d.datafileSize.should.equal(2); | ||
assert.deepEqual(d.indexes, {}); | ||
d.insert({ z: "12", yes: 'yes' }, function (err, newDoc1) { | ||
d.insert({ z: "14", nope: 'nope' }, function (err, newDoc2) { | ||
d.remove({ z: "2" }, {}, function () { | ||
d.update({ z: "1" }, { $set: { 'yes': 'yep' } }, {}, function () { | ||
assert.deepEqual(d.indexes, {}); | ||
d.ensureIndex({ fieldName: 'z' }); | ||
d.indexes.z.fieldName.should.equal('z'); | ||
d.indexes.z.unique.should.equal(false); | ||
d.indexes.z.sparse.should.equal(false); | ||
d.indexes.z.tree.getNumberOfKeys().should.equal(3); | ||
d.indexes.z.tree.search('1')[0].should.equal(d.data[0]); | ||
assert.deepEqual(d.data[0], { _id: "aaa", z: "1", a: 2, ages: [1, 5, 12], yes: 'yep' }); | ||
d.indexes.z.tree.search('12')[0].should.equal(d.data[1]); | ||
assert.deepEqual(d.data[1], { _id: newDoc1._id, z: "12", yes: 'yes' }); | ||
d.indexes.z.tree.search('14')[0].should.equal(d.data[2]); | ||
assert.deepEqual(d.data[2], { _id: newDoc2._id, z: "14", nope: 'nope' }); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('ensureIndex can be called before a loadDatabase and still be initialized and filled correctly', function (done) { | ||
var now = new Date() | ||
, rawData = model.serialize({ _id: "aaa", z: "1", a: 2, ages: [1, 5, 12] }) + '\n' + | ||
model.serialize({ _id: "bbb", z: "2", hello: 'world' }) + '\n' + | ||
model.serialize({ _id: "ccc", z: "3", nested: { today: now } }) | ||
; | ||
d.data.length.should.equal(0); | ||
d.datafileSize.should.equal(0); | ||
d.ensureIndex({ fieldName: 'z' }); | ||
d.indexes.z.fieldName.should.equal('z'); | ||
d.indexes.z.unique.should.equal(false); | ||
d.indexes.z.sparse.should.equal(false); | ||
d.indexes.z.tree.getNumberOfKeys().should.equal(0); | ||
fs.writeFile(testDb, rawData, 'utf8', function () { | ||
d.loadDatabase(function () { | ||
d.data.length.should.equal(3); | ||
d.datafileSize.should.equal(3); | ||
d.indexes.z.tree.getNumberOfKeys().should.equal(3); | ||
d.indexes.z.tree.search('1')[0].should.equal(d.data[0]); | ||
d.indexes.z.tree.search('2')[0].should.equal(d.data[1]); | ||
d.indexes.z.tree.search('3')[0].should.equal(d.data[2]); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); // ==== End of 'ensureIndex' ==== // | ||
describe('Indexing newly inserted documents', function () { | ||
it('Newly inserted documents are indexed', function (done) { | ||
d.ensureIndex({ fieldName: 'z' }); | ||
d.indexes.z.tree.getNumberOfKeys().should.equal(0); | ||
d.insert({ a: 2, z: 'yes' }, function (err, newDoc) { | ||
d.indexes.z.tree.getNumberOfKeys().should.equal(1); | ||
assert.deepEqual(d.indexes.z.getMatching('yes'), [newDoc]); | ||
d.insert({ a: 5, z: 'nope' }, function (err, newDoc) { | ||
d.indexes.z.tree.getNumberOfKeys().should.equal(2); | ||
assert.deepEqual(d.indexes.z.getMatching('nope'), [newDoc]); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('Can insert two docs at the same key for a non unique index', function (done) { | ||
d.ensureIndex({ fieldName: 'z' }); | ||
d.indexes.z.tree.getNumberOfKeys().should.equal(0); | ||
d.insert({ a: 2, z: 'yes' }, function (err, newDoc) { | ||
d.indexes.z.tree.getNumberOfKeys().should.equal(1); | ||
assert.deepEqual(d.indexes.z.getMatching('yes'), [newDoc]); | ||
d.insert({ a: 5, z: 'yes' }, function (err, newDoc2) { | ||
d.indexes.z.tree.getNumberOfKeys().should.equal(1); | ||
assert.deepEqual(d.indexes.z.getMatching('yes'), [newDoc, newDoc2]); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); // ==== End of '' ==== // | ||
}); // ==== End of 'Using indexes' ==== // | ||
}); |
@@ -332,2 +332,131 @@ var model = require('../lib/model') | ||
describe('Comparing things', function () { | ||
it('undefined is the smallest', function () { | ||
var otherStuff = [null, "string", "", -1, 0, 5.3, 12, true, false, new Date(12345), {}, { hello: 'world' }, [], ['quite', 5]]; | ||
model.compareThings(undefined, undefined).should.equal(0); | ||
otherStuff.forEach(function (stuff) { | ||
model.compareThings(undefined, stuff).should.equal(-1); | ||
model.compareThings(stuff, undefined).should.equal(1); | ||
}); | ||
}); | ||
it('Then null', function () { | ||
var otherStuff = ["string", "", -1, 0, 5.3, 12, true, false, new Date(12345), {}, { hello: 'world' }, [], ['quite', 5]]; | ||
model.compareThings(null, null).should.equal(0); | ||
otherStuff.forEach(function (stuff) { | ||
model.compareThings(null, stuff).should.equal(-1); | ||
model.compareThings(stuff, null).should.equal(1); | ||
}); | ||
}); | ||
it('Then numbers', function () { | ||
var otherStuff = ["string", "", true, false, new Date(4312), {}, { hello: 'world' }, [], ['quite', 5]] | ||
, numbers = [-12, 0, 12, 5.7]; | ||
model.compareThings(-12, 0).should.equal(-1); | ||
model.compareThings(0, -3).should.equal(1); | ||
model.compareThings(5.7, 2).should.equal(1); | ||
model.compareThings(5.7, 12.3).should.equal(-1); | ||
model.compareThings(0, 0).should.equal(0); | ||
model.compareThings(-2.6, -2.6).should.equal(0); | ||
model.compareThings(5, 5).should.equal(0); | ||
otherStuff.forEach(function (stuff) { | ||
numbers.forEach(function (number) { | ||
model.compareThings(number, stuff).should.equal(-1); | ||
model.compareThings(stuff, number).should.equal(1); | ||
}); | ||
}); | ||
}); | ||
it('Then strings', function () { | ||
var otherStuff = [true, false, new Date(4321), {}, { hello: 'world' }, [], ['quite', 5]] | ||
, strings = ['', 'string', 'hello world']; | ||
model.compareThings('', 'hey').should.equal(-1); | ||
model.compareThings('hey', '').should.equal(1); | ||
model.compareThings('hey', 'hew').should.equal(1); | ||
model.compareThings('hey', 'hey').should.equal(0); | ||
otherStuff.forEach(function (stuff) { | ||
strings.forEach(function (string) { | ||
model.compareThings(string, stuff).should.equal(-1); | ||
model.compareThings(stuff, string).should.equal(1); | ||
}); | ||
}); | ||
}); | ||
it('Then booleans', function () { | ||
var otherStuff = [new Date(4321), {}, { hello: 'world' }, [], ['quite', 5]] | ||
, bools = [true, false]; | ||
model.compareThings(true, true).should.equal(0); | ||
model.compareThings(false, false).should.equal(0); | ||
model.compareThings(true, false).should.equal(1); | ||
model.compareThings(false, true).should.equal(-1); | ||
otherStuff.forEach(function (stuff) { | ||
bools.forEach(function (bool) { | ||
model.compareThings(bool, stuff).should.equal(-1); | ||
model.compareThings(stuff, bool).should.equal(1); | ||
}); | ||
}); | ||
}); | ||
it('Then dates', function () { | ||
var otherStuff = [{}, { hello: 'world' }, [], ['quite', 5]] | ||
, dates = [new Date(-123), new Date(), new Date(5555), new Date(0)] | ||
, now = new Date(); | ||
model.compareThings(now, now).should.equal(0); | ||
model.compareThings(new Date(54341), now).should.equal(-1); | ||
model.compareThings(now, new Date(54341)).should.equal(1); | ||
model.compareThings(new Date(0), new Date(-54341)).should.equal(1); | ||
model.compareThings(new Date(123), new Date(4341)).should.equal(-1); | ||
otherStuff.forEach(function (stuff) { | ||
dates.forEach(function (date) { | ||
model.compareThings(date, stuff).should.equal(-1); | ||
model.compareThings(stuff, date).should.equal(1); | ||
}); | ||
}); | ||
}); | ||
it('Then arrays', function () { | ||
var otherStuff = [{}, { hello: 'world' }] | ||
, arrays = [[], ['yes'], ['hello', 5]] | ||
; | ||
model.compareThings([], []).should.equal(0); | ||
model.compareThings(['hello'], []).should.equal(1); | ||
model.compareThings([], ['hello']).should.equal(-1); | ||
model.compareThings(['hello'], ['hello', 'world']).should.equal(-1); | ||
model.compareThings(['hello', 'earth'], ['hello', 'world']).should.equal(-1); | ||
model.compareThings(['hello', 'zzz'], ['hello', 'world']).should.equal(1); | ||
model.compareThings(['hello', 'world'], ['hello', 'world']).should.equal(0); | ||
otherStuff.forEach(function (stuff) { | ||
arrays.forEach(function (array) { | ||
model.compareThings(array, stuff).should.equal(-1); | ||
model.compareThings(stuff, array).should.equal(1); | ||
}); | ||
}); | ||
}); | ||
it('And finally objects', function () { | ||
model.compareThings({}, {}).should.equal(0); | ||
model.compareThings({ a: 42 }, { a: 312}).should.equal(-1); | ||
model.compareThings({ a: '42' }, { a: '312'}).should.equal(1); | ||
model.compareThings({ a: 42, b: 312 }, { b: 312, a: 42 }).should.equal(0); | ||
model.compareThings({ a: 42, b: 312, c: 54 }, { b: 313, a: 42 }).should.equal(-1); | ||
}); | ||
}); // ==== End of 'Comparing things' ==== // | ||
describe('Querying', function () { | ||
@@ -334,0 +463,0 @@ |
150748
20
3293
321
3
6
+ Addedbinary-search-tree@0.1.2
+ Addedbinary-search-tree@0.1.2(transitive)