summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/document_source_unwind.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/pipeline/document_source_unwind.cpp')
-rw-r--r--src/mongo/db/pipeline/document_source_unwind.cpp153
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));