summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/accumulator_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/pipeline/accumulator_test.cpp')
-rw-r--r--src/mongo/db/pipeline/accumulator_test.cpp368
1 files changed, 15 insertions, 353 deletions
diff --git a/src/mongo/db/pipeline/accumulator_test.cpp b/src/mongo/db/pipeline/accumulator_test.cpp
index 3f3a4ba9385..d0e40c192ca 100644
--- a/src/mongo/db/pipeline/accumulator_test.cpp
+++ b/src/mongo/db/pipeline/accumulator_test.cpp
@@ -39,12 +39,10 @@
#include "mongo/db/pipeline/accumulation_statement.h"
#include "mongo/db/pipeline/accumulator.h"
#include "mongo/db/pipeline/accumulator_for_window_functions.h"
-#include "mongo/db/pipeline/accumulator_js_reduce.h"
#include "mongo/db/pipeline/accumulator_multi.h"
#include "mongo/db/pipeline/aggregation_context_fixture.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/query/collation/collator_interface_mock.h"
-#include "mongo/db/query/query_shape/serialization_options.h"
#include "mongo/dbtests/dbtests.h"
#include "mongo/idl/server_parameter_test_util.h"
#include "mongo/logv2/log.h"
@@ -605,8 +603,8 @@ TEST(Accumulators, TopBottomNRespectsCollation) {
expCtx->setCollator(std::move(collator));
const auto n = Value(2);
auto mkdoc = [](Value a) {
- return Value(BSON(AccumulatorN::kFieldNameOutput
- << a << (AccumulatorN::kFieldNameSortFields + "0") << a));
+ return Value(BSON(AccumulatorN::kFieldNameOutput << a << AccumulatorN::kFieldNameSortFields
+ << BSON_ARRAY(a)));
};
OperationsType bottomCasesAscending{
@@ -667,12 +665,12 @@ TEST(Accumulators, TopNDescendingBottomNAscending) {
const auto n3 = Value(3);
const auto n1 = Value(1);
auto mkdoc = [](Value a) {
- return Value(BSON(AccumulatorN::kFieldNameOutput
- << a << (AccumulatorN::kFieldNameSortFields + "0") << a));
+ return Value(BSON(AccumulatorN::kFieldNameOutput << a << AccumulatorN::kFieldNameSortFields
+ << BSON_ARRAY(a)));
};
auto mkdoc2 = [](int a, Value b) {
- return Value(BSON(AccumulatorN::kFieldNameOutput
- << b << (AccumulatorN::kFieldNameSortFields + "0") << a));
+ return Value(BSON(AccumulatorN::kFieldNameOutput << b << AccumulatorN::kFieldNameSortFields
+ << BSON_ARRAY(a)));
};
OperationsType cases{
// Basic tests.
@@ -806,12 +804,12 @@ TEST(Accumulators, TopNAscendingBottomNDescending) {
const auto n3 = Value(3);
const auto n1 = Value(1);
auto mkdoc = [](Value a) {
- return Value(BSON(AccumulatorN::kFieldNameOutput
- << a << (AccumulatorN::kFieldNameSortFields + "0") << a));
+ return Value(BSON(AccumulatorN::kFieldNameOutput << a << AccumulatorN::kFieldNameSortFields
+ << BSON_ARRAY(a)));
};
auto mkdoc2 = [](int a, Value b) {
- return Value(BSON(AccumulatorN::kFieldNameOutput
- << b << (AccumulatorN::kFieldNameSortFields + "0") << a));
+ return Value(BSON(AccumulatorN::kFieldNameOutput << b << AccumulatorN::kFieldNameSortFields
+ << BSON_ARRAY(a)));
};
OperationsType cases{
// Basic tests.
@@ -947,133 +945,6 @@ TEST(Accumulators, TopNAscendingBottomNDescending) {
}
}
-TEST(Accumulators, TopBottomNMultiSortPattern) {
- auto expCtx = make_intrusive<ExpressionContextForTest>();
- const auto n = Value(3);
- auto mkdoc = [](int32_t a, int32_t b) {
- return Value(BSON(AccumulatorN::kFieldNameOutput
- << (a * 10 + b) << (AccumulatorN::kFieldNameSortFields + "0") << a
- << (AccumulatorN::kFieldNameSortFields + "1") << b));
- };
- auto mkdoc2 = [](Value output, Value a, Value b) {
- return Value(BSON(AccumulatorN::kFieldNameOutput
- << output << (AccumulatorN::kFieldNameSortFields + "0") << a
- << (AccumulatorN::kFieldNameSortFields + "1") << b));
- };
-
- OperationsType cases{
- // Basic tests.
- {{mkdoc(3, 3), mkdoc(4, 4), mkdoc(4, 5), mkdoc(100, 10)},
- {Value(std::vector<Value>{Value(45), Value(44), Value(33)})}},
- {{mkdoc(9, 5), mkdoc(8, 2), mkdoc(9, 1), mkdoc(8, 1), mkdoc(1, 0)},
- {Value(std::vector<Value>{Value(82), Value(81), Value(10)})}},
- // 3 or fewer values results in those values being returned.
- {{mkdoc(9, 9), mkdoc(8, 8), mkdoc(9, 8)},
- {Value(std::vector<Value>{Value(99), Value(98), Value(88)})}},
- {{mkdoc(9, 9)}, {Value(std::vector<Value>{Value(99)})}},
-
- // Ties are broken arbitrarily.
- {{mkdoc(9, 9), mkdoc(9, 9), mkdoc(1, 0), mkdoc(9, 9), mkdoc(1, 0), mkdoc(9, 9)},
- {Value(std::vector<Value>{Value(99), Value(10), Value(10)})}},
-
- // Null/missing cases (missing and null are NOT ignored, but missing is upconverted to
- // null).
- {{mkdoc(9, 9),
- mkdoc2(Value(BSONNULL), Value(BSONNULL), Value(BSONNULL)),
- mkdoc2(Value(BSONNULL), Value(), Value(BSONNULL)),
- mkdoc2(Value(), Value(BSONNULL), Value()),
- mkdoc2(Value(), Value(BSONNULL), Value(BSONNULL))},
- {Value(std::vector<Value>{Value(BSONNULL), Value(BSONNULL), Value(BSONNULL)})}}};
-
- try {
- auto accInit = [&](ExpressionContext* const expCtx) -> intrusive_ptr<AccumulatorState> {
- auto acc = AccumulatorTopBottomN<TopBottomSense::kBottom, false>::create(
- expCtx, BSON("a" << -1 << "b" << -1));
- acc->startNewGroup(n);
- return acc;
- };
- assertExpectedResults(expCtx.get(), cases, accInit);
- } catch (...) {
- LOGV2(8236800, "bottom3 a: -1, b: -1");
- throw;
- }
-
- // topN ascending will return same results, but in reverse order.
- for (auto& [input, expected] : cases) {
- tassert(8236803, "expected should be an array", expected.isArray());
- auto arr = expected.getArray();
- std::reverse(std::begin(arr), std::end(arr));
- expected = Value(arr);
- }
-
- try {
- auto accInit = [&](ExpressionContext* const expCtx) -> intrusive_ptr<AccumulatorState> {
- auto acc = AccumulatorTopBottomN<TopBottomSense::kTop, false>::create(
- expCtx, BSON("a" << 1 << "b" << 1));
- acc->startNewGroup(n);
- return acc;
- };
- assertExpectedResults(expCtx.get(), cases, accInit);
- } catch (...) {
- LOGV2(8236801, "top3 a: 1, b: 1");
- throw;
- }
-}
-
-template <TopBottomSense Sense>
-void runTopBottomAccumulatorTest(ExpressionContext* const expCtx,
- int32_t dir,
- Value n,
- OperationsType cases) {
- try {
- auto accInit = [&](ExpressionContext* const expCtx) -> intrusive_ptr<AccumulatorState> {
- auto acc = AccumulatorTopBottomN<Sense, false>::create(expCtx, BSON("a" << dir));
- acc->startNewGroup(n);
- return acc;
- };
- assertExpectedResults(expCtx, cases, accInit);
- } catch (...) {
- LOGV2(8236802,
- "failed top/bottom accumulator test",
- "n"_attr = n,
- "sort_dir"_attr = dir,
- "sense"_attr = Sense);
- throw;
- }
-}
-
-TEST(Accumulators, TopBottomNSortArray) {
- auto expCtx = make_intrusive<ExpressionContextForTest>();
- Value n{2};
- Value arr1{std::vector<Value>{
- Value(6),
- Value(1),
- }};
- Value arr2{std::vector<Value>{
- Value(5),
- Value(2),
- }};
- Value arr3{std::vector<Value>{
- Value(4),
- Value(3),
- }};
-
- auto mkdoc = [](int32_t id, Value arr) {
- return Value(BSON(AccumulatorN::kFieldNameOutput
- << id << (AccumulatorN::kFieldNameSortFields + "0") << arr));
- };
- OperationsType topCases{{{mkdoc(1, arr1), mkdoc(2, arr2), mkdoc(3, arr3)},
- {Value(std::vector<Value>{Value(1), Value(2)})}}};
- OperationsType bottomCases{{{mkdoc(1, arr1), mkdoc(2, arr2), mkdoc(3, arr3)},
- {Value(std::vector<Value>{Value(2), Value(3)})}}};
-
- runTopBottomAccumulatorTest<TopBottomSense::kTop>(expCtx.get(), 1, n, topCases);
- runTopBottomAccumulatorTest<TopBottomSense::kTop>(expCtx.get(), -1, n, topCases);
-
- runTopBottomAccumulatorTest<TopBottomSense::kBottom>(expCtx.get(), 1, n, bottomCases);
- runTopBottomAccumulatorTest<TopBottomSense::kBottom>(expCtx.get(), -1, n, bottomCases);
-}
-
// Utility to test the single counterparts of the topN/bottomN accumulators.
template <TopBottomSense s>
void testSingle(OperationsType cases, ExpressionContext* const expCtx, const BSONObj& sortPattern) {
@@ -1104,8 +975,8 @@ TEST(Accumulators, TopBottomSingle) {
auto expCtx = make_intrusive<ExpressionContextForTest>();
const auto n = Value(1);
auto mkdoc = [](Value a) {
- return Value(BSON(AccumulatorN::kFieldNameOutput
- << a << (AccumulatorN::kFieldNameSortFields + "0") << a));
+ return Value(BSON(AccumulatorN::kFieldNameOutput << a << AccumulatorN::kFieldNameSortFields
+ << BSON_ARRAY(a)));
};
const BSONObj ascSort = BSON("a" << 1);
@@ -1211,8 +1082,9 @@ struct TopBottomNRemoveTest : public AggregationContextFixture {
template <typename SortKeyType>
void add(SortKeyType sortKey, int output) {
- auto v = Value(BSON(AccumulatorN::kFieldNameOutput
- << output << (AccumulatorN::kFieldNameSortFields + "0") << sortKey));
+ auto v =
+ Value(BSON(AccumulatorN::kFieldNameOutput
+ << output << AccumulatorN::kFieldNameSortFields << BSON_ARRAY(sortKey)));
_acc->process(v, false);
_q.push(v);
}
@@ -1871,199 +1743,6 @@ TEST(Accumulators, CovarianceWithRandomVariables) {
assertCovariance<AccumulatorCovarianceSamp>(&expCtx, randomVariables, boost::none);
}
-Value parseAndSerializeAccumExpr(
- const BSONObj& obj,
- std::function<boost::intrusive_ptr<Expression>(
- ExpressionContext* expCtx, BSONElement, const VariablesParseState&)> func) {
- SerializationOptions options = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST;
- auto expCtx = make_intrusive<ExpressionContextForTest>();
- auto expr = func(expCtx.get(), obj.firstElement(), expCtx->variablesParseState);
- return expr->serialize(options);
-}
-
-Document parseAndSerializeAccum(
- const BSONElement elem,
- std::function<AccumulationExpression(
- ExpressionContext* const expCtx, BSONElement, VariablesParseState)> func) {
- SerializationOptions options = SerializationOptions::kDebugShapeAndMarkIdentifiers_FOR_TEST;
- auto expCtx = make_intrusive<ExpressionContextForTest>();
- VariablesParseState vps = expCtx->variablesParseState;
-
- auto expr = func(expCtx.get(), elem, vps);
- auto accum = expr.factory();
- return accum->serialize(expr.initializer, expr.argument, options);
-}
-
-Document parseAndSerializeAccumRepresentative(
- const BSONElement elem,
- std::function<AccumulationExpression(
- ExpressionContext* const expCtx, BSONElement, VariablesParseState)> func) {
- SerializationOptions options = SerializationOptions::kRepresentativeQueryShapeSerializeOptions;
- auto expCtx = make_intrusive<ExpressionContextForTest>();
- VariablesParseState vps = expCtx->variablesParseState;
-
- auto expr = func(expCtx.get(), elem, vps);
- auto accum = expr.factory();
- return accum->serialize(expr.initializer, expr.argument, options);
-}
-
-TEST(Accumulators, SerializeWithRedaction) {
- auto jsReduce =
- BSON("$accumulator" << BSON("init"
- << "function() {}"
- << "accumulateArgs"
- << BSON_ARRAY("$a"
- << "$b")
- << "accumulate"
- << "function(state, str1, str2) {return str1 + str2;}"
- << "merge"
- << "function(s1, s2) {return s1 || s2;}"
- << "lang"
- << "js"));
- auto actual = parseAndSerializeAccum(jsReduce.firstElement(), &AccumulatorJs::parse);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({
- "$accumulator": {
- "init": "?string",
- "initArgs": "[]",
- "accumulate": "?string",
- "accumulateArgs": [
- "$HASH<a>",
- "$HASH<b>"
- ],
- "merge": "?string",
- "lang": "js"
- }
- })",
- actual);
-
- auto topN = BSON("$topN" << BSON("n" << 3 << "output"
- << "$output"
- << "sortBy" << BSON("sortKey" << 1)));
- actual = parseAndSerializeAccum(
- topN.firstElement(), &AccumulatorTopBottomN<TopBottomSense::kTop, false>::parseTopBottomN);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({
- "$topN": {
- "n": "?number",
- "output": "$HASH<output>",
- "sortBy": {
- "HASH<sortKey>": 1
- }
- }
- })",
- actual);
-
- auto addToSet = BSON("$addToSet" << BSON("a" << 5));
- actual = parseAndSerializeAccum(addToSet.firstElement(),
- &genericParseSingleExpressionAccumulator<AccumulatorAddToSet>);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"$addToSet":"?object"})",
- actual);
-
- auto sum = BSON("$sum" << BSON_ARRAY(4 << 6));
- actual = parseAndSerializeAccum(sum.firstElement(),
- &genericParseSingleExpressionAccumulator<AccumulatorSum>);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"$sum": "?array<?number>"})",
- actual);
-
- sum = BSON("$sum" << BSON_ARRAY("$a" << 5 << 3 << BSON("$sum" << BSON_ARRAY(4 << 6))));
- actual = parseAndSerializeAccum(sum.firstElement(),
- &genericParseSingleExpressionAccumulator<AccumulatorSum>);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"$sum":["$HASH<a>","?number","?number",{"$sum":"?array<?number>"}]})",
- actual);
-
- auto mergeObjs = BSON("$mergeObjects" << BSON_ARRAY("$a" << BSON("b"
- << "null")));
- actual =
- parseAndSerializeAccum(mergeObjs.firstElement(),
- &genericParseSingleExpressionAccumulator<AccumulatorMergeObjects>);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"$mergeObjects":["$HASH<a>","?object"]})",
- actual);
-
- auto push = BSON("$push" << BSON("$eq" << BSON_ARRAY("$str"
- << "str2")));
- actual = parseAndSerializeAccum(push.firstElement(),
- &genericParseSingleExpressionAccumulator<AccumulatorPush>);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"$push":{"$eq":["$HASH<str>","?string"]}})",
- actual);
-
- auto top = BSON("$top" << BSON("output"
- << "$b"
- << "sortBy" << BSON("sales" << 1)));
- actual = parseAndSerializeAccum(
- top.firstElement(), &AccumulatorTopBottomN<TopBottomSense::kTop, true>::parseTopBottomN);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({
- "$top": {
- "output": "$HASH<b>",
- "sortBy": {
- "HASH<sales>": 1
- }
- }
- })",
- actual);
-
- auto max = BSON("$max" << BSON_ARRAY(
- "$a" << 2 << 3 << BSON("$max" << BSON_ARRAY(BSON_ARRAY("$b" << 4 << 5)))));
- actual = parseAndSerializeAccum(max.firstElement(),
- &genericParseSingleExpressionAccumulator<AccumulatorMax>);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({
- "$max": [
- "$HASH<a>",
- "?number",
- "?number",
- {
- "$max": [
- [
- "$HASH<b>",
- "?number",
- "?number"
- ]
- ]
- }
- ]
- })",
- actual);
-
- auto internalJsReduce = BSON(
- "$_internalJsReduce" << BSON("data"
- << "$emits"
- << "eval"
- << "function(key, values) {\n return Array.sum(values);\n"));
- actual = parseAndSerializeAccum(internalJsReduce.firstElement(),
- &AccumulatorInternalJsReduce::parseInternalJsReduce);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"$_internalJsReduce":{"data":"$HASH<emits>","eval":"?string"}})",
- actual);
-}
-
-TEST(AccumulatorsToExpression, SerializeWithRedaction) {
- auto maxN = BSON("$maxN" << BSON("n" << 3 << "input" << BSON_ARRAY(19 << 7 << 28 << 3 << 5)));
- using Sense = AccumulatorMinMax::Sense;
- auto actual =
- parseAndSerializeAccumExpr(maxN, &AccumulatorMinMaxN::parseExpression<Sense::kMax>);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"$maxN":{"n":"?number","input":"?array<?number>"}})",
- actual.getDocument());
-
- auto firstN = BSON("$firstN" << BSON("input"
- << "$sales"
- << "n"
- << "\'string\'"));
- using FirstLastSense = AccumulatorFirstLastN::Sense;
- actual = parseAndSerializeAccumExpr(
- firstN, &AccumulatorFirstLastN::parseExpression<FirstLastSense::kFirst>);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"$firstN":{"n":"?string","input":"$HASH<sales>"}})",
- actual.getDocument());
-}
-
/* ------------------------- AccumulatorMergeObjects -------------------------- */
TEST(AccumulatorMergeObjects, MergingZeroObjectsShouldReturnEmptyDocument) {
@@ -2115,21 +1794,4 @@ TEST(AccumulatorMergeObjects, MergingWithEmptyDocumentShouldIgnore) {
assertExpectedResults<AccumulatorMergeObjects>(&expCtx, {{{first, second}, expected}});
}
-TEST(AccumulatorMergeObjects, RoundTripSerializationLiteral) {
- auto mergeObjs = BSON("$mergeObjects" << BSON("$literal" << BSON_ARRAY(5 << true)));
- auto actual = parseAndSerializeAccumRepresentative(
- mergeObjs.firstElement(),
- &genericParseSingleExpressionAccumulator<AccumulatorMergeObjects>);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"$mergeObjects":{"$const":[2,"or more types"]}})",
- actual);
-
- auto roundTrip = parseAndSerializeAccumRepresentative(
- actual.toBson().firstElement(),
- &genericParseSingleExpressionAccumulator<AccumulatorMergeObjects>);
- ASSERT_DOCUMENT_EQ_AUTO( // NOLINT
- R"({"$mergeObjects":{"$const":[2,"or more types"]}})",
- roundTrip);
-}
-
} // namespace AccumulatorTests