diff options
Diffstat (limited to 'src/mongo/db/pipeline/document_source_unwind.cpp')
| -rw-r--r-- | src/mongo/db/pipeline/document_source_unwind.cpp | 153 |
1 files changed, 57 insertions, 96 deletions
diff --git a/src/mongo/db/pipeline/document_source_unwind.cpp b/src/mongo/db/pipeline/document_source_unwind.cpp index 249d3efb593..f7c28fa8ec4 100644 --- a/src/mongo/db/pipeline/document_source_unwind.cpp +++ b/src/mongo/db/pipeline/document_source_unwind.cpp @@ -12,36 +12,46 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. */ -#include "pch.h" -#include "db/pipeline/document_source.h" +#include "mongo/pch.h" -#include "db/jsobj.h" -#include "db/pipeline/document.h" -#include "db/pipeline/expression.h" -#include "db/pipeline/value.h" +#include "mongo/db/jsobj.h" +#include "mongo/db/pipeline/document.h" +#include "mongo/db/pipeline/document_source.h" +#include "mongo/db/pipeline/expression.h" +#include "mongo/db/pipeline/value.h" namespace mongo { - /** Helper class to unwind arrays within a series of documents. */ + /** Helper class to unwind array from a single document. */ class DocumentSourceUnwind::Unwinder { public: /** @param unwindPath is the field path to the array to unwind. */ Unwinder(const FieldPath& unwindPath); /** Reset the unwinder to unwind a new document. */ void resetDocument(const Document& document); - /** @return true if done unwinding the last document passed to resetDocument(). */ - bool eof() const; - /** Try to advance to the next document unwound from the document passed to resetDocument(). */ - void advance(); + /** - * @return the current document unwound from the document provided to resetDocument(), using - * the current value in the array located at the provided unwindPath. But @return - * Document() if resetDocument() has not been called or the results to unwind - * have been exhausted. + * @return the next document unwound from the document provided to resetDocument(), using + * the current value in the array located at the provided unwindPath. + * + * Returns boost::none if the array is exhausted. */ - Document getCurrent(); + boost::optional<Document> getNext(); + private: // Path to the array to unwind. const FieldPath _unwindPath; @@ -74,34 +84,17 @@ namespace mongo { } // The target field must be an array to unwind. - uassert(15978, str::stream() << (string)DocumentSourceUnwind::unwindName - << ": value at end of field path must be an array", + uassert(15978, str::stream() << "Value at end of $unwind field path '" + << _unwindPath.getPath(true) << "' must be an Array, but is a " + << typeName(pathValue.getType()), pathValue.getType() == Array); - if (pathValue.getArray().empty()) { - // there are no values to unwind. - return; - } - _inputArray = pathValue; - verify(!eof()); // Checked above that the array is nonempty. } - bool DocumentSourceUnwind::Unwinder::eof() const { - return (_inputArray.getType() != Array) - || (_index == _inputArray.getArrayLength()); - } - - void DocumentSourceUnwind::Unwinder::advance() { - if (!eof()) { // don't advance past end() - _index++; - } - } - - Document DocumentSourceUnwind::Unwinder::getCurrent() { - if (eof()) { - return Document(); - } + boost::optional<Document> DocumentSourceUnwind::Unwinder::getNext() { + if (_inputArray.missing() || _index == _inputArray.getArrayLength()) + return boost::none; // If needed, this will automatically clone all the documents along the // field path so that the end values are not shared across documents @@ -111,80 +104,47 @@ namespace mongo { // that change with any other clones (or the original). _output.setNestedField(_unwindPathFieldIndexes, _inputArray[_index]); - + _index++; return _output.peek(); } const char DocumentSourceUnwind::unwindName[] = "$unwind"; - DocumentSourceUnwind::~DocumentSourceUnwind() { - } - DocumentSourceUnwind::DocumentSourceUnwind( const intrusive_ptr<ExpressionContext> &pExpCtx): DocumentSource(pExpCtx) { } - void DocumentSourceUnwind::lazyInit() { - if (!_unwinder) { - verify(_unwindPath); - _unwinder.reset(new Unwinder(*_unwindPath)); - if (!pSource->eof()) { - // Set up the first source document for unwinding. - _unwinder->resetDocument(pSource->getCurrent()); - } - mayAdvanceSource(); - } - } - - void DocumentSourceUnwind::mayAdvanceSource() { - while(_unwinder->eof()) { - // The _unwinder is exhausted. - - if (pSource->eof()) { - // The source is exhausted. - return; - } - if (!pSource->advance()) { - // The source is exhausted. - return; - } - // Reset the _unwinder with pSource's next document. - _unwinder->resetDocument(pSource->getCurrent()); - } - } - const char *DocumentSourceUnwind::getSourceName() const { return unwindName; } - bool DocumentSourceUnwind::eof() { - lazyInit(); - return _unwinder->eof(); - } + boost::optional<Document> DocumentSourceUnwind::getNext() { + pExpCtx->checkForInterrupt(); - bool DocumentSourceUnwind::advance() { - DocumentSource::advance(); // check for interrupts - lazyInit(); - _unwinder->advance(); - mayAdvanceSource(); - return !_unwinder->eof(); - } + boost::optional<Document> out = _unwinder->getNext(); + while (!out) { + // No more elements in array currently being unwound. This will loop if the input + // document is missing the unwind field or has an empty array. + boost::optional<Document> input = pSource->getNext(); + if (!input) + return boost::none; // input exhausted - Document DocumentSourceUnwind::getCurrent() { - verify(!eof()); - return _unwinder->getCurrent(); + // Try to extract an output document from the new input document. + _unwinder->resetDocument(*input); + out = _unwinder->getNext(); + } + + return out; } - void DocumentSourceUnwind::sourceToBson( - BSONObjBuilder *pBuilder, bool explain) const { + Value DocumentSourceUnwind::serialize(bool explain) const { verify(_unwindPath); - pBuilder->append(unwindName, _unwindPath->getPath(true)); + return Value(DOC(getSourceName() << _unwindPath->getPath(true))); } - DocumentSource::GetDepsReturn DocumentSourceUnwind::getDependencies(set<string>& deps) const { - verify(_unwindPath); - deps.insert(_unwindPath->getPath(false)); + DocumentSource::GetDepsReturn DocumentSourceUnwind::getDependencies(DepsTracker* deps) const { + deps->fields.insert(_unwindPath->getPath(false)); return SEE_NEXT; } @@ -194,19 +154,20 @@ namespace mongo { !_unwindPath); // Record the unwind path. _unwindPath.reset(new FieldPath(fieldPath)); + _unwinder.reset(new Unwinder(fieldPath)); } intrusive_ptr<DocumentSource> DocumentSourceUnwind::createFromBson( - BSONElement *pBsonElement, - const intrusive_ptr<ExpressionContext> &pExpCtx) { + BSONElement elem, + const intrusive_ptr<ExpressionContext> &pExpCtx) { /* The value of $unwind should just be a field path. */ uassert(15981, str::stream() << "the " << unwindName << " field path must be specified as a string", - pBsonElement->type() == String); + elem.type() == String); - string prefixedPathString(pBsonElement->str()); + string prefixedPathString(elem.str()); string pathString(Expression::removeFieldPrefix(prefixedPathString)); intrusive_ptr<DocumentSourceUnwind> pUnwind(new DocumentSourceUnwind(pExpCtx)); pUnwind->unwindPath(FieldPath(pathString)); |
