summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/document_source_group_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/pipeline/document_source_group_test.cpp')
-rw-r--r--src/mongo/db/pipeline/document_source_group_test.cpp340
1 files changed, 327 insertions, 13 deletions
diff --git a/src/mongo/db/pipeline/document_source_group_test.cpp b/src/mongo/db/pipeline/document_source_group_test.cpp
index d3ed11f2031..9a621cffdcd 100644
--- a/src/mongo/db/pipeline/document_source_group_test.cpp
+++ b/src/mongo/db/pipeline/document_source_group_test.cpp
@@ -48,6 +48,7 @@
#include "mongo/db/pipeline/dependencies.h"
#include "mongo/db/pipeline/document_source_group.h"
#include "mongo/db/pipeline/document_source_mock.h"
+#include "mongo/db/pipeline/document_source_streaming_group.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/query/query_test_service_context.h"
@@ -252,32 +253,65 @@ BSONObj toBson(const intrusive_ptr<DocumentSource>& source) {
return arr[0].getDocument().toBson();
}
+enum class GroupStageType { Default, Streaming };
+
class Base : public ServiceContextTest {
public:
- Base()
+ Base(GroupStageType groupStageType = GroupStageType::Default)
: _opCtx(makeOperationContext()),
_ctx(new ExpressionContextForTest(_opCtx.get(),
AggregateCommandRequest(NamespaceString(ns), {}))),
- _tempDir("DocumentSourceGroupTest") {}
+ _tempDir("DocumentSourceGroupTest"),
+ _groupStageType(groupStageType) {}
protected:
+ StringData getStageName() const {
+ switch (_groupStageType) {
+ case GroupStageType::Default:
+ return DocumentSourceGroup::kStageName;
+ case GroupStageType::Streaming:
+ return DocumentSourceStreamingGroup::kStageName;
+ default:
+ MONGO_UNREACHABLE;
+ }
+ }
+
+ virtual boost::optional<size_t> getMaxMemoryUsageBytes() {
+ return boost::none;
+ }
+
+ intrusive_ptr<DocumentSource> createFromBson(
+ BSONElement specElement, intrusive_ptr<ExpressionContext> expressionContext) {
+ switch (_groupStageType) {
+ case GroupStageType::Default:
+ return DocumentSourceGroup::createFromBsonWithMaxMemoryUsage(
+ std::move(specElement), expressionContext, getMaxMemoryUsageBytes());
+ case GroupStageType::Streaming:
+ return DocumentSourceStreamingGroup::createFromBsonWithMaxMemoryUsage(
+ std::move(specElement), expressionContext, getMaxMemoryUsageBytes());
+ default:
+ MONGO_UNREACHABLE;
+ }
+ }
+
void createGroup(const BSONObj& spec, bool inShard = false, bool inMongos = false) {
- BSONObj namedSpec = BSON("$group" << spec);
+ BSONObj namedSpec = BSON(getStageName() << spec);
BSONElement specElement = namedSpec.firstElement();
intrusive_ptr<ExpressionContextForTest> expressionContext = new ExpressionContextForTest(
_opCtx.get(), AggregateCommandRequest(NamespaceString(ns), {}));
+ expressionContext->allowDiskUse = true;
// For $group, 'inShard' implies 'fromMongos' and 'needsMerge'.
expressionContext->fromMongos = expressionContext->needsMerge = inShard;
expressionContext->inMongos = inMongos;
// Won't spill to disk properly if it needs to.
expressionContext->tempDir = _tempDir.path();
- _group = DocumentSourceGroup::createFromBson(specElement, expressionContext);
+ _group = createFromBson(specElement, expressionContext);
assertRoundTrips(_group, expressionContext);
}
- DocumentSourceGroup* group() {
- return static_cast<DocumentSourceGroup*>(_group.get());
+ DocumentSourceGroupBase* group() {
+ return static_cast<DocumentSourceGroupBase*>(_group.get());
}
/** Assert that iterator state accessors consistently report the source is exhausted. */
void assertEOF(const intrusive_ptr<DocumentSource>& source) const {
@@ -299,8 +333,7 @@ private:
// $const operators may be introduced in the first serialization.
BSONObj spec = toBson(group);
BSONElement specElement = spec.firstElement();
- intrusive_ptr<DocumentSource> generated =
- DocumentSourceGroup::createFromBson(specElement, expCtx);
+ intrusive_ptr<DocumentSource> generated = createFromBson(specElement, expCtx);
ASSERT_BSONOBJ_EQ(spec, toBson(generated));
}
std::unique_ptr<QueryTestServiceContext> _queryServiceContext;
@@ -308,6 +341,7 @@ private:
intrusive_ptr<ExpressionContextForTest> _ctx;
intrusive_ptr<DocumentSource> _group;
TempDir _tempDir;
+ GroupStageType _groupStageType;
};
class ParseErrorBase : public Base {
@@ -355,10 +389,9 @@ class IdConstantBase : public ExpressionBase {
class NonObject : public Base {
public:
void _doTest() final {
- BSONObj spec = BSON("$group"
- << "foo");
+ BSONObj spec = BSON(getStageName() << "foo");
BSONElement specElement = spec.firstElement();
- ASSERT_THROWS(DocumentSourceGroup::createFromBson(specElement, ctx()), AssertionException);
+ ASSERT_THROWS(createFromBson(specElement, ctx()), AssertionException);
}
};
@@ -557,8 +590,10 @@ typedef map<Value, Document, ValueCmp> IdMap;
class CheckResultsBase : public Base {
public:
+ CheckResultsBase(GroupStageType groupStageType = GroupStageType::Default)
+ : Base(groupStageType) {}
virtual ~CheckResultsBase() {}
- void _doTest() {
+ void _doTest() override {
runSharded(false);
runSharded(true);
}
@@ -571,7 +606,7 @@ public:
if (sharded) {
sink = createMerger();
// Serialize and re-parse the shard stage.
- createGroup(toBson(group())["$group"].Obj(), true);
+ createGroup(toBson(group())[group()->getSourceName()].Obj(), true);
group()->setSource(source.get());
sink->setSource(group());
}
@@ -871,6 +906,277 @@ public:
}
};
+class StreamingSimple final : public CheckResultsBase {
+public:
+ StreamingSimple() : CheckResultsBase(GroupStageType::Streaming) {}
+
+private:
+ deque<DocumentSource::GetNextResult> inputData() final {
+ return {Document(BSON("a" << 1 << "b" << 1)),
+ Document(BSON("a" << 1 << "b" << 2)),
+ Document(BSON("a" << 2 << "b" << 3)),
+ Document(BSON("a" << 2 << "b" << 1))};
+ }
+ BSONObj groupSpec() final {
+ return BSON("_id"
+ << "$a"
+ << "sum"
+ << BSON("$sum"
+ << "$b")
+ << "$monotonicIdFields" << BSON_ARRAY("_id"));
+ }
+ string expectedResultSetString() final {
+ return "[{_id:1,sum:3},{_id:2,sum:4}]";
+ }
+};
+
+constexpr size_t kBigStringSize = 1024;
+const std::string kBigString(kBigStringSize, 'a');
+
+class CheckResultsAndSpills : public CheckResultsBase {
+public:
+ CheckResultsAndSpills(GroupStageType groupStageType, uint64_t expectedSpills)
+ : CheckResultsBase(groupStageType), _expectedSpills(expectedSpills) {}
+
+ void _doTest() final {
+ for (int sharded = 0; sharded < 2; ++sharded) {
+ runSharded(sharded);
+ const auto* groupStats = static_cast<const GroupStats*>(group()->getSpecificStats());
+ ASSERT_EQ(groupStats->spills, _expectedSpills);
+ }
+ }
+
+private:
+ uint64_t _expectedSpills;
+};
+
+template <GroupStageType groupStageType, uint64_t expectedSpills>
+class StreamingSpillTest : public CheckResultsAndSpills {
+public:
+ StreamingSpillTest() : CheckResultsAndSpills(groupStageType, expectedSpills) {}
+
+private:
+ static constexpr int kCount = 11;
+
+ deque<DocumentSource::GetNextResult> inputData() final {
+ deque<DocumentSource::GetNextResult> queue;
+ for (int i = 0; i < kCount; ++i) {
+ queue.emplace_back(Document(BSON("a" << i << "b" << kBigString)));
+ }
+ return queue;
+ }
+
+ BSONObj groupSpec() final {
+ if constexpr (groupStageType == GroupStageType::Streaming) {
+ return fromjson("{_id: '$a', big_array: {$push: '$b'}, $monotonicIdFields: ['_id']}");
+ } else {
+ return fromjson("{_id: '$a', big_array: {$push: '$b'}}");
+ }
+ }
+
+ boost::optional<size_t> getMaxMemoryUsageBytes() final {
+ return 10 * kBigStringSize;
+ }
+
+ BSONObj expectedResultSet() final {
+ BSONArrayBuilder result;
+ for (int i = 0; i < kCount; ++i) {
+ result << BSON("_id" << i << "big_array" << BSON_ARRAY(kBigString));
+ }
+ return result.arr();
+ }
+};
+
+class WithoutStreamingSpills final
+ : public StreamingSpillTest<GroupStageType::Default, 2 /*expectedSpills*/> {};
+class StreamingDoesNotSpill final
+ : public StreamingSpillTest<GroupStageType::Streaming, 0 /*expectedSpills*/> {};
+
+class StreamingCanSpill final : public CheckResultsAndSpills {
+public:
+ StreamingCanSpill() : CheckResultsAndSpills(GroupStageType::Streaming, 2 /*expectedSpills*/) {}
+
+private:
+ static constexpr int kCount = 11;
+
+ deque<DocumentSource::GetNextResult> inputData() final {
+ deque<DocumentSource::GetNextResult> queue;
+ for (int i = 0; i < kCount; ++i) {
+ queue.emplace_back(Document(BSON("x" << 0 << "y" << i << "b" << kBigString)));
+ }
+ return queue;
+ }
+
+ BSONObj groupSpec() final {
+ auto id = BSON("x"
+ << "$x"
+ << "y"
+ << "$y");
+ return BSON("_id" << id << "big_array"
+ << BSON("$push"
+ << "$b")
+ << "$monotonicIdFields" << BSON_ARRAY("x"));
+ }
+
+ boost::optional<size_t> getMaxMemoryUsageBytes() final {
+ return 10 * kBigStringSize;
+ }
+
+ BSONObj expectedResultSet() final {
+ BSONArrayBuilder result;
+ for (int i = 0; i < kCount; ++i) {
+ auto id = BSON("x" << 0 << "y" << i);
+ result << BSON("_id" << id << "big_array" << BSON_ARRAY(kBigString));
+ }
+ return result.arr();
+ }
+};
+
+class StreamingAlternatingSpillAndNoSpillBatches : public CheckResultsAndSpills {
+public:
+ StreamingAlternatingSpillAndNoSpillBatches()
+ : CheckResultsAndSpills(GroupStageType::Streaming, 3 /*expectedSpills*/) {}
+
+private:
+ static constexpr int kCount = 12;
+
+ deque<DocumentSource::GetNextResult> inputData() final {
+ deque<DocumentSource::GetNextResult> queue;
+ for (int i = 0; i < kCount; ++i) {
+ // For groups with i % 3 == 0 and i % 3 == 1 there should be no spilling, but groups
+ // with i % 3 == 2 should spill.
+ for (int j = 0; j < (i % 3) + 1; ++j) {
+ queue.emplace_back(Document(BSON("a" << i << "b" << kBigString)));
+ }
+ }
+ return queue;
+ }
+
+ BSONObj groupSpec() final {
+ return BSON("_id"
+ << "$a"
+ << "big_array"
+ << BSON("$push"
+ << "$b")
+ << "$monotonicIdFields" << BSON_ARRAY("_id"));
+ }
+
+ boost::optional<size_t> getMaxMemoryUsageBytes() final {
+ return (25 * kBigStringSize) / 10;
+ }
+
+ BSONObj expectedResultSet() final {
+ BSONArrayBuilder result;
+ for (int i = 0; i < kCount; ++i) {
+ BSONArrayBuilder bigArrayBuilder;
+ for (int j = 0; j < (i % 3) + 1; ++j) {
+ bigArrayBuilder << kBigString;
+ }
+ result << BSON("_id" << i << "big_array" << bigArrayBuilder.arr());
+ }
+ return result.arr();
+ }
+};
+
+class StreamingComplex final : public CheckResultsBase {
+public:
+ StreamingComplex() : CheckResultsBase(GroupStageType::Streaming) {}
+
+private:
+ static constexpr int kCount = 3;
+
+ deque<DocumentSource::GetNextResult> inputData() final {
+ deque<DocumentSource::GetNextResult> queue;
+ for (int i = 0; i < kCount; ++i) {
+ for (int j = 0; j < kCount; ++j) {
+ for (int k = 0; k < kCount; ++k) {
+ queue.emplace_back(Document(BSON("x" << i << "y" << j << "z" << k)));
+ }
+ }
+ }
+ return queue;
+ }
+
+ BSONObj groupSpec() final {
+ BSONObj id = BSON("x"
+ << "$x"
+ << "y"
+ << "$y");
+ return BSON("_id" << id << "sum"
+ << BSON("$sum"
+ << "$z")
+ << "$monotonicIdFields" << BSON_ARRAY("x"));
+ }
+
+ boost::optional<size_t> getMaxMemoryUsageBytes() final {
+ return 10 * kBigStringSize;
+ }
+
+ BSONObj expectedResultSet() final {
+ BSONArrayBuilder result;
+ for (int i = 0; i < kCount; ++i) {
+ for (int j = 0; j < kCount; ++j) {
+ result << BSON("_id" << BSON("x" << i << "y" << j) << "sum"
+ << (kCount * (kCount - 1)) / 2);
+ }
+ }
+ return result.arr();
+ }
+};
+
+class StreamingMultipleMonotonicFields final : public CheckResultsBase {
+public:
+ StreamingMultipleMonotonicFields() : CheckResultsBase(GroupStageType::Streaming) {}
+
+private:
+ static constexpr int kCount = 6;
+ deque<DocumentSource::GetNextResult> inputData() final {
+ deque<DocumentSource::GetNextResult> queue;
+ generateInputOutput([&queue](int x, int y) {
+ for (int i = 0; i < kCount; ++i) {
+ queue.emplace_back(Document(BSON("x" << x << "y" << y << "z" << i)));
+ }
+ });
+ return queue;
+ }
+
+ BSONObj groupSpec() final {
+ return fromjson(
+ "{_id: {x: '$x', y: '$y'}, sum: {$sum: '$z'}, $monotonicIdFields: ['x', 'y']}");
+ }
+
+ boost::optional<size_t> getMaxMemoryUsageBytes() final {
+ return 10 * kBigStringSize;
+ }
+
+ BSONObj expectedResultSet() final {
+ BSONArrayBuilder result;
+ const int sum = (kCount * (kCount - 1)) / 2;
+ generateInputOutput([&](int x, int y) {
+ result << BSON("_id" << BSON("x" << x << "y" << y) << "sum" << sum);
+ });
+ return result.arr();
+ }
+
+ template <typename Callback>
+ void generateInputOutput(const Callback& callback) {
+ int x = 0;
+ int y = 0;
+ for (int i = 0; i < kCount; ++i) {
+ callback(x, y);
+ int state = i % 3;
+ if (state == 0) {
+ x++;
+ } else if (state == 1) {
+ y++;
+ } else {
+ x++;
+ y++;
+ }
+ }
+ }
+};
+
class All : public OldStyleSuiteSpecification {
public:
All() : OldStyleSuiteSpecification("DocumentSourceGroupTests") {}
@@ -911,6 +1217,14 @@ public:
add<Dependencies>();
add<StringConstantIdAndAccumulatorExpressions>();
add<ArrayConstantAccumulatorExpression>();
+
+ add<StreamingSimple>();
+ add<WithoutStreamingSpills>();
+ add<StreamingDoesNotSpill>();
+ add<StreamingCanSpill>();
+ add<StreamingAlternatingSpillAndNoSpillBatches>();
+ add<StreamingComplex>();
+ add<StreamingMultipleMonotonicFields>();
#if 0
// Disabled tests until SERVER-23318 is implemented.
add<StreamingOptimization>();