summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimour Katchaounov <timour.katchaounov@mongodb.com>2022-02-23 11:22:10 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-02-23 11:49:56 +0000
commitad12685ff33131f95053dc567639312999957475 (patch)
treec6416426d98f6a51f3b503dbaa8c201960b657e7
parentd9cbe79974a9dd0596c72cd4033fc9d8ceb1eb5b (diff)
downloadmongo-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.js24
-rw-r--r--jstests/noPassthrough/operator_counters_expressions.js179
-rw-r--r--src/mongo/db/catalog/collection_impl.cpp2
-rw-r--r--src/mongo/db/ops/parsed_delete.cpp1
-rw-r--r--src/mongo/db/ops/parsed_update.cpp10
-rw-r--r--src/mongo/db/ops/parsed_update.h3
-rw-r--r--src/mongo/db/ops/write_ops_exec.cpp23
-rw-r--r--src/mongo/db/pipeline/document_source_lookup.cpp2
-rw-r--r--src/mongo/db/pipeline/expression.cpp6
-rw-r--r--src/mongo/db/pipeline/expression_context.cpp13
-rw-r--r--src/mongo/db/pipeline/expression_context.h41
-rw-r--r--src/mongo/db/query/canonical_query.cpp2
-rw-r--r--src/mongo/db/query/projection_ast.h15
-rw-r--r--src/mongo/db/stats/counters.cpp1
-rw-r--r--src/mongo/db/stats/counters.h31
-rw-r--r--src/mongo/db/update/pipeline_executor.cpp2
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()) {