diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 91cfffb794..bf89da0771 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -623,7 +623,7 @@ LocalCollection.prototype._modifyAndNotify = function ( // in the output. So it's safe to skip recompute if neither before or // after are true.) if (before || after) - recomputeQids[qid] = true; + recomputeQids[qid] = true; } else if (before && !after) { LocalCollection._removeFromResults(query, doc); } else if (!before && after) { @@ -1069,7 +1069,7 @@ LocalCollection._compileProjection = function (fields) { var _idProjection = _.isUndefined(fields._id) ? true : fields._id; delete fields._id; var including = null; // Unknown - var projectionRules = []; + var projectionRulesTree = {}; // Tree represented as nested objects _.each(fields, function (rule, keyPath) { rule = !!rule; @@ -1078,74 +1078,47 @@ LocalCollection._compileProjection = function (fields) { if (including !== rule) // This error message is copies from MongoDB shell throw MinimongoError("You cannot currently mix including and excluding fields."); - projectionRules.push(keyPath.split('.')); + var treePos = projectionRulesTree; + keyPath = keyPath.split('.'); + + _.each(keyPath.slice(0, -1), function (key) { + if (!_.has(treePos, key)) + treePos[key] = {}; + treePos = treePos[key]; + }); + + treePos[_.last(keyPath)] = including; }); - // XXX do these functions share too much in common? - if (including) - return function (doc) { - var result = {}; + // returns transformed doc according to ruleTree + var transform = function (doc, ruleTree) { + var res = including ? {} : EJSON.clone(doc); + _.each(ruleTree, function (rule, key) { + if (!_.has(doc, key)) + return; + if (_.isObject(rule)) { + if (_.isArray(doc[key])) + res[key] = _.map(doc[key], function (subdoc) { + return transform(subdoc, rule); + }); + else if (_.isObject(doc[key])) + res[key] = transform(doc[key], rule); + } else if (including) + res[key] = doc[key]; + else + delete res[key]; + }); + + return res; + }; - _.each(projectionRules, function (keyPath) { - var target = result; - var docTarget = doc; - for (var i = 0; i < keyPath.length - 1; i++) { - var key = keyPath[i]; - // This block simulates MongoDB behavior for different edge-cases when - // object on certain path wasn't found or array found instead of an - // object, or vice-versa. - if (!_.has(target, key)) { - if (_.isArray(docTarget[key])) { - target[key] = []; - docTarget = undefined; - break; - } else if (_.isObject(docTarget[key])) - target[key] = {}; - else { - docTarget = undefined; - break; - } - } - - target = target[key]; - docTarget = docTarget[key]; - } - - if (keyPath.length > 0 && docTarget && _.has(docTarget, _.last(keyPath))) - target[_.last(keyPath)] = docTarget[_.last(keyPath)]; - }); - - if (_idProjection && _.has(doc, '_id')) - result._id = doc._id; - - return result; - }; - else - return function (doc) { - // XXX Deep copy on this level might be a slowing factor, - // In fact we need it only in case of nested excluded fields. - var result = EJSON.clone(doc); - - _.each(projectionRules, function (keyPath) { - var target = result; - var docTarget = doc; - for (var i = 0; i < keyPath.length - 1; i++) { - var key = keyPath[i]; - if (!_.has(target, key)) { - break; - } - - target = target[key]; - docTarget = docTarget[key]; - } - - if (keyPath.length > 0) - delete target[_.last(keyPath)]; - }); - - if (!_idProjection) - delete result._id; - - return result; - }; + return function (obj) { + var res = transform(obj, projectionRulesTree); + + if (_idProjection && _.has(obj, '_id')) + res._id = obj._id; + if (!_idProjection && _.has(res, '_id')) + delete res._id; + return res; + }; };