diff --git a/lib/datastore.js b/lib/datastore.js index 76d74ebe..c567f39c 100755 --- a/lib/datastore.js +++ b/lib/datastore.js @@ -577,7 +577,6 @@ class Datastore extends EventEmitter { const callback = cb || function () {}; const multi = options.multi !== undefined ? options.multi : false; const upsert = options.upsert !== undefined ? options.upsert : false; - const position = options.position !== undefined ? options.position : false; async.waterfall([ cb => { @@ -604,7 +603,7 @@ class Datastore extends EventEmitter { // updateQuery contains modifiers, use the find query as the base, // strip it from all operators and update it according to updateQuery try { - toBeInserted = model.modify(model.deepCopy(query, true), updateQuery, position); + toBeInserted = model.modify(model.deepCopy(query, true), updateQuery, options); } catch (err) { return callback(err); } @@ -640,10 +639,9 @@ class Datastore extends EventEmitter { if (this.timestampData) { createdAt = candidates[i].createdAt; } - const modifiedDoc = model.modify(candidates[i], updateQuery, position); + const modifiedDoc = model.modify(candidates[i], updateQuery, options); if (this.timestampData) { modifiedDoc.createdAt = createdAt; - // modifiedDoc.updatedAt = new Date(); if (updateQuery.$set.updatedAt === undefined) { modifiedDoc.updatedAt = new Date(); } diff --git a/lib/model.js b/lib/model.js index ab06606c..acf4674a 100755 --- a/lib/model.js +++ b/lib/model.js @@ -647,11 +647,12 @@ lastStepModifierFunctions.$min = function (obj, field, value) { // Given its name, create the complete modifier function function createModifierFunction (modifier) { - return function (obj, field, value, position) { - let fieldParts = typeof field === 'string' ? field.split('.') : field; + return function (obj, field, value, options) { + const fieldParts = typeof field === 'string' ? field.split('.') : field; if (fieldParts.length === 1) { - lastStepModifierFunctions[modifier](obj, field, value, position); + lastStepModifierFunctions[modifier](obj, field, value, options); + } else { if (obj[fieldParts[0]] === undefined) { // Bad looking specific fix, needs to be generalized modifiers that behave like $unset are implemented @@ -660,7 +661,28 @@ function createModifierFunction (modifier) { } obj[fieldParts[0]] = {}; } - modifierFunctions[modifier](obj[fieldParts[0]], fieldParts.slice(1), value, position); + + if (fieldParts[1] === '$') { + const arrayKey = fieldParts[0]; + const arrayObj = obj[arrayKey]; + const query = options.query; + + if (!Array.isArray) { + throw new Error("You cannot apply the positional ($) operator to a non-array field"); + } + + for (let i = arrayObj.length - 1; i >= 0; i -= 1) { + obj[arrayKey] = arrayObj[i]; + + if (match(obj, query)) { + obj[arrayKey] = arrayObj; + fieldParts[1] = i; + break; + } + } + } + + modifierFunctions[modifier](obj[fieldParts[0]], fieldParts.slice(1), value, options); } }; } @@ -671,7 +693,11 @@ Object.keys(lastStepModifierFunctions).forEach(function (modifier) { }); /** Modify a DB object according to an update query */ -function modify (obj, updateQuery, position) { +function modify (obj, updateQuery, options = {}) { + const position = options.position; + const query = options.query; + + let keys = Object.keys(updateQuery), firstChars = keys.map(item => item[0]), dollarFirstChars = firstChars.filter(item => item === '$'), @@ -690,6 +716,7 @@ function modify (obj, updateQuery, position) { // Simply replace the object with the update query contents newDoc = deepCopy(updateQuery); newDoc._id = obj._id; + } else { // Apply modifiers modifiers = [...keys]; @@ -706,8 +733,32 @@ function modify (obj, updateQuery, position) { } const keys = Object.keys(updateQuery[m]); - keys.forEach(function (k) { - modifierFunctions[m](newDoc, k, updateQuery[m][k], position); + keys.forEach(k => { + let queryFields = k.split('.'); + let index = queryFields.indexOf('$'); + + if (index === -1) { + const arrayField = queryFields[index - 1]; + const queryKeys = Object.keys(query); + + let i = queryKeys.length, + found = false; + + while (i--) { + let fields = queryKeys[i].split('.'); + + if (fields.indexOf(arrayField) !== -1) { + found = true; + break; + } + } + + if (!found) { + throw new Error("You must include the relevant array field in query when using the positional ($) operator"); + } + } + + modifierFunctions[m](newDoc, k, updateQuery[m][k], options); }); }); }