diff options
Diffstat (limited to 'src/mongo/db/pipeline/accumulator_test.cpp')
| -rw-r--r-- | src/mongo/db/pipeline/accumulator_test.cpp | 368 |
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 |
