diff --git a/lib/datastore.js b/lib/datastore.js index 4b978ad3..fee42f5c 100755 --- a/lib/datastore.js +++ b/lib/datastore.js @@ -262,7 +262,6 @@ Datastore.prototype.getCandidates = function (query, dontExpireStaleDocs, callba dontExpireStaleDocs = false; } - async.waterfall([ // STEP 1: get candidates list by checking indexes from most to least frequent usecase function (cb) { @@ -560,15 +559,14 @@ Datastore.prototype._update = function (query, updateQuery, options, cb) { var callback , self = this , numReplaced = 0 - , multi, upsert + , multi, upsert, posnum , i ; - if (typeof options === 'function') { cb = options; options = {}; } callback = cb || function () {}; multi = options.multi !== undefined ? options.multi : false; upsert = options.upsert !== undefined ? options.upsert : false; - + posnum = options.position !== undefined ? options.position : false; async.waterfall([ function (cb) { // If upsert option is set, check whether we need to insert the doc if (!upsert) { return cb(); } @@ -586,11 +584,13 @@ Datastore.prototype._update = function (query, updateQuery, options, cb) { model.checkObject(updateQuery); // updateQuery is a simple object with no modifier, use it as the document to insert toBeInserted = updateQuery; + } catch (e) { // 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); + toBeInserted = model.modify(model.deepCopy(query, true), updateQuery, posnum); + } catch (err) { return callback(err); } @@ -616,7 +616,7 @@ Datastore.prototype._update = function (query, updateQuery, options, cb) { if (model.match(candidates[i], query) && (multi || numReplaced === 0)) { numReplaced += 1; if (self.timestampData) { createdAt = candidates[i].createdAt; } - modifiedDoc = model.modify(candidates[i], updateQuery); + modifiedDoc = model.modify(candidates[i], updateQuery, posnum); if (self.timestampData) { modifiedDoc.createdAt = createdAt; modifiedDoc.updatedAt = new Date(); @@ -653,7 +653,7 @@ Datastore.prototype._update = function (query, updateQuery, options, cb) { }; Datastore.prototype.update = function () { - this.executor.push({ this: this, fn: this._update, arguments: arguments }); + this.executor.push({ this: this, fn: this._update, arguments: arguments}); }; diff --git a/lib/model.js b/lib/model.js index 0aa9e928..4a070709 100755 --- a/lib/model.js +++ b/lib/model.js @@ -15,7 +15,7 @@ var util = require('util') ; -/** +/* * Check a key, throw an error if the key is non valid * @param {String} k key * @param {Model} v value, needed to treat the Date edge case @@ -268,16 +268,18 @@ lastStepModifierFunctions.$unset = function (obj, field, value) { * Optional modifier $slice to slice the resulting array, see https://docs.mongodb.org/manual/reference/operator/update/slice/ * Différeence with MongoDB: if $slice is specified and not $each, we act as if value is an empty array */ -lastStepModifierFunctions.$push = function (obj, field, value) { +lastStepModifierFunctions.$push = function (obj, field, value, posnum) { // Create the array if it doesn't exist if (!obj.hasOwnProperty(field)) { obj[field] = []; } if (!util.isArray(obj[field])) { throw new Error("Can't $push an element on non-array values"); } - if (value !== null && typeof value === 'object' && value.$slice && value.$each === undefined) { + if (value !== null && typeof value === 'object' && value.$slice && value.$each && value.position === undefined) { value.$each = []; } + + if (value !== null && typeof value === 'object' && value.$each) { if (Object.keys(value).length >= 3 || (Object.keys(value).length === 2 && value.$slice === undefined)) { throw new Error("Can only use $slice in cunjunction with $each when $push to array"); } if (!util.isArray(value.$each)) { throw new Error("$each requires an array value"); } @@ -302,11 +304,14 @@ lastStepModifierFunctions.$push = function (obj, field, value) { obj[field] = obj[field].slice(start, end); } } else { - obj[field].push(value); - } -}; - + if (posnum === undefined){ + obj[field].push(value); + } + if (posnum !== undefined){ + obj[field].splice(posnum, 0, value); + }} +}; /** * Add an element to an array field only if it is not already in it * No modification if the element is already in the array @@ -339,16 +344,18 @@ lastStepModifierFunctions.$addToSet = function (obj, field, value) { /** * Remove the first or last element of an array */ -lastStepModifierFunctions.$pop = function (obj, field, value) { +lastStepModifierFunctions.$pop = function (obj, field, value, posnum) { if (!util.isArray(obj[field])) { throw new Error("Can't $pop an element from non-array values"); } if (typeof value !== 'number') { throw new Error(value + " isn't an integer, can't use it with $pop"); } if (value === 0) { return; } - + if (posnum !== undefined) { + obj[field] = obj[field].splice(posnum,1); + } else { if (value > 0) { obj[field] = obj[field].slice(0, obj[field].length - 1); } else { obj[field] = obj[field].slice(1); - } + }} }; @@ -401,7 +408,7 @@ lastStepModifierFunctions.$max = function (obj, field, value) { * Updates the value of the field, only if specified field is smaller than the current value of the field */ lastStepModifierFunctions.$min = function (obj, field, value) { - if (typeof obj[field] === 'undefined') {  + if (typeof obj[field] === 'undefined') { obj[field] = value; } else if (value < obj[field]) { obj[field] = value; @@ -410,22 +417,23 @@ lastStepModifierFunctions.$min = function (obj, field, value) { // Given its name, create the complete modifier function function createModifierFunction (modifier) { - return function (obj, field, value) { + return function (obj, field, value, posnum) { var fieldParts = typeof field === 'string' ? field.split('.') : field; if (fieldParts.length === 1) { - lastStepModifierFunctions[modifier](obj, field, value); + lastStepModifierFunctions[modifier](obj, field, value, posnum); } else { if (obj[fieldParts[0]] === undefined) { if (modifier === '$unset') { return; } // Bad looking specific fix, needs to be generalized modifiers that behave like $unset are implemented obj[fieldParts[0]] = {}; } - modifierFunctions[modifier](obj[fieldParts[0]], fieldParts.slice(1), value); + modifierFunctions[modifier](obj[fieldParts[0]], fieldParts.slice(1), value, posnum); } }; } // Actually create all modifier functions + Object.keys(lastStepModifierFunctions).forEach(function (modifier) { modifierFunctions[modifier] = createModifierFunction(modifier); }); @@ -434,7 +442,8 @@ Object.keys(lastStepModifierFunctions).forEach(function (modifier) { /** * Modify a DB object according to an update query */ -function modify (obj, updateQuery) { +function modify (obj, updateQuery, posnum) { + var keys = Object.keys(updateQuery) , firstChars = _.map(keys, function (item) { return item[0]; }) , dollarFirstChars = _.filter(firstChars, function (c) { return c === '$'; }) @@ -458,7 +467,7 @@ function modify (obj, updateQuery) { modifiers.forEach(function (m) { var keys; - if (!modifierFunctions[m]) { throw new Error("Unknown modifier " + m); } + if (!modifierFunctions[m]) { throw new Error("Unknown modifier hehe " + m); } // Can't rely on Object.keys throwing on non objects since ES6 // Not 100% satisfying as non objects can be interpreted as objects but no false negatives so we can live with it @@ -468,7 +477,7 @@ function modify (obj, updateQuery) { keys = Object.keys(updateQuery[m]); keys.forEach(function (k) { - modifierFunctions[m](newDoc, k, updateQuery[m][k]); + modifierFunctions[m](newDoc, k, updateQuery[m][k], posnum); }); }); }