Support subsets fields filtering.

Rewrote the returned closure. Still don't support '$' and '$slice' operators.
This commit is contained in:
Slava Kim
2013-09-09 18:16:23 -04:00
parent 2e10d43041
commit 27fd0d5066

View File

@@ -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;
};
};