summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/pipeline')
-rwxr-xr-xsrc/mongo/db/pipeline/document.h6
-rwxr-xr-xsrc/mongo/db/pipeline/document_source.h31
-rw-r--r--[-rwxr-xr-x]src/mongo/db/pipeline/document_source_project.cpp124
-rw-r--r--[-rwxr-xr-x]src/mongo/db/pipeline/expression.cpp514
-rwxr-xr-xsrc/mongo/db/pipeline/expression.h96
-rwxr-xr-xsrc/mongo/db/pipeline/field_path.cpp12
-rwxr-xr-xsrc/mongo/db/pipeline/field_path.h3
7 files changed, 255 insertions, 531 deletions
diff --git a/src/mongo/db/pipeline/document.h b/src/mongo/db/pipeline/document.h
index 67f813faff6..65670fcd08e 100755
--- a/src/mongo/db/pipeline/document.h
+++ b/src/mongo/db/pipeline/document.h
@@ -218,9 +218,6 @@ namespace mongo {
*/
Document::FieldPair next();
- private:
- friend class Document;
-
/*
Constructor.
@@ -229,6 +226,9 @@ namespace mongo {
*/
FieldIterator(const intrusive_ptr<Document> &pDocument);
+ private:
+ friend class Document;
+
/*
We'll hang on to the original document to ensure we keep the
fieldPtr vector alive.
diff --git a/src/mongo/db/pipeline/document_source.h b/src/mongo/db/pipeline/document_source.h
index 8b358313f23..baddaaa6c6c 100755
--- a/src/mongo/db/pipeline/document_source.h
+++ b/src/mongo/db/pipeline/document_source.h
@@ -800,32 +800,6 @@ namespace mongo {
const intrusive_ptr<DependencyTracker> &pTracker);
/**
- Include a field path in a projection.
-
- @param fieldPath the path of the field to include
- */
- void includePath(const string &fieldPath);
-
- /**
- Exclude a field path from the projection.
-
- @param fieldPath the path of the field to exclude
- */
- void excludePath(const string &fieldPath);
-
- /**
- Add an output Expression in the projection.
-
- BSON document fields are ordered, so the new field will be
- appended to the existing set.
-
- @param fieldName the name of the field as it will appear
- @param pExpression the expression used to compute the field
- */
- void addField(const string &fieldName,
- const intrusive_ptr<Expression> &pExpression);
-
- /**
Create a new projection DocumentSource from BSON.
This is a convenience for directly handling BSON, and relies on the
@@ -847,7 +821,9 @@ namespace mongo {
/** true if just include/exclude, no renames */
bool isSimple() const { return _isSimple; }
- /** called by PipelineD::prepareCursorSource in debug builds if it would remove this Projection */
+ /** called by PipelineD::prepareCursorSource in debug builds if it
+ * would remove this Projection
+ */
void setWouldBeRemoved() { _wouldBeRemoved = true; }
protected:
@@ -858,7 +834,6 @@ namespace mongo {
DocumentSourceProject(const intrusive_ptr<ExpressionContext> &pExpCtx);
// configuration state
- bool excludeId;
intrusive_ptr<ExpressionObject> pEO;
BSONObj _raw;
bool _isSimple;
diff --git a/src/mongo/db/pipeline/document_source_project.cpp b/src/mongo/db/pipeline/document_source_project.cpp
index 241f0e28c52..a46d30ff809 100755..100644
--- a/src/mongo/db/pipeline/document_source_project.cpp
+++ b/src/mongo/db/pipeline/document_source_project.cpp
@@ -32,7 +32,6 @@ namespace mongo {
DocumentSourceProject::DocumentSourceProject(
const intrusive_ptr<ExpressionContext> &pExpCtx):
DocumentSource(pExpCtx),
- excludeId(false),
pEO(ExpressionObject::create()),
_isSimple(true), // set to false in addField
_wouldBeRemoved(false)
@@ -56,30 +55,16 @@ namespace mongo {
intrusive_ptr<Document> pInDocument(pSource->getCurrent());
/* create the result document */
- const size_t sizeHint =
- pEO->getSizeHint(pInDocument) + (excludeId ? 0 : 1);
+ const size_t sizeHint = pEO->getSizeHint();
intrusive_ptr<Document> pResultDocument(Document::create(sizeHint));
- if (!excludeId) {
- intrusive_ptr<const Value> pId(
- pInDocument->getField(Document::idName));
-
- /*
- Previous projections could have removed _id, (or declined to
- generate it) so it might already not exist. Only attempt to add
- if we found it.
- */
- if (pId.get())
- pResultDocument->addField(Document::idName, pId);
- }
-
/*
Use the ExpressionObject to create the base result.
If we're excluding fields at the top level, leave out the _id if
it is found, because we took care of it above.
*/
- pEO->addToDocument(pResultDocument, pInDocument, true);
+ pEO->addToDocument(pResultDocument, pInDocument, /*root=*/pInDocument);
if (debug && _wouldBeRemoved) {
// In this case the $project would have been removed in a
@@ -111,43 +96,10 @@ namespace mongo {
void DocumentSourceProject::sourceToBson(
BSONObjBuilder *pBuilder, bool explain) const {
BSONObjBuilder insides;
- if (excludeId)
- insides.append(Document::idName, false);
pEO->documentToBson(&insides, true);
pBuilder->append(projectName, insides.done());
}
- void DocumentSourceProject::addField(
- const string &fieldName, const intrusive_ptr<Expression> &pExpression) {
- uassert(15960,
- "projection fields must be defined by non-empty expressions",
- pExpression);
-
- _isSimple = false; // this projection is no longer just inclusion/exclusion
- pEO->addField(fieldName, pExpression);
- }
-
- void DocumentSourceProject::includePath(const string &fieldPath) {
- if (Document::idName.compare(fieldPath) == 0) {
- uassert(15961, str::stream() << projectName <<
- ": _id cannot be included once it has been excluded",
- !excludeId);
-
- return;
- }
-
- pEO->includePath(fieldPath);
- }
-
- void DocumentSourceProject::excludePath(const string &fieldPath) {
- if (Document::idName.compare(fieldPath) == 0) {
- excludeId = true;
- return;
- }
-
- pEO->excludePath(fieldPath);
- }
-
intrusive_ptr<DocumentSource> DocumentSourceProject::createFromBson(
BSONElement *pBsonElement,
const intrusive_ptr<ExpressionContext> &pExpCtx) {
@@ -156,64 +108,24 @@ namespace mongo {
" specification must be an object",
pBsonElement->type() == Object);
- /* chain the projection onto the original source */
intrusive_ptr<DocumentSourceProject> pProject(new DocumentSourceProject(pExpCtx));
- /*
- Pull out the $project object. This should just be a list of
- field inclusion or exclusion specifications. Note you can't do
- both, except for the case of _id.
- */
BSONObj projectObj(pBsonElement->Obj());
pProject->_raw = projectObj.getOwned(); // probably not necessary, but better to be safe
- BSONObjIterator fieldIterator(projectObj);
- Expression::ObjectCtx objectCtx(
- Expression::ObjectCtx::DOCUMENT_OK);
- while(fieldIterator.more()) {
- BSONElement outFieldElement(fieldIterator.next());
- string outFieldPath(outFieldElement.fieldName());
- string inFieldName(outFieldPath);
- BSONType specType = outFieldElement.type();
-
- switch(specType) {
- case Bool:
- case NumberDouble:
- case NumberLong:
- case NumberInt:
- /* simple include/exclude specification */
- if (outFieldElement.trueValue())
- pProject->includePath(outFieldPath);
- else
- pProject->excludePath(outFieldPath);
- break;
-
- case String:
- /* include a field, with rename */
- inFieldName = outFieldElement.String();
- pProject->addField(
- outFieldPath,
- ExpressionFieldPath::create(
- Expression::removeFieldPrefix(inFieldName)));
- break;
-
- case Object: {
- intrusive_ptr<Expression> pDocument(
- Expression::parseObject(&outFieldElement, &objectCtx));
-
- /* add The document expression to the projection */
- pProject->addField(outFieldPath, pDocument);
- break;
- }
-
- default:
- uassert(15971, str::stream() <<
- "invalid BSON type (" << specType <<
- ") for " << projectName <<
- " field " << outFieldPath, false);
- }
-
- }
+ Expression::ObjectCtx objectCtx(
+ Expression::ObjectCtx::DOCUMENT_OK
+ | Expression::ObjectCtx::TOP_LEVEL
+ );
+
+ intrusive_ptr<Expression> parsed = Expression::parseObject(pBsonElement, &objectCtx);
+ ExpressionObject* exprObj = dynamic_cast<ExpressionObject*>(parsed.get());
+ massert(16402, "parseObject() returned wrong type of Expression", exprObj);
+ uassert(16403, "$projection requires at least one output field", exprObj->getFieldCount());
+ pProject->pEO = exprObj;
+
+ // TODO simpleness
+ pProject->_isSimple = false;
return pProject;
}
@@ -246,6 +158,11 @@ namespace mongo {
void DocumentSourceProject::manageDependencies(
const intrusive_ptr<DependencyTracker> &pTracker) {
+
+ // the manageDependecies system is currently unused
+ return;
+
+#if 0
/*
Look at all the products (inclusions and computed fields) of this
projection. For each one that is a dependency, remove it from the
@@ -274,6 +191,7 @@ namespace mongo {
add their dependencies to the list of dependencies.
*/
pEO->addDependencies(pTracker, this);
+#endif
}
}
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index e3732009252..476d384d2d4 100755..100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -51,6 +51,10 @@ namespace mongo {
return ((options & DOCUMENT_OK) != 0);
}
+ bool Expression::ObjectCtx::topLevel() const {
+ return ((options & TOP_LEVEL) != 0);
+ }
+
const char Expression::unwindName[] = "$unwind";
string Expression::removeFieldPrefix(const string &prefixedField) {
@@ -95,6 +99,9 @@ namespace mongo {
<< pFieldName << "\"",
fieldCount == 0);
+ uassert(16404, "$expressions are not allowed at the top-level of $project",
+ !pCtx->topLevel());
+
/* we've determined this "object" is an operator expression */
kind = OPERATOR;
@@ -105,6 +112,9 @@ namespace mongo {
pFieldName << "\")",
kind != OPERATOR);
+ uassert(16405, "dotted field names are only allowed at the top level",
+ pCtx->topLevel() || !str::contains(pFieldName, '.'));
+
/* if it's our first time, create the document expression */
if (!pExpression.get()) {
verify(pCtx->documentOk());
@@ -119,7 +129,6 @@ namespace mongo {
BSONType fieldType = fieldElement.type();
string fieldName(pFieldName);
- int inclusion = -1;
switch (fieldType){
case Object: {
/* it's a nested document */
@@ -144,10 +153,15 @@ namespace mongo {
case NumberLong:
case NumberInt: {
/* it's an inclusion specification */
- if (fieldElement.trueValue())
+ if (fieldElement.trueValue()) {
pExpressionObject->includePath(fieldName);
- else if (inclusion == 1)
- pExpressionObject->excludePath(fieldName);
+ }
+ else {
+ uassert(16406,
+ "The top-level _id field is the only field currently supported for exclusion",
+ pCtx->topLevel() && fieldName == "_id");
+ pExpressionObject->excludeId(true);
+ }
break;
}
default:
@@ -1122,18 +1136,13 @@ namespace mongo {
return pExpression;
}
- ExpressionObject::ExpressionObject():
- excludePaths(false),
- path(),
- vFieldName(),
- vpExpression() {
+ ExpressionObject::ExpressionObject(): _excludeId(false) {
}
intrusive_ptr<Expression> ExpressionObject::optimize() {
- const size_t n = vpExpression.size();
- for(size_t i = 0; i < n; ++i) {
- intrusive_ptr<Expression> pE(vpExpression[i]->optimize());
- vpExpression[i] = pE;
+ for (ExpressionMap::iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
+ if (it->second)
+ it->second = it->second->optimize();
}
return intrusive_ptr<Expression>(this);
@@ -1142,126 +1151,132 @@ namespace mongo {
void ExpressionObject::addDependencies(
const intrusive_ptr<DependencyTracker> &pTracker,
const DocumentSource *pSource) const {
- for(ExpressionVector::const_iterator i(vpExpression.begin());
- i != vpExpression.end(); ++i) {
- (*i)->addDependencies(pTracker, pSource);
+ for (ExpressionMap::const_iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
+ if (it->second)
+ it->second->addDependencies(pTracker, pSource);
}
}
void ExpressionObject::addToDocument(
const intrusive_ptr<Document> &pResult,
const intrusive_ptr<Document> &pDocument,
- bool excludeId) const {
- const size_t pathSize = path.size();
- set<string>::const_iterator end(path.end());
+ const intrusive_ptr<Document> &rootDoc
+ ) const
+ {
+ const bool atRoot = (pDocument == rootDoc);
- if (pathSize) {
- auto_ptr<FieldIterator> pIter(pDocument->createFieldIterator());
- if (excludePaths) {
- while(pIter->more()) {
- pair<string, intrusive_ptr<const Value> > field(pIter->next());
+ ExpressionMap::const_iterator end = _expressions.end();
- /*
- If we're excluding _id, and this is it, skip it.
+ // This is used to mark fields we've done so that we can add the ones we haven't
+ set<string> doneFields;
- If the field in the document is not in the exclusion set,
- add it to the result document.
+ FieldIterator fields(pDocument);
+ while(fields.more()) {
+ Document::FieldPair field (fields.next());
- Note that exclusions are only allowed on leaves, so we
- can assume we don't have to descend recursively here.
- */
- if ((excludeId && !field.first.compare(Document::idName)) ||
- (path.find(field.first) != end))
- continue; // we found it, so don't add it
+ ExpressionMap::const_iterator exprIter = _expressions.find(field.first);
+ // This field is not supposed to be in the output (unless it is _id)
+ if (exprIter == end) {
+ if (!_excludeId && atRoot && field.first == "_id") {
+ // _id from the root doc is always included (until exclusion is supported)
+ // not updating doneFields since "_id" isn't in _expressions
pResult->addField(field.first, field.second);
}
+ continue;
}
- else { /* !excludePaths */
- while(pIter->more()) {
- pair<string, intrusive_ptr<const Value> > field(
- pIter->next());
- /*
- Note that this could be an inclusion along a pathway,
- so we look for an ExpressionObject in vpExpression; when
- we find one, we populate the result with the evaluation
- of that on the nested object, yielding relative paths.
- This also allows us to handle intermediate arrays; if we
- encounter one, we repeat this for each array element.
- */
- if (path.find(field.first) != end) {
- /* find the Expression */
- const size_t n = vFieldName.size();
- size_t i;
- Expression *pE = NULL;
- for(i = 0; i < n; ++i) {
- if (field.first.compare(vFieldName[i]) == 0) {
- pE = vpExpression[i].get();
- break;
- }
- }
- /*
- If we didn't find an expression, it's the last path
- element to include.
- */
- if (!pE) {
- pResult->addField(field.first, field.second);
- continue;
- }
+ // make sure we don't add this field again
+ doneFields.insert(exprIter->first);
- ExpressionObject *pChild =
- dynamic_cast<ExpressionObject *>(pE);
- verify(pChild);
-
- /*
- Check on the type of the result object. If it's an
- object, just walk down into that recursively, and
- add it to the result.
- */
- BSONType valueType = field.second->getType();
- if (valueType == Object) {
- intrusive_ptr<Document> pD(
- pChild->evaluateDocument(
- field.second->getDocument()));
- pResult->addField(vFieldName[i],
- Value::createDocument(pD));
- }
- else if (valueType == Array) {
- /*
- If it's an array, we have to do the same thing,
- but to each array element. Then, add the array
- of results to the current document.
- */
- vector<intrusive_ptr<const Value> > result;
- intrusive_ptr<ValueIterator> pVI(
- field.second->getArray());
- while(pVI->more()) {
- intrusive_ptr<Document> pD(
- pChild->evaluateDocument(
- pVI->next()->getDocument()));
- result.push_back(Value::createDocument(pD));
- }
-
- pResult->addField(vFieldName[i],
- Value::createArray(result));
- }
- }
+ Expression* expr = exprIter->second.get();
+
+ if (!expr) {
+ // This means pull the matching field from the input document
+ pResult->addField(field.first, field.second);
+ continue;
+ }
+
+ ExpressionObject* exprObj = dynamic_cast<ExpressionObject*>(expr);
+ BSONType valueType = field.second->getType();
+ if ((valueType != Object && valueType != Array) || !exprObj ) {
+ // This expression replace the whole field
+
+ intrusive_ptr<const Value> pValue(expr->evaluate(rootDoc));
+
+ // don't add field if nothing was found in the subobject
+ if (exprObj && pValue->getDocument()->getFieldCount() == 0)
+ continue;
+
+ /*
+ Don't add non-existent values (note: different from NULL);
+ this is consistent with existing selection syntax which doesn't
+ force the appearnance of non-existent fields.
+ */
+ // TODO make missing distinct from Undefined
+ if (pValue->getType() != Undefined)
+ pResult->addField(field.first, pValue);
+
+
+ continue;
+ }
+
+ /*
+ Check on the type of the input value. If it's an
+ object, just walk down into that recursively, and
+ add it to the result.
+ */
+ if (valueType == Object) {
+ intrusive_ptr<Document> doc = Document::create(exprObj->getSizeHint());
+ exprObj->addToDocument(doc,
+ field.second->getDocument(),
+ rootDoc);
+ pResult->addField(field.first, Value::createDocument(doc));
+ }
+ else if (valueType == Array) {
+ /*
+ If it's an array, we have to do the same thing,
+ but to each array element. Then, add the array
+ of results to the current document.
+ */
+ vector<intrusive_ptr<const Value> > result;
+ intrusive_ptr<ValueIterator> pVI(field.second->getArray());
+ while(pVI->more()) {
+ intrusive_ptr<const Value> next = pVI->next();
+
+ // can't look for a subfield in a non-object value.
+ if (next->getType() != Object)
+ continue;
+
+ intrusive_ptr<Document> doc = Document::create(exprObj->getSizeHint());
+ exprObj->addToDocument(doc,
+ next->getDocument(),
+ rootDoc);
+ result.push_back(Value::createDocument(doc));
}
+
+ pResult->addField(field.first,
+ Value::createArray(result));
}
}
+ if (doneFields.size() == _expressions.size())
+ return;
+
/* add any remaining fields we haven't already taken care of */
- const size_t n = vFieldName.size();
- for(size_t i = 0; i < n; ++i) {
- string fieldName(vFieldName[i]);
+ for (vector<string>::const_iterator i(_order.begin()); i!=_order.end(); ++i) {
+ ExpressionMap::const_iterator it = _expressions.find(*i);
+ string fieldName(it->first);
/* if we've already dealt with this field, above, do nothing */
- if (path.find(fieldName) != end)
+ if (doneFields.count(fieldName))
continue;
- intrusive_ptr<const Value> pValue(
- vpExpression[i]->evaluate(pDocument));
+ // this is a missing inclusion field
+ if (!it->second)
+ continue;
+
+ intrusive_ptr<const Value> pValue(it->second->evaluate(rootDoc));
/*
Don't add non-existent values (note: different from NULL);
@@ -1271,36 +1286,27 @@ namespace mongo {
if (pValue->getType() == Undefined)
continue;
+ // don't add field if nothing was found in the subobject
+ if (dynamic_cast<ExpressionObject*>(it->second.get())
+ && pValue->getDocument()->getFieldCount() == 0)
+ continue;
+
+
pResult->addField(fieldName, pValue);
}
}
- size_t ExpressionObject::getSizeHint(
- const intrusive_ptr<Document> &pDocument) const {
- size_t sizeHint = pDocument->getFieldCount();
- const size_t pathSize = path.size();
- if (!excludePaths)
- sizeHint += pathSize;
- else {
- size_t excludeCount = pathSize;
- if (sizeHint > excludeCount)
- sizeHint -= excludeCount;
- else
- sizeHint = 0;
- }
-
- /* account for the additional computed fields */
- sizeHint += vFieldName.size();
-
- return sizeHint;
+ size_t ExpressionObject::getSizeHint() const {
+ // Note: this can overestimate, but that is better than underestimating
+ return _expressions.size() + (_excludeId ? 0 : 1);
}
intrusive_ptr<Document> ExpressionObject::evaluateDocument(
const intrusive_ptr<Document> &pDocument) const {
/* create and populate the result */
intrusive_ptr<Document> pResult(
- Document::create(getSizeHint(pDocument)));
- addToDocument(pResult, pDocument);
+ Document::create(getSizeHint()));
+ addToDocument(pResult, Document::create(), pDocument);
return pResult;
}
@@ -1309,226 +1315,96 @@ namespace mongo {
return Value::createDocument(evaluateDocument(pDocument));
}
- void ExpressionObject::addField(const string &fieldName,
+ void ExpressionObject::addField(const FieldPath &fieldPath,
const intrusive_ptr<Expression> &pExpression) {
- /* must have an expression */
- verify(pExpression.get());
-
- /* parse the field path */
- FieldPath fieldPath(fieldName);
- uassert(16008, str::stream() <<
- "an expression object's field names cannot be field paths (at \"" <<
- fieldName << "\")", fieldPath.getPathLength() == 1);
-
- /* make sure it isn't a name we've included or excluded */
- set<string>::iterator ex(path.find(fieldName));
- uassert(16009, str::stream() <<
- "can't add a field to an object expression that has already been excluded (at \"" <<
- fieldName << "\")", ex == path.end());
-
- /* make sure it isn't a name we've already got */
- const size_t n = vFieldName.size();
- for(size_t i = 0; i < n; ++i) {
- uassert(16010, str::stream() <<
- "can't add the same field to an object expression more than once (at \"" <<
- fieldName << "\")",
- fieldName.compare(vFieldName[i]) != 0);
+ verify(fieldPath.getPathLength() > 0); // sanity check
+
+ const string fieldPart = fieldPath.getFieldName(0);
+ const bool haveExpr = _expressions.count(fieldPart);
+
+ intrusive_ptr<Expression>& expr = _expressions[fieldPart]; // inserts if !haveExpr
+ intrusive_ptr<ExpressionObject> subObj = dynamic_cast<ExpressionObject*>(expr.get());
+
+ if (!haveExpr) {
+ _order.push_back(fieldPart);
+ }
+ else { // we already have an expression or inclusion for this field
+ if (fieldPath.getPathLength() == 1) {
+ // This expression is for right here
+
+ ExpressionObject* newSubObj = dynamic_cast<ExpressionObject*>(pExpression.get());
+ uassert(16400, str::stream()
+ << "can't add an expression for field " << fieldPart
+ << " because there is already an expression for that field"
+ << " or one of its sub-fields.",
+ subObj && newSubObj); // we can merge them
+
+ // Copy everything from the newSubObj to the existing subObj
+ // This is for cases like { $project:{ 'b.c':1, b:{ a:1 } } }
+ for (vector<string>::const_iterator it (newSubObj->_order.begin());
+ it != newSubObj->_order.end();
+ ++it) {
+ // asserts if any fields are dupes
+ subObj->addField(*it, newSubObj->_expressions[*it]);
+ }
+ return;
+ }
+ else {
+ // This expression is for a subfield
+ uassert(16401, str::stream()
+ << "can't add an expression for a subfield of " << fieldPart
+ << " because there is already an expression that applies to"
+ << " the whole field",
+ subObj);
+ }
}
- vFieldName.push_back(fieldName);
- vpExpression.push_back(pExpression);
- }
-
- void ExpressionObject::includePath(
- const FieldPath *pPath, size_t pathi, size_t pathn, bool excludeLast) {
-
- /* get the current path field name */
- string fieldName(pPath->getFieldName(pathi));
- uassert(16011,
- "an object expression can't include an empty field-name",
- fieldName.length());
-
- const size_t pathCount = path.size();
-
- /* if this is the leaf-most object, stop */
- if (pathi == pathn - 1) {
- /*
- Make sure the exclusion configuration of this node matches
- the requested result. Or, that this is the first (determining)
- specification.
- */
- uassert(16012, str::stream() <<
- "incompatible exclusion for \"" <<
- pPath->getPath(false) <<
- "\" because of a prior inclusion that includes a common sub-path",
- ((excludePaths == excludeLast) || !pathCount));
-
- excludePaths = excludeLast; // if (!pathCount), set this
- path.insert(fieldName);
+ if (fieldPath.getPathLength() == 1) {
+ expr = pExpression;
return;
}
- /* this level had better be about inclusions */
- uassert(16013, str::stream() <<
- "incompatible inclusion for \"" << pPath->getPath(false) <<
- "\" because of a prior exclusion that includes a common sub-path",
- !excludePaths);
-
- /* see if we already know about this field */
- const size_t n = vFieldName.size();
- size_t i;
- for(i = 0; i < n; ++i) {
- if (fieldName.compare(vFieldName[i]) == 0)
- break;
- }
+ if (!haveExpr)
+ expr = subObj = ExpressionObject::create();
- /* find the right object, and continue */
- ExpressionObject *pChild;
- if (i < n) {
- /* the intermediate child already exists */
- pChild = dynamic_cast<ExpressionObject *>(vpExpression[i].get());
- verify(pChild);
- }
- else {
- /*
- If we get here, the intervening child isn't already there,
- so create it.
- */
- intrusive_ptr<ExpressionObject> pSharedChild(
- ExpressionObject::create());
- path.insert(fieldName);
- vFieldName.push_back(fieldName);
- vpExpression.push_back(pSharedChild);
- pChild = pSharedChild.get();
- }
-
- // LATER CW TODO turn this into a loop
- pChild->includePath(pPath, pathi + 1, pathn, excludeLast);
+ subObj->addField(fieldPath.tail(), pExpression);
}
void ExpressionObject::includePath(const string &theFieldPath) {
- /* parse the field path */
- FieldPath fieldPath(theFieldPath);
- includePath(&fieldPath, 0, fieldPath.getPathLength(), false);
- }
-
- void ExpressionObject::excludePath(const string &theFieldPath) {
- /* parse the field path */
- FieldPath fieldPath(theFieldPath);
- includePath(&fieldPath, 0, fieldPath.getPathLength(), true);
- }
-
- intrusive_ptr<Expression> ExpressionObject::getField(
- const string &fieldName) const {
- const size_t n = vFieldName.size();
- for(size_t i = 0; i < n; ++i) {
- if (fieldName.compare(vFieldName[i]) == 0)
- return vpExpression[i];
- }
-
- /* if we got here, we didn't find it */
- return intrusive_ptr<Expression>();
- }
-
- void ExpressionObject::emitPaths(PathSink *pPathSink) const {
- vector<string> vPath;
- emitPaths(pPathSink, &vPath);
+ addField(theFieldPath, NULL);
}
- void ExpressionObject::emitPaths(
- PathSink *pPathSink, vector<string> *pvPath) const {
- if (!path.size())
- return;
-
- /* we use these for loops */
- const size_t nField = vFieldName.size();
- const size_t nPath = pvPath->size();
-
- /*
- We can iterate over the inclusion/exclusion paths in their
- (random) set order because they don't affect the order that
- fields are listed in the result. That comes from the underlying
- Document they are fetched from.
- */
- for(set<string>::const_iterator end(path.end()),
- iter(path.begin()); iter != end; ++iter) {
-
- /* find the matching field description */
- size_t iField = 0;
- for(; iField < nField; ++iField) {
- if (iter->compare(vFieldName[iField]) == 0)
- break;
- }
-
- if (iField == nField) {
- /*
- If we didn't find a matching field description, this is the
- leaf, so add the path.
- */
- stringstream ss;
-
- for(size_t iPath = 0; iPath < nPath; ++iPath)
- ss << (*pvPath)[iPath] << ".";
- ss << *iter;
-
- pPathSink->path(ss.str(), !excludePaths);
+ void ExpressionObject::documentToBson(BSONObjBuilder *pBuilder, bool requireExpression) const {
+ for (vector<string>::const_iterator it(_order.begin()); it!=_order.end(); ++it) {
+ string fieldName = *it;
+ verify(_expressions.find(fieldName) != _expressions.end());
+ intrusive_ptr<Expression> expr = _expressions.find(fieldName)->second;
+
+ if (!expr) {
+ // this is inclusion, not an expression
+ pBuilder->appendBool(fieldName, true);
}
else {
- /*
- If we found a matching field description, then we need to
- descend into the next level.
- */
- Expression *pE = vpExpression[iField].get();
- ExpressionObject *pEO = dynamic_cast<ExpressionObject *>(pE);
- verify(pEO);
-
- /*
- Add the current field name to the path being built up,
- then go down into the next level.
- */
- PathPusher pathPusher(pvPath, vFieldName[iField]);
- pEO->emitPaths(pPathSink, pvPath);
+ expr->addToBsonObj(pBuilder, fieldName, requireExpression);
}
}
}
- void ExpressionObject::documentToBson(
- BSONObjBuilder *pBuilder, bool requireExpression) const {
-
- /* emit any inclusion/exclusion paths */
- BuilderPathSink builderPathSink(pBuilder);
- emitPaths(&builderPathSink);
-
- /* then add any expressions */
- const size_t nField = vFieldName.size();
- const set<string>::const_iterator pathEnd(path.end());
- for(size_t iField = 0; iField < nField; ++iField) {
- string fieldName(vFieldName[iField]);
-
- /* if we already took care of this, don't repeat it */
- if (path.find(fieldName) != pathEnd)
- continue;
-
- vpExpression[iField]->addToBsonObj(
- pBuilder, fieldName, requireExpression);
- }
- }
-
void ExpressionObject::addToBsonObj(
BSONObjBuilder *pBuilder, string fieldName,
bool requireExpression) const {
- BSONObjBuilder objBuilder;
+ BSONObjBuilder objBuilder (pBuilder->subobjStart(fieldName));
documentToBson(&objBuilder, requireExpression);
- pBuilder->append(fieldName, objBuilder.done());
+ objBuilder.done();
}
void ExpressionObject::addToBsonArray(
BSONArrayBuilder *pBuilder) const {
- BSONObjBuilder objBuilder;
+ BSONObjBuilder objBuilder (pBuilder->subobjStart());
documentToBson(&objBuilder, false);
- pBuilder->append(objBuilder.done());
+ objBuilder.done();
}
void ExpressionObject::BuilderPathSink::path(
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h
index aa8e6bc1b90..c6b0309abde 100755
--- a/src/mongo/db/pipeline/expression.h
+++ b/src/mongo/db/pipeline/expression.h
@@ -141,12 +141,14 @@ namespace mongo {
ObjectCtx(int options);
static const int UNWIND_OK = 0x0001;
static const int DOCUMENT_OK = 0x0002;
+ static const int TOP_LEVEL = 0x0004;
bool unwindOk() const;
bool unwindUsed() const;
void unwind(string fieldName);
bool documentOk() const;
+ bool topLevel() const;
private:
int options;
@@ -926,25 +928,16 @@ namespace mongo {
instead of creating a new one.
@param pResult the Document to add the evaluated expressions to
- @param pDocument the input Document
- @param excludeId for exclusions, exclude the _id, if present
+ @param pDocument the input Document for this level
+ @param rootDoc the root of the whole input document
*/
- void addToDocument(const intrusive_ptr<Document> &pResult,
- const intrusive_ptr<Document> &pDocument,
- bool excludeId = false) const;
+ void addToDocument(const intrusive_ptr<Document>& pResult,
+ const intrusive_ptr<Document>& pDocument,
+ const intrusive_ptr<Document>& rootDoc
+ ) const;
- /*
- Estimate the number of fields that will result from evaluating
- this over pDocument. Does not include _id. This is an estimate
- (really an upper bound) because we can't account for undefined
- fields without actually doing the evaluation. But this is still
- useful as an argument to Document::create(), if you plan to use
- addToDocument().
-
- @param pDocument the input document
- @returns estimated number of fields that will result
- */
- size_t getSizeHint(const intrusive_ptr<Document> &pDocument) const;
+ // estimated number of fields that will be output
+ size_t getSizeHint() const;
/*
Create an empty expression. Until fields are added, this
@@ -960,7 +953,7 @@ namespace mongo {
@param pExpression the expression to evaluate obtain this field's
Value in the result Document
*/
- void addField(const string &fieldPath,
+ void addField(const FieldPath &fieldPath,
const intrusive_ptr<Expression> &pExpression);
/*
@@ -974,26 +967,6 @@ namespace mongo {
void includePath(const string &fieldPath);
/*
- Add a field path to the set of those to be excluded.
-
- Note that excluding a nested field implies including everything on
- the path leading down to it (because you're stating you want to see
- all the other fields that aren't being excluded).
-
- @param fieldName the name of the field to be excluded
- */
- void excludePath(const string &fieldPath);
-
- /*
- Return the expression for a field.
-
- @param fieldName the field name for the expression to return
- @returns the expression used to compute the field, if it is present,
- otherwise NULL.
- */
- intrusive_ptr<Expression> getField(const string &fieldName) const;
-
- /*
Get a count of the added fields.
@returns how many fields have been added
@@ -1001,13 +974,6 @@ namespace mongo {
size_t getFieldCount() const;
/*
- Get a count of the exclusions.
-
- @returns how many fields have been excluded.
- */
- size_t getExclusionCount() const;
-
- /*
Specialized BSON conversion that allows for writing out a
$project specification. This creates a standalone object, which must
be added to a containing object with a name
@@ -1036,42 +1002,20 @@ namespace mongo {
virtual void path(const string &path, bool include) = 0;
};
- /**
- Emit the field paths that have been included or excluded. "Included"
- includes paths that are referenced in expressions for computed
- fields.
-
- @param pSink where to write the paths to
- @param pvPath pointer to a vector of strings describing the path on
- descent; the top-level call should pass an empty vector
- */
- void emitPaths(PathSink *pPathSink) const;
+ void excludeId(bool b) { _excludeId = b; }
private:
ExpressionObject();
- void includePath(
- const FieldPath *pPath, size_t pathi, size_t pathn,
- bool excludeLast);
-
- bool excludePaths;
- set<string> path;
-
- /* these two vectors are maintained in parallel */
- vector<string> vFieldName;
- vector<intrusive_ptr<Expression> > vpExpression;
+ // mapping from fieldname to Expression to generate the value
+ // NULL expression means include from source document
+ typedef map<string, intrusive_ptr<Expression> > ExpressionMap;
+ ExpressionMap _expressions;
+ // this is used to maintain order for generated fields not in the source document
+ vector<string> _order;
- /*
- Utility function used by documentToBson(). Emits inclusion
- and exclusion paths by recursively walking down the nested
- ExpressionObject trees these have created.
-
- @param pSink where to write the paths to
- @param pvPath pointer to a vector of strings describing the path on
- descent; the top-level call should pass an empty vector
- */
- void emitPaths(PathSink *pPathSink, vector<string> *pvPath) const;
+ bool _excludeId;
/*
Utility object for collecting emitPaths() results in a BSON
@@ -1313,7 +1257,7 @@ namespace mongo {
}
inline size_t ExpressionObject::getFieldCount() const {
- return vFieldName.size();
+ return _expressions.size();
}
inline ExpressionObject::BuilderPathSink::BuilderPathSink(
diff --git a/src/mongo/db/pipeline/field_path.cpp b/src/mongo/db/pipeline/field_path.cpp
index 459c4b69e68..87c1acdd41f 100755
--- a/src/mongo/db/pipeline/field_path.cpp
+++ b/src/mongo/db/pipeline/field_path.cpp
@@ -73,10 +73,9 @@ namespace mongo {
outStream << prefix;
outStream << vFieldName[0];
-
const size_t n = vFieldName.size();
for(size_t i = 1; i < n; ++i)
- outStream << "." << vFieldName[i];
+ outStream << '.' << vFieldName[i];
}
FieldPath &FieldPath::operator=(const FieldPath &rRHS) {
@@ -87,4 +86,13 @@ namespace mongo {
return *this;
}
+ FieldPath FieldPath::tail() const {
+ verify(!vFieldName.empty());
+
+ FieldPath out;
+ vector<string> allButFirst(vFieldName.begin()+1, vFieldName.end());
+ out.vFieldName.swap(allButFirst);
+ return out;
+ }
+
}
diff --git a/src/mongo/db/pipeline/field_path.h b/src/mongo/db/pipeline/field_path.h
index f6f59ad3295..efbda98a55d 100755
--- a/src/mongo/db/pipeline/field_path.h
+++ b/src/mongo/db/pipeline/field_path.h
@@ -83,6 +83,9 @@ namespace mongo {
static const char prefix[];
+ /** a FieldPath like this but missing the first element (useful for recursion) */
+ FieldPath tail() const;
+
private:
vector<string> vFieldName;
};