diff options
author | Timour Katchaounov <timour.katchaounov@mongodb.com> | 2022-02-23 11:22:10 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-02-23 11:49:56 +0000 |
commit | ad12685ff33131f95053dc567639312999957475 (patch) | |
tree | c6416426d98f6a51f3b503dbaa8c201960b657e7 | |
parent | d9cbe79974a9dd0596c72cd4033fc9d8ceb1eb5b (diff) | |
download | mongo-ad12685ff33131f95053dc567639312999957475.tar.gz |
SERVER-57588 Inconsistent query results when an array position is indexed whose value is an array
(cherry picked from commit 90699509e15b33fda10832e79efcd158aee1f0eb)
-rw-r--r-- | jstests/libs/doc_validation_utils.js | 24 | ||||
-rw-r--r-- | jstests/noPassthrough/operator_counters_expressions.js | 179 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_impl.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/ops/parsed_delete.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/ops/parsed_update.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/ops/parsed_update.h | 3 | ||||
-rw-r--r-- | src/mongo/db/ops/write_ops_exec.cpp | 23 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_lookup.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_context.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_context.h | 41 | ||||
-rw-r--r-- | src/mongo/db/query/canonical_query.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/projection_ast.h | 15 | ||||
-rw-r--r-- | src/mongo/db/stats/counters.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/stats/counters.h | 31 | ||||
-rw-r--r-- | src/mongo/db/update/pipeline_executor.cpp | 2 |
16 files changed, 338 insertions, 17 deletions
diff --git a/jstests/libs/doc_validation_utils.js b/jstests/libs/doc_validation_utils.js new file mode 100644 index 00000000000..6017c10a4af --- /dev/null +++ b/jstests/libs/doc_validation_utils.js @@ -0,0 +1,24 @@ +/** + * Helper functions for document validation. + */ + +/** + * Assert that a command fails with a DocumentValidationFailure, and verify that the + * 'errInfo' field is propogated as a part of the doc validation failure. + */ +function assertDocumentValidationFailure(res, coll) { + assert.commandFailedWithCode(res, ErrorCodes.DocumentValidationFailure, tojson(res)); + if (coll.getMongo().writeMode() === "commands") { + if (res instanceof BulkWriteResult) { + const errors = res.getWriteErrors(); + for (const error of errors) { + assert(error.hasOwnProperty("errInfo"), tojson(error)); + assert.eq(typeof error["errInfo"], "object", tojson(error)); + } + } else { + const error = res instanceof WriteResult ? res.getWriteError() : res; + assert(error.hasOwnProperty("errInfo"), tojson(error)); + assert.eq(typeof error["errInfo"], "object", tojson(error)); + } + } +} diff --git a/jstests/noPassthrough/operator_counters_expressions.js b/jstests/noPassthrough/operator_counters_expressions.js new file mode 100644 index 00000000000..021bc243fd3 --- /dev/null +++ b/jstests/noPassthrough/operator_counters_expressions.js @@ -0,0 +1,179 @@ +/** + * Tests aggregate expression counters. + * @tags: [ + * ] + */ + +(function() { +"use strict"; +load("jstests/libs/doc_validation_utils.js"); // for assertDocumentValidationFailure + +const mongod = MongoRunner.runMongod(); +const db = mongod.getDB(jsTest.name()); +const collName = jsTest.name(); +const coll = db[collName]; +coll.drop(); + +for (let i = 0; i < 3; i++) { + assert.commandWorked(coll.insert({ + _id: i, + x: i, + "xyz": 42, + "a.b": "bar", + })); +} + +/** + * Execute the `command` and compare the operator counters with the expected values. + * @param {*} command to be executed. + * @param {*} expectedCounters expected operator counters. + */ +function checkCounters(command, expectedCounters) { + const origCounters = db.serverStatus().metrics.operatorCounters.expressions; + + command(); + + const newCounters = db.serverStatus().metrics.operatorCounters.expressions; + let actualCounters = {}; + for (let ec in origCounters) { + const diff = newCounters[ec] - origCounters[ec]; + if (diff !== 0) { + actualCounters[ec] = diff; + } + } + + assert.docEq(expectedCounters, actualCounters); +} + +/** + * Check operator counters in the `find`. + * @param {*} query to be executed. + * @param {*} expectedCounters - expected operator counters. + * @param {*} expectedCount - expected number of records returned by the `find` function. + */ +function checkFindCounters(query, expectedCounters, expectedCount) { + checkCounters(() => assert.eq(expectedCount, coll.find(query).itcount()), expectedCounters); +} + +/** + * Check operator counters in the `aggregate`. + * @param {*} pipeline to be executed. + * @param {*} expectedCounters - expected operator counters. + * @param {*} expectedCount - expected number of records returned by the `find` function. + */ +function checkAggregationCounters(pipeline, expectedCounters, expectedCount) { + checkCounters(() => assert.eq(expectedCount, coll.aggregate(pipeline).itcount()), + expectedCounters); +} + +// Find. +checkFindCounters({$expr: {$eq: [{$add: [0, "$xyz"]}, 42]}}, {"$add": 1, "$eq": 1}, 3); + +// Update. +checkCounters(() => assert.commandWorked( + coll.update({_id: 0, $expr: {$eq: [{$add: [0, "$xyz"]}, 42]}}, {$set: {y: 10}})), + {"$add": 1, "$eq": 1}); + +// Delete. +checkCounters(() => assert.commandWorked(db.runCommand({ + delete: coll.getName(), + deletes: [{q: {"_id": {$gt: 1}, $expr: {$eq: [{$add: [0, "$xyz"]}, 42]}}, limit: 1}] +})), + {"$add": 1, "$eq": 1}); + +// In aggregation pipeline. +let pipeline = [{$project: {_id: 1, test: {$add: [0, "$xyz"]}}}]; +checkAggregationCounters(pipeline, {"$add": 1}, 2); + +pipeline = [{$match: {_id: 1, $expr: {$eq: [{$add: [0, "$xyz"]}, 42]}}}]; +checkAggregationCounters(pipeline, {"$add": 1, "$eq": 1}, 1); + +// With sub-pipeline. +const testColl = db.operator_counters_expressions2; + +function initTestColl(i) { + testColl.drop(); + assert.commandWorked(testColl.insert({ + _id: i, + x: i, + "xyz": "bar", + })); +} + +initTestColl(1); + +// Expressions in view pipeline. +db.view.drop(); +let viewPipeline = [{$match: {$expr: {$eq: ["$a.b", "bar"]}}}]; +assert.commandWorked( + db.runCommand({create: "view", viewOn: coll.getName(), pipeline: viewPipeline})); +checkCounters(() => db.view.find().itcount(), {"$eq": 1}); + +// $cond +pipeline = [{$project: {item: 1, discount: {$cond: {if: {$gte: ["$x", 1]}, then: 10, else: 0}}}}]; +checkAggregationCounters(pipeline, {"$cond": 1, "$gte": 1}, 2); + +// $ifNull +pipeline = [{$project: {description: {$ifNull: ["$description", "Unspecified"]}}}]; +checkAggregationCounters(pipeline, {"$ifNull": 1}, 2); + +// $divide, $switch +let query = { + $expr: { + $eq: [ + { + $switch: { + branches: [{case: {$gt: ["$x", 0]}, then: {$divide: ["$x", 2]}}], + default: {$subtract: [100, "$x"]} + } + }, + 100 + ] + } +}; +checkFindCounters(query, {"$divide": 1, "$subtract": 1, "$eq": 1, "$gt": 1, "$switch": 1}, 1); + +// $cmp, $exp, $abs, $range +pipeline = [{ + $project: { + cmpField: {$cmp: ["$x", 250]}, + expField: {$exp: "$x"}, + absField: {$abs: "$x"}, + rangeField: {$range: [0, "$x", 25]} + } +}]; +checkAggregationCounters(pipeline, {"$abs": 1, "$cmp": 1, "$exp": 1, "$range": 1}, 2); + +// $or +pipeline = [{$match: {$expr: {$or: [{$eq: ["$_id", 0]}, {$eq: ["$x", 1]}]}}}]; +checkAggregationCounters(pipeline, {"$eq": 2, "$or": 1}, 2); + +// $dateFromParts +pipeline = + [{$project: {date: {$dateFromParts: {'year': 2021, 'month': 10, 'day': {$add: ['$x', 10]}}}}}]; +checkAggregationCounters(pipeline, {"$add": 1, "$dateFromParts": 1}, 2); + +// $concat +pipeline = [{$project: {mystring: {$concat: ["0", "$a.b"]}}}]; +checkAggregationCounters(pipeline, {"$concat": 1}, 2); + +// $toDouble +pipeline = [{$project: {doubleval: {$toDouble: "$_id"}}}]; +checkAggregationCounters(pipeline, {"$toDouble": 1}, 2); + +// $setIntersection +pipeline = [{$project: {intersection: {$setIntersection: [[1, 2, 3], [3, 2]]}}}]; +checkAggregationCounters(pipeline, {"$setIntersection": 1}, 2); + +// Expressions in bulk operations. +const bulkColl = db.operator_counters_expressions3; +for (let i = 0; i < 3; i++) { + assert.commandWorked(bulkColl.insert({_id: i, x: i})); +} +const bulkOp = bulkColl.initializeUnorderedBulkOp(); +bulkOp.find({$expr: {$eq: ["$x", 2]}}).update({$set: {x: 10}}); +bulkOp.find({$expr: {$lt: ["$x", 1]}}).remove(); +checkCounters(() => assert.commandWorked(bulkOp.execute()), {"$eq": 1, "$lt": 1}); + +MongoRunner.stopMongod(mongod); +})(); diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp index 9dc1804de0d..7d4ecf212af 100644 --- a/src/mongo/db/catalog/collection_impl.cpp +++ b/src/mongo/db/catalog/collection_impl.cpp @@ -438,8 +438,10 @@ Collection::Validator CollectionImpl::parseValidator( _validationLevel == CollectionImpl::ValidationLevel::MODERATE) allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords; + expCtx->startExpressionCounters(); auto statusWithMatcher = MatchExpressionParser::parse(validator, expCtx, ExtensionsCallbackNoop(), allowedFeatures); + expCtx->stopExpressionCounters(); if (!statusWithMatcher.isOK()) { return { diff --git a/src/mongo/db/ops/parsed_delete.cpp b/src/mongo/db/ops/parsed_delete.cpp index e56b38735d9..15c8e6b9253 100644 --- a/src/mongo/db/ops/parsed_delete.cpp +++ b/src/mongo/db/ops/parsed_delete.cpp @@ -79,6 +79,7 @@ Status ParsedDelete::parseRequest() { return Status::OK(); } + _expCtx->startExpressionCounters(); return parseQueryToCQ(); } diff --git a/src/mongo/db/ops/parsed_update.cpp b/src/mongo/db/ops/parsed_update.cpp index cdcc2428029..31bfa8bbdc8 100644 --- a/src/mongo/db/ops/parsed_update.cpp +++ b/src/mongo/db/ops/parsed_update.cpp @@ -42,14 +42,19 @@ namespace mongo { ParsedUpdate::ParsedUpdate(OperationContext* opCtx, const UpdateRequest* request, - const ExtensionsCallback& extensionsCallback) + const ExtensionsCallback& extensionsCallback, + bool forgoOpCounterIncrements) : _opCtx(opCtx), _request(request), _expCtx(make_intrusive<ExpressionContext>( opCtx, nullptr, _request->getNamespaceString(), _request->getRuntimeConstants())), _driver(_expCtx), _canonicalQuery(), - _extensionsCallback(extensionsCallback) {} + _extensionsCallback(extensionsCallback) { + if (forgoOpCounterIncrements) { + _expCtx->enabledCounters = false; + } +} Status ParsedUpdate::parseRequest() { // It is invalid to request that the UpdateStage return the prior or newly-updated version @@ -151,6 +156,7 @@ Status ParsedUpdate::parseQueryToCQ() { qr->setRuntimeConstants(*runtimeConstants); } + _expCtx->startExpressionCounters(); auto statusWithCQ = CanonicalQuery::canonicalize( _opCtx, std::move(qr), _expCtx, _extensionsCallback, allowedMatcherFeatures); if (statusWithCQ.isOK()) { diff --git a/src/mongo/db/ops/parsed_update.h b/src/mongo/db/ops/parsed_update.h index e9be9312389..7759dc1530f 100644 --- a/src/mongo/db/ops/parsed_update.h +++ b/src/mongo/db/ops/parsed_update.h @@ -76,7 +76,8 @@ public: */ ParsedUpdate(OperationContext* opCtx, const UpdateRequest* request, - const ExtensionsCallback& extensionsCallback); + const ExtensionsCallback& extensionsCallback, + bool forgoOpCounterIncrements = false); /** * Parses the update request to a canonical query and an update driver. On success, the diff --git a/src/mongo/db/ops/write_ops_exec.cpp b/src/mongo/db/ops/write_ops_exec.cpp index 3fef9f96e38..f36498e3c4f 100644 --- a/src/mongo/db/ops/write_ops_exec.cpp +++ b/src/mongo/db/ops/write_ops_exec.cpp @@ -645,9 +645,10 @@ WriteResult performInserts(OperationContext* opCtx, static SingleWriteResult performSingleUpdateOp(OperationContext* opCtx, const NamespaceString& ns, StmtId stmtId, - const UpdateRequest& updateRequest) { + const UpdateRequest& updateRequest, + bool forgoOpCounterIncrements) { const ExtensionsCallbackReal extensionsCallback(opCtx, &updateRequest.getNamespaceString()); - ParsedUpdate parsedUpdate(opCtx, &updateRequest, extensionsCallback); + ParsedUpdate parsedUpdate(opCtx, &updateRequest, extensionsCallback, forgoOpCounterIncrements); uassertStatusOK(parsedUpdate.parseRequest()); CurOpFailpointHelpers::waitWhileFailPointEnabled( @@ -744,7 +745,8 @@ static SingleWriteResult performSingleUpdateOpWithDupKeyRetry(OperationContext* const NamespaceString& ns, StmtId stmtId, const write_ops::UpdateOpEntry& op, - RuntimeConstants runtimeConstants) { + RuntimeConstants runtimeConstants, + bool forgoOpCounterIncrements) { globalOpCounters.gotUpdate(); ServerWriteConcernMetrics::get(opCtx)->recordWriteConcernForUpdate(opCtx->getWriteConcern()); auto& curOp = *CurOp::get(opCtx); @@ -782,7 +784,7 @@ static SingleWriteResult performSingleUpdateOpWithDupKeyRetry(OperationContext* ++numAttempts; try { - return performSingleUpdateOp(opCtx, ns, stmtId, request); + return performSingleUpdateOp(opCtx, ns, stmtId, request, forgoOpCounterIncrements); } catch (ExceptionFor<ErrorCodes::DuplicateKey>& ex) { const ExtensionsCallbackReal extensionsCallback(opCtx, &request.getNamespaceString()); ParsedUpdate parsedUpdate(opCtx, &request, extensionsCallback); @@ -833,6 +835,9 @@ WriteResult performUpdates(OperationContext* opCtx, const write_ops::Update& who const auto& runtimeConstants = wholeOp.getRuntimeConstants().value_or(Variables::generateRuntimeConstants(opCtx)); + // Increment operator counters only during the fisrt single update operation in a batch of + // updates. + bool forgoOpCounterIncrements = false; for (auto&& singleOp : wholeOp.getUpdates()) { const auto stmtId = getStmtIdForWriteOp(opCtx, wholeOp, stmtIdIndex++); if (opCtx->getTxnNumber()) { @@ -858,8 +863,14 @@ WriteResult performUpdates(OperationContext* opCtx, const write_ops::Update& who ON_BLOCK_EXIT([&] { finishCurOp(opCtx, &curOp); }); try { lastOpFixer.startingOp(); - out.results.emplace_back(performSingleUpdateOpWithDupKeyRetry( - opCtx, wholeOp.getNamespace(), stmtId, singleOp, runtimeConstants)); + out.results.emplace_back( + performSingleUpdateOpWithDupKeyRetry(opCtx, + wholeOp.getNamespace(), + stmtId, + singleOp, + runtimeConstants, + forgoOpCounterIncrements)); + forgoOpCounterIncrements = true; lastOpFixer.finishedOpSuccessfully(); } catch (const DBException& ex) { const bool canContinue = diff --git a/src/mongo/db/pipeline/document_source_lookup.cpp b/src/mongo/db/pipeline/document_source_lookup.cpp index 7eb2f9d7169..496c641b969 100644 --- a/src/mongo/db/pipeline/document_source_lookup.cpp +++ b/src/mongo/db/pipeline/document_source_lookup.cpp @@ -674,8 +674,10 @@ void DocumentSourceLookUp::resolveLetVariables(const Document& localDoc, Variabl void DocumentSourceLookUp::initializeResolvedIntrospectionPipeline() { copyVariablesToExpCtx(_variables, _variablesParseState, _fromExpCtx.get()); + _fromExpCtx->startExpressionCounters(); _resolvedIntrospectionPipeline = Pipeline::parse(_resolvedPipeline, _fromExpCtx, lookupPipeValidator); + _fromExpCtx->stopExpressionCounters(); } void DocumentSourceLookUp::serializeToArray( diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 7b53cd77eeb..c63c5fde288 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -46,6 +46,7 @@ #include "mongo/db/jsobj.h" #include "mongo/db/pipeline/expression_context.h" #include "mongo/db/query/datetime/date_time_support.h" +#include "mongo/db/stats/counters.h" #include "mongo/platform/bits.h" #include "mongo/platform/decimal128.h" #include "mongo/util/regex_util.h" @@ -123,6 +124,8 @@ void Expression::registerExpression( str::stream() << "Duplicate expression (" << key << ") registered.", op == parserMap.end()); parserMap[key] = {parser, requiredMinVersion}; + // Add this expression to the global map of operator counters for expressions. + operatorCountersAggExpressions.addAggExpressionCounter(key); } intrusive_ptr<Expression> Expression::parseExpression( @@ -155,6 +158,9 @@ intrusive_ptr<Expression> Expression::parseExpression( << " for more information.", !expCtx->maxFeatureCompatibilityVersion || !entry.requiredMinVersion || (*entry.requiredMinVersion <= *expCtx->maxFeatureCompatibilityVersion)); + + // Increment the counter for this expression in the current context. + expCtx->incrementAggExprCounter(opName); return entry.parser(expCtx, obj.firstElement(), vps); } diff --git a/src/mongo/db/pipeline/expression_context.cpp b/src/mongo/db/pipeline/expression_context.cpp index 5bfc56283f7..26c9854160f 100644 --- a/src/mongo/db/pipeline/expression_context.cpp +++ b/src/mongo/db/pipeline/expression_context.cpp @@ -215,21 +215,28 @@ intrusive_ptr<ExpressionContext> ExpressionContext::copyWith( } void ExpressionContext::startExpressionCounters() { - if (!_expressionCounters) { + if (enabledCounters && !_expressionCounters) { _expressionCounters = boost::make_optional<ExpressionCounters>({}); } } void ExpressionContext::incrementMatchExprCounter(StringData name) { - if (_expressionCounters) { + if (enabledCounters && _expressionCounters) { ++_expressionCounters.get().matchExprCountersMap[name]; } } +void ExpressionContext::incrementAggExprCounter(StringData name) { + if (enabledCounters && _expressionCounters) { + ++_expressionCounters.get().aggExprCountersMap[name]; + } +} + void ExpressionContext::stopExpressionCounters() { - if (_expressionCounters) { + if (enabledCounters && _expressionCounters) { operatorCountersMatchExpressions.mergeCounters( _expressionCounters.get().matchExprCountersMap); + operatorCountersAggExpressions.mergeCounters(_expressionCounters.get().aggExprCountersMap); } _expressionCounters = boost::none; } diff --git a/src/mongo/db/pipeline/expression_context.h b/src/mongo/db/pipeline/expression_context.h index 341bc4317cb..8ab69171792 100644 --- a/src/mongo/db/pipeline/expression_context.h +++ b/src/mongo/db/pipeline/expression_context.h @@ -110,6 +110,15 @@ public: }; /** + * The structure ExpressionCounters encapsulates counters for match, aggregate, and other + * expression types as seen in end-user queries. + */ + struct ExpressionCounters { + StringMap<uint64_t> aggExprCountersMap; + StringMap<uint64_t> matchExprCountersMap; + }; + + /** * Constructs an ExpressionContext to be used for Pipeline parsing and evaluation. * 'resolvedNamespaces' maps collection names (not full namespaces) to ResolvedNamespaces. */ @@ -316,11 +325,20 @@ public: void incrementMatchExprCounter(StringData name); /** + * Increment the counter for the aggregate expression with a given name. + */ + void incrementAggExprCounter(StringData name); + + /** * Merge expression counters from the current expression context into the global maps * and stop counting. */ void stopExpressionCounters(); + bool expressionCountersAreActive() { + return _expressionCounters.is_initialized(); + } + // The explain verbosity requested by the user, or boost::none if no explain was requested. boost::optional<ExplainOptions::Verbosity> explain; @@ -390,6 +408,29 @@ public: // True if this ExpressionContext is used to parse a collection validator expression. bool isParsingCollectionValidator = false; + // Indicates where there is any chance this operation will be profiled. Must be set at + // construction. + const bool mayDbProfile = true; + + // These fields can be used in a context when API version validations were not enforced during + // parse time (Example creating a view or validator), but needs to be enforce while querying + // later. + bool exprUnstableForApiV1 = false; + bool exprDeprectedForApiV1 = false; + + // Tracks whether the collator to use for the aggregation matches the default collation of the + // collection or view. For collectionless aggregates this is set to 'kNoDefaultCollation'. + enum class CollationMatchesDefault { kNoDefault, kYes, kNo }; + CollationMatchesDefault collationMatchesDefault = CollationMatchesDefault::kNoDefault; + + // When non-empty, contains the unmodified user provided aggregation command. + BSONObj originalAggregateCommand; + + // True if the expression context is the original one for a given pipeline. + // False if another context is created for the same pipeline. Used to disable duplicate + // expression counting. + bool enabledCounters = true; + protected: static const int kInterruptCheckPeriod = 128; diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp index c49c382b01f..4f036b2fc45 100644 --- a/src/mongo/db/query/canonical_query.cpp +++ b/src/mongo/db/query/canonical_query.cpp @@ -165,7 +165,7 @@ StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize( return statusWithMatcher.getStatus(); } - // Stop counting match expressions after they have been parsed to exclude expressions created + // Stop counting expressions after they have been parsed to exclude expressions created // during optimization and other processing steps. newExpCtx->stopExpressionCounters(); diff --git a/src/mongo/db/query/projection_ast.h b/src/mongo/db/query/projection_ast.h index 3f85c85c419..75af9f3b0ee 100644 --- a/src/mongo/db/query/projection_ast.h +++ b/src/mongo/db/query/projection_ast.h @@ -262,10 +262,17 @@ public: bob << "" << other._expr->serialize(false); // TODO SERVER-31003: add a clone() method to Expression. - boost::intrusive_ptr<Expression> clonedExpr = - Expression::parseOperand(other._expr->getExpressionContext(), - bob.obj().firstElement(), - other._expr->getExpressionContext()->variablesParseState); + // Temporary stop expression counters while processing the cloned expression. + auto otherCtx = other._expr->getExpressionContext(); + auto activeCounting = otherCtx->expressionCountersAreActive(); + if (activeCounting) { + otherCtx->enabledCounters = false; + } + boost::intrusive_ptr<Expression> clonedExpr = Expression::parseOperand( + otherCtx, bob.obj().firstElement(), otherCtx->variablesParseState); + if (activeCounting) { + otherCtx->enabledCounters = true; + } _expr = clonedExpr; } diff --git a/src/mongo/db/stats/counters.cpp b/src/mongo/db/stats/counters.cpp index 852440b366f..55b9cbeffb6 100644 --- a/src/mongo/db/stats/counters.cpp +++ b/src/mongo/db/stats/counters.cpp @@ -295,5 +295,6 @@ OpCounters replOpCounters; NetworkCounter networkCounter; AuthCounter authCounter; AggStageCounters aggStageCounters; +OperatorCountersAggExpressions operatorCountersAggExpressions; OperatorCountersMatchExpressions operatorCountersMatchExpressions; } // namespace mongo diff --git a/src/mongo/db/stats/counters.h b/src/mongo/db/stats/counters.h index 154131ffc30..aa11c84c2f1 100644 --- a/src/mongo/db/stats/counters.h +++ b/src/mongo/db/stats/counters.h @@ -252,6 +252,37 @@ public: extern AggStageCounters aggStageCounters; +class OperatorCountersAggExpressions { +private: + struct AggExprCounter { + AggExprCounter(StringData name) + : metric("operatorCounters.expressions." + name, &counter) {} + + Counter64 counter; + ServerStatusMetricField<Counter64> metric; + }; + +public: + void addAggExpressionCounter(StringData name) { + operatorCountersAggExpressionMap[name] = std::make_unique<AggExprCounter>(name); + } + + void mergeCounters(StringMap<uint64_t>& toMerge) { + for (auto&& [name, cnt] : toMerge) { + if (auto it = operatorCountersAggExpressionMap.find(name); + it != operatorCountersAggExpressionMap.end()) { + it->second->counter.increment(cnt); + } + } + } + +private: + // Map of aggregation expressions to the number of occurrences in aggregation pipelines. + StringMap<std::unique_ptr<AggExprCounter>> operatorCountersAggExpressionMap = {}; +}; + +extern OperatorCountersAggExpressions operatorCountersAggExpressions; + /** * Global counters for match expressions. */ diff --git a/src/mongo/db/update/pipeline_executor.cpp b/src/mongo/db/update/pipeline_executor.cpp index 241143af138..281679ad492 100644 --- a/src/mongo/db/update/pipeline_executor.cpp +++ b/src/mongo/db/update/pipeline_executor.cpp @@ -67,7 +67,9 @@ PipelineExecutor::PipelineExecutor(const boost::intrusive_ptr<ExpressionContext> } _expCtx->setResolvedNamespaces(resolvedNamespaces); + _expCtx->startExpressionCounters(); _pipeline = Pipeline::parse(pipeline, _expCtx); + _expCtx->stopExpressionCounters(); // Validate the update pipeline. for (auto&& stage : _pipeline->getSources()) { |