diff options
Diffstat (limited to 'src/mongo/db/pipeline')
-rwxr-xr-x | src/mongo/db/pipeline/document.h | 6 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/document_source.h | 31 | ||||
-rw-r--r--[-rwxr-xr-x] | src/mongo/db/pipeline/document_source_project.cpp | 124 | ||||
-rw-r--r--[-rwxr-xr-x] | src/mongo/db/pipeline/expression.cpp | 514 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/expression.h | 96 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/field_path.cpp | 12 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/field_path.h | 3 |
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; }; |