The JavaScript Database, for Node.js, nw.js, electron and the browser
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
nedb/lib/indexes.js

223 lines
5.5 KiB

var BinarySearchTree = require('binary-search-tree').BinarySearchTree
, model = require('./model')
, _ = require('underscore')
, util = require('util')
;
/**
* Two indexed pointers are equal iif they point to the same place
*/
function checkValueEquality (a, b) {
return a === b;
}
/**
* Create a new index
* @param {String} options.fieldName On which field should the index apply (can use dot notation to index on sub fields)
* @param {Datastore} options.datastore Datastore on which the index is created
* @param {Boolean} options.unique Optional, enforce a unique constraint (default: false)
* @param {Boolean} options.sparse Optional, allow a sparse index (we can have documents for which fieldName is undefined) (default: false)
* TODO: for now the sparse option doesn't work fully
* don't use it. I will implement it in the future
* in the meantime you can use non-unique, non-sparse indexes
* for approx. the same behaviour
*/
function Index (options) {
this.fieldName = options.fieldName;
this.datastore = options.datastore;
this.unique = options.unique || false;
this.sparse = options.sparse || false;
this.treeOptions = { unique: this.unique, compareKeys: model.compareThings, checkValueEquality: checkValueEquality };
this.reset(); // No data in the beginning
}
/**
* Reset an index
* @param {Document or Array of documents} newData Optional, data to initialize the index with
* If an error is thrown during insertion, the index is not modified
*/
Index.prototype.reset = function (newData) {
this.tree = new BinarySearchTree(this.treeOptions);
if (this.sparse) { this.nonindexedDocs = []; }
if (newData) { this.insert(newData); }
};
/**
* Insert a new document in the index
* If an array is passed, we insert all its elements (if one insertion fails the index is not modified)
* O(log(n))
*/
Index.prototype.insert = function (doc) {
var key, self = this;
if (util.isArray(doc)) { this.insertMultipleDocs(doc); return; }
key = model.getDotValue(doc, this.fieldName);
// We don't index documents that don't contain the field if the index is sparse
if (key === undefined && this.sparse) {
this.nonindexedDocs.push(doc);
return;
}
this.tree.insert(key, doc);
};
/**
* Insert an array of documents in the index
* If a constraint is violated, an error should be thrown and the changes rolled back
*/
Index.prototype.insertMultipleDocs = function (docs) {
var i, error, failingI;
for (i = 0; i < docs.length; i += 1) {
try {
this.insert(docs[i]);
} catch (e) {
error = e;
failingI = i;
break;
}
}
if (error) {
for (i = 0; i < failingI; i += 1) {
this.remove(docs[i]);
}
throw error;
}
};
/**
* Remove a document from the index
* If an array is passed, we remove all its elements
* The remove operation is safe with regards to the 'unique' constraint
* O(log(n))
*/
Index.prototype.remove = function (doc) {
var key, self = this
if (util.isArray(doc)) { doc.forEach(function (d) { self.remove(d); }); return; }
key = model.getDotValue(doc, this.fieldName);
if (key === undefined && this.sparse) {
this.nonindexedDocs = _.without(this.nonindexedDocs, doc);
return;
}
this.tree.delete(key, doc);
};
/**
* Update a document in the index
* Naive implementation, still in O(log(n))
*/
Index.prototype.update = function (oldDoc, newDoc) {
if (util.isArray(oldDoc)) { this.updateMultipleDocs(oldDoc); }
this.remove(oldDoc);
this.insert(newDoc);
};
/**
* Revert an update
*/
Index.prototype.revertUpdate = function (oldDoc, newDoc) {
var revert = [];
if (!util.isArray(oldDoc)) {
this.update(newDoc, oldDoc);
} else {
oldDoc.forEach(function (pair) {
revert.push({ oldDoc: pair.newDoc, newDoc: pair.oldDoc });
});
this.update(revert);
}
};
/**
* Update multiple documents in the index
* If a constraint is violated, the changes need to be rolled back
* and an error thrown
* @param {Array of oldDoc, newDoc pairs} pairs
*/
Index.prototype.updateMultipleDocs = function (pairs) {
var i, failingI, error;
for (i = 0; i < pairs.length; i += 1) {
this.remove(pairs[i].oldDoc);
}
for (i = 0; i < pairs.length; i += 1) {
try {
this.insert(pairs[i].newDoc);
} catch (e) {
error = e;
failingI = i;
break;
}
}
// If an error was raised, roll back changes in the inverse order
if (error) {
for (i = 0; i < failingI; i += 1) {
this.remove(pairs[i].newDoc);
}
for (i = 0; i < pairs.length; i += 1) {
this.insert(pairs[i].oldDoc);
}
throw error;
}
};
/**
* Get all documents in index that match the query on fieldName
* For now only works with field equality (i.e. can't use the index for $lt query for example)
* And doesn't return non indexed docs
* @param {Thing} value Value to match the key against
* @return {Array of documents}
*/
Index.prototype.getMatching = function (value) {
return this.tree.search(value);
};
/**
* Get all elements in the index
* @return {Array of documents}
*/
Index.prototype.getAll = function () {
var res = [];
this.tree.executeOnEveryNode(function (node) {
var i;
for (i = 0; i < node.data.length; i += 1) {
res.push(node.data[i]);
}
});
return res;
};
// Interface
module.exports = Index;