|
|
|
@ -115,6 +115,7 @@ Datastore.prototype.resetIndexes = function (newData) { |
|
|
|
|
* @param {String} options.fieldName |
|
|
|
|
* @param {Boolean} options.unique |
|
|
|
|
* @param {Boolean} options.sparse |
|
|
|
|
* @param {Number} options.expireAfterSeconds - Optional, if set this index becomes a TTL index |
|
|
|
|
* @param {Function} cb Optional callback, signature: err |
|
|
|
|
*/ |
|
|
|
|
Datastore.prototype.ensureIndex = function (options, cb) { |
|
|
|
@ -243,50 +244,63 @@ Datastore.prototype.updateIndexes = function (oldDoc, newDoc) { |
|
|
|
|
* One way to make it better would be to enable the use of multiple indexes if the first usable index |
|
|
|
|
* returns too much data. I may do it in the future. |
|
|
|
|
* |
|
|
|
|
* TODO: needs to be moved to the Cursor module |
|
|
|
|
* Returned candidates will be scanned to find and remove all expired documents |
|
|
|
|
* |
|
|
|
|
* @param {Query} query |
|
|
|
|
* @param {Function} callback Signature err, docs |
|
|
|
|
*/ |
|
|
|
|
Datastore.prototype.getCandidates = function (query) { |
|
|
|
|
Datastore.prototype.getCandidates = function (query, callback) { |
|
|
|
|
var indexNames = Object.keys(this.indexes) |
|
|
|
|
, self = this |
|
|
|
|
, usableQueryKeys; |
|
|
|
|
|
|
|
|
|
// For a basic match
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
async.waterfall([ |
|
|
|
|
// STEP 1: get candidates list by checking indexes from most to least frequent usecase
|
|
|
|
|
function (cb) { |
|
|
|
|
// For a basic match
|
|
|
|
|
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 cb(null, self.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]])); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
usableQueryKeys = _.intersection(usableQueryKeys, indexNames); |
|
|
|
|
if (usableQueryKeys.length > 0) { |
|
|
|
|
return this.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// For a $in match
|
|
|
|
|
usableQueryKeys = []; |
|
|
|
|
Object.keys(query).forEach(function (k) { |
|
|
|
|
if (query[k] && query[k].hasOwnProperty('$in')) { |
|
|
|
|
usableQueryKeys.push(k); |
|
|
|
|
// For a $in match
|
|
|
|
|
usableQueryKeys = []; |
|
|
|
|
Object.keys(query).forEach(function (k) { |
|
|
|
|
if (query[k] && query[k].hasOwnProperty('$in')) { |
|
|
|
|
usableQueryKeys.push(k); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
usableQueryKeys = _.intersection(usableQueryKeys, indexNames); |
|
|
|
|
if (usableQueryKeys.length > 0) { |
|
|
|
|
return cb(null, self.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]].$in)); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
usableQueryKeys = _.intersection(usableQueryKeys, indexNames); |
|
|
|
|
if (usableQueryKeys.length > 0) { |
|
|
|
|
return this.indexes[usableQueryKeys[0]].getMatching(query[usableQueryKeys[0]].$in); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// For a comparison match
|
|
|
|
|
usableQueryKeys = []; |
|
|
|
|
Object.keys(query).forEach(function (k) { |
|
|
|
|
if (query[k] && (query[k].hasOwnProperty('$lt') || query[k].hasOwnProperty('$lte') || query[k].hasOwnProperty('$gt') || query[k].hasOwnProperty('$gte'))) { |
|
|
|
|
usableQueryKeys.push(k); |
|
|
|
|
// For a comparison match
|
|
|
|
|
usableQueryKeys = []; |
|
|
|
|
Object.keys(query).forEach(function (k) { |
|
|
|
|
if (query[k] && (query[k].hasOwnProperty('$lt') || query[k].hasOwnProperty('$lte') || query[k].hasOwnProperty('$gt') || query[k].hasOwnProperty('$gte'))) { |
|
|
|
|
usableQueryKeys.push(k); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
usableQueryKeys = _.intersection(usableQueryKeys, indexNames); |
|
|
|
|
if (usableQueryKeys.length > 0) { |
|
|
|
|
return cb(null, self.indexes[usableQueryKeys[0]].getBetweenBounds(query[usableQueryKeys[0]])); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
usableQueryKeys = _.intersection(usableQueryKeys, indexNames); |
|
|
|
|
if (usableQueryKeys.length > 0) { |
|
|
|
|
return this.indexes[usableQueryKeys[0]].getBetweenBounds(query[usableQueryKeys[0]]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// By default, return all the DB data
|
|
|
|
|
return this.getAllData(); |
|
|
|
|
// By default, return all the DB data
|
|
|
|
|
return cb(null, self.getAllData()); |
|
|
|
|
} |
|
|
|
|
// STEP 2: remove all expired documents
|
|
|
|
|
, function (docs) { |
|
|
|
|
return callback(null, docs); |
|
|
|
|
}]); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -548,48 +562,49 @@ Datastore.prototype._update = function (query, updateQuery, options, cb) { |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
, function () { // Perform the update
|
|
|
|
|
var modifiedDoc |
|
|
|
|
, candidates = self.getCandidates(query) |
|
|
|
|
, modifications = [] |
|
|
|
|
; |
|
|
|
|
var modifiedDoc , modifications = []; |
|
|
|
|
|
|
|
|
|
// Preparing update (if an error is thrown here neither the datafile nor
|
|
|
|
|
// the in-memory indexes are affected)
|
|
|
|
|
try { |
|
|
|
|
for (i = 0; i < candidates.length; i += 1) { |
|
|
|
|
if (model.match(candidates[i], query) && (multi || numReplaced === 0)) { |
|
|
|
|
numReplaced += 1; |
|
|
|
|
modifiedDoc = model.modify(candidates[i], updateQuery); |
|
|
|
|
if (self.timestampData) { modifiedDoc.updatedAt = new Date(); } |
|
|
|
|
modifications.push({ oldDoc: candidates[i], newDoc: modifiedDoc }); |
|
|
|
|
self.getCandidates(query, function (err, candidates) { |
|
|
|
|
if (err) { return callback(err); } |
|
|
|
|
|
|
|
|
|
// Preparing update (if an error is thrown here neither the datafile nor
|
|
|
|
|
// the in-memory indexes are affected)
|
|
|
|
|
try { |
|
|
|
|
for (i = 0; i < candidates.length; i += 1) { |
|
|
|
|
if (model.match(candidates[i], query) && (multi || numReplaced === 0)) { |
|
|
|
|
numReplaced += 1; |
|
|
|
|
modifiedDoc = model.modify(candidates[i], updateQuery); |
|
|
|
|
if (self.timestampData) { modifiedDoc.updatedAt = new Date(); } |
|
|
|
|
modifications.push({ oldDoc: candidates[i], newDoc: modifiedDoc }); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} catch (err) { |
|
|
|
|
return callback(err); |
|
|
|
|
} |
|
|
|
|
} catch (err) { |
|
|
|
|
return callback(err); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Change the docs in memory
|
|
|
|
|
try { |
|
|
|
|
self.updateIndexes(modifications); |
|
|
|
|
} catch (err) { |
|
|
|
|
return callback(err); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Update the datafile
|
|
|
|
|
var updatedDocs = _.pluck(modifications, 'newDoc'); |
|
|
|
|
self.persistence.persistNewState(updatedDocs, function (err) { |
|
|
|
|
if (err) { return callback(err); } |
|
|
|
|
if (!options.returnUpdatedDocs) { |
|
|
|
|
return callback(null, numReplaced); |
|
|
|
|
} else { |
|
|
|
|
var updatedDocsDC = []; |
|
|
|
|
updatedDocs.forEach(function (doc) { updatedDocsDC.push(model.deepCopy(doc)); }); |
|
|
|
|
return callback(null, numReplaced, updatedDocsDC); |
|
|
|
|
// Change the docs in memory
|
|
|
|
|
try { |
|
|
|
|
self.updateIndexes(modifications); |
|
|
|
|
} catch (err) { |
|
|
|
|
return callback(err); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Update the datafile
|
|
|
|
|
var updatedDocs = _.pluck(modifications, 'newDoc'); |
|
|
|
|
self.persistence.persistNewState(updatedDocs, function (err) { |
|
|
|
|
if (err) { return callback(err); } |
|
|
|
|
if (!options.returnUpdatedDocs) { |
|
|
|
|
return callback(null, numReplaced); |
|
|
|
|
} else { |
|
|
|
|
var updatedDocsDC = []; |
|
|
|
|
updatedDocs.forEach(function (doc) { updatedDocsDC.push(model.deepCopy(doc)); }); |
|
|
|
|
return callback(null, numReplaced, updatedDocsDC); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
]); |
|
|
|
|
}]); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
Datastore.prototype.update = function () { |
|
|
|
|
this.executor.push({ this: this, fn: this._update, arguments: arguments }); |
|
|
|
|
}; |
|
|
|
@ -607,32 +622,33 @@ Datastore.prototype.update = function () { |
|
|
|
|
*/ |
|
|
|
|
Datastore.prototype._remove = function (query, options, cb) { |
|
|
|
|
var callback |
|
|
|
|
, self = this |
|
|
|
|
, numRemoved = 0 |
|
|
|
|
, multi |
|
|
|
|
, removedDocs = [] |
|
|
|
|
, candidates = this.getCandidates(query) |
|
|
|
|
, self = this, numRemoved = 0, removedDocs = [], multi |
|
|
|
|
; |
|
|
|
|
|
|
|
|
|
if (typeof options === 'function') { cb = options; options = {}; } |
|
|
|
|
callback = cb || function () {}; |
|
|
|
|
multi = options.multi !== undefined ? options.multi : false; |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
candidates.forEach(function (d) { |
|
|
|
|
if (model.match(d, query) && (multi || numRemoved === 0)) { |
|
|
|
|
numRemoved += 1; |
|
|
|
|
removedDocs.push({ $$deleted: true, _id: d._id }); |
|
|
|
|
self.removeFromIndexes(d); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} catch (err) { return callback(err); } |
|
|
|
|
|
|
|
|
|
self.persistence.persistNewState(removedDocs, function (err) { |
|
|
|
|
this.getCandidates(query, function (err, candidates) { |
|
|
|
|
if (err) { return callback(err); } |
|
|
|
|
return callback(null, numRemoved); |
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
candidates.forEach(function (d) { |
|
|
|
|
if (model.match(d, query) && (multi || numRemoved === 0)) { |
|
|
|
|
numRemoved += 1; |
|
|
|
|
removedDocs.push({ $$deleted: true, _id: d._id }); |
|
|
|
|
self.removeFromIndexes(d); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} catch (err) { return callback(err); } |
|
|
|
|
|
|
|
|
|
self.persistence.persistNewState(removedDocs, function (err) { |
|
|
|
|
if (err) { return callback(err); } |
|
|
|
|
return callback(null, numRemoved); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
Datastore.prototype.remove = function () { |
|
|
|
|
this.executor.push({ this: this, fn: this._remove, arguments: arguments }); |
|
|
|
|
}; |
|
|
|
|