summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMihai Andrei <mihai.andrei@10gen.com>2020-06-30 09:39:19 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-07-15 21:54:42 +0000
commiteaaee39e2c4eaf9938c5a75bce30648435ae10cc (patch)
tree07a9d7840070233da4ce2cb45d5fdc6ce98fa398
parentb52de8036887396dec91c32b531790e7b1fe01cc (diff)
downloadmongo-eaaee39e2c4eaf9938c5a75bce30648435ae10cc.tar.gz
SERVER-49022 Implement validation error generation for comparison query operators
-rw-r--r--src/mongo/db/matcher/SConscript1
-rw-r--r--src/mongo/db/matcher/doc_validation_error.cpp147
-rw-r--r--src/mongo/db/matcher/doc_validation_error_test.cpp389
-rw-r--r--src/mongo/db/matcher/expression.cpp3
-rw-r--r--src/mongo/db/matcher/expression.h55
-rw-r--r--src/mongo/db/matcher/expression_leaf.cpp18
-rw-r--r--src/mongo/db/matcher/expression_leaf.h63
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp141
-rw-r--r--src/mongo/db/matcher/expression_path.h5
-rw-r--r--src/mongo/db/matcher/expression_tree.h6
10 files changed, 754 insertions, 74 deletions
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript
index 471dd951305..7ef7f899c9a 100644
--- a/src/mongo/db/matcher/SConscript
+++ b/src/mongo/db/matcher/SConscript
@@ -97,6 +97,7 @@ env.Library(
env.CppUnitTest(
target='db_matcher_test',
source=[
+ 'doc_validation_error_test.cpp',
'expression_algo_test.cpp',
'expression_always_boolean_test.cpp',
'expression_array_test.cpp',
diff --git a/src/mongo/db/matcher/doc_validation_error.cpp b/src/mongo/db/matcher/doc_validation_error.cpp
index 4018443dbd4..4f19849af73 100644
--- a/src/mongo/db/matcher/doc_validation_error.cpp
+++ b/src/mongo/db/matcher/doc_validation_error.cpp
@@ -32,6 +32,8 @@
#include "mongo/base/init.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/db/matcher/doc_validation_error.h"
+#include "mongo/db/matcher/expression_leaf.h"
+#include "mongo/db/matcher/expression_tree.h"
#include "mongo/db/matcher/expression_visitor.h"
#include "mongo/db/matcher/match_expression_walker.h"
@@ -51,7 +53,41 @@ enum class InvertError { kNormal, kInverted };
/**
* A struct which tracks context during error generation.
*/
-struct ValidationErrorContext {};
+struct ValidationErrorContext {
+ ValidationErrorContext(const MatchableDocument* doc) : doc(doc) {}
+
+ /**
+ * Returns the complete document validation error as a BSONObj once error generation has
+ * finished.
+ */
+ BSONObj done() {
+ // When error generation is finished, there must be exactly one BSONObjBuilder which
+ // contains the complete error.
+ return objBuilder.obj();
+ }
+
+ BSONObjBuilder& getCurrentObjBuilder() {
+ return objBuilder;
+ }
+
+ /**
+ * Sets 'inversion' to the opposite of its current value.
+ */
+ void flipInversion() {
+ inversion =
+ inversion == InvertError::kNormal ? InvertError::kInverted : InvertError::kNormal;
+ }
+
+ // BSONObjBuilder which is used to construct the generated error.
+ BSONObjBuilder objBuilder;
+ // Document which failed to match against the collection's validator.
+ const MatchableDocument* doc;
+ // Tracks whether the generated error should be described normally or in an inverted context.
+ InvertError inversion = InvertError::kNormal;
+};
+
+using ErrorAnnotation = MatchExpression::ErrorAnnotation;
+using Mode = ErrorAnnotation::Mode;
/**
* Visitor which is primarily responsible for error generation.
@@ -68,16 +104,26 @@ public:
void visit(const BitsAnySetMatchExpression* expr) final {}
void visit(const ElemMatchObjectMatchExpression* expr) final {}
void visit(const ElemMatchValueMatchExpression* expr) final {}
- void visit(const EqualityMatchExpression* expr) final {}
+ void visit(const EqualityMatchExpression* expr) final {
+ generateComparisonError(expr);
+ }
void visit(const ExistsMatchExpression* expr) final {}
void visit(const ExprMatchExpression* expr) final {}
- void visit(const GTEMatchExpression* expr) final {}
- void visit(const GTMatchExpression* expr) final {}
+ void visit(const GTEMatchExpression* expr) final {
+ generateComparisonError(expr);
+ }
+ void visit(const GTMatchExpression* expr) final {
+ generateComparisonError(expr);
+ }
void visit(const GeoMatchExpression* expr) final {}
void visit(const GeoNearMatchExpression* expr) final {
MONGO_UNREACHABLE;
}
- void visit(const InMatchExpression* expr) final {}
+ void visit(const InMatchExpression* expr) final {
+ static constexpr auto kNormalReason = "no matching value found in array";
+ static constexpr auto kInvertedReason = "matching value found in array";
+ generateLeafError(expr, kNormalReason, kInvertedReason);
+ }
void visit(const InternalExprEqMatchExpression* expr) final {}
void visit(const InternalSchemaAllElemMatchFromIndexMatchExpression* expr) final {}
void visit(const InternalSchemaAllowedPropertiesMatchExpression* expr) final {}
@@ -98,11 +144,17 @@ public:
void visit(const InternalSchemaTypeExpression* expr) final {}
void visit(const InternalSchemaUniqueItemsMatchExpression* expr) final {}
void visit(const InternalSchemaXorMatchExpression* expr) final {}
- void visit(const LTEMatchExpression* expr) final {}
- void visit(const LTMatchExpression* expr) final {}
+ void visit(const LTEMatchExpression* expr) final {
+ generateComparisonError(expr);
+ }
+ void visit(const LTMatchExpression* expr) final {
+ generateComparisonError(expr);
+ }
void visit(const ModMatchExpression* expr) final {}
void visit(const NorMatchExpression* expr) final {}
- void visit(const NotMatchExpression* expr) final {}
+ void visit(const NotMatchExpression* expr) final {
+ _context->flipInversion();
+ }
void visit(const OrMatchExpression* expr) final {}
void visit(const RegexMatchExpression* expr) final {}
void visit(const SizeMatchExpression* expr) final {}
@@ -122,6 +174,75 @@ public:
}
private:
+ // Set of utilities responsible for appending various fields to build a descriptive error.
+ void appendOperatorName(const ErrorAnnotation& annotation, BSONObjBuilder* bob) {
+ bob->append("operatorName", annotation.operatorName);
+ }
+ void appendSpecifiedAs(const ErrorAnnotation& annotation, BSONObjBuilder* bob) {
+ bob->append("specifiedAs", annotation.annotation);
+ }
+
+ /**
+ * Given a pointer to a LeafMatchExpression 'expr', appends details to the current
+ * BSONObjBuilder tracked by '_context' describing why the document failed to match against
+ * 'expr'. In particular:
+ * - Appends "reason: field was missing" if expr's path is missing from the document.
+ * - Appends the specified 'reason' along with 'consideredValue' if the 'path' in the
+ * document resolves to a single value.
+ * - Appends the specified 'reason' along with 'consideredValues' if the 'path' in the
+ * document resolves to an array of values that is implicitly traversed by 'expr'.
+ */
+ void appendLeafErrorDetails(const LeafMatchExpression* expr, const std::string& reason) {
+ BSONObjBuilder* bob = &(_context->getCurrentObjBuilder());
+ ElementPath path(expr->path());
+ MatchableDocument::IteratorHolder cursor(_context->doc, &path);
+ BSONArrayBuilder bab;
+ while (cursor->more()) {
+ auto elem = cursor->next().element();
+ if (elem.eoo()) {
+ break;
+ } else {
+ bab.append(elem);
+ }
+ }
+ auto size = bab.arrSize();
+ if (size == 0) {
+ bob->append("reason", "field was missing");
+ } else {
+ bob->append("reason", reason);
+ auto arr = bab.arr();
+ if (size == 1) {
+ bob->appendAs(arr[0], "consideredValue");
+ } else {
+ bob->append("consideredValues", arr);
+ }
+ }
+ }
+
+ void generateLeafError(const LeafMatchExpression* expr,
+ const std::string& normalReason,
+ const std::string& invertedReason) {
+ if (auto annotationPtr = expr->getErrorAnnotation()) {
+ const auto& annotation = *annotationPtr;
+ BSONObjBuilder& bob = _context->getCurrentObjBuilder();
+ if (annotation.mode == Mode::kGenerateError) {
+ appendOperatorName(annotation, &bob);
+ appendSpecifiedAs(annotation, &bob);
+ if (_context->inversion == InvertError::kNormal) {
+ appendLeafErrorDetails(expr, normalReason);
+ } else {
+ appendLeafErrorDetails(expr, invertedReason);
+ }
+ }
+ }
+ }
+
+ void generateComparisonError(const ComparisonMatchExpression* expr) {
+ static constexpr auto normalReason = "comparison failed";
+ static constexpr auto invertedReason = "comparison succeeded";
+ generateLeafError(expr, normalReason, invertedReason);
+ }
+
ValidationErrorContext* _context;
};
@@ -246,7 +367,9 @@ public:
void visit(const LTMatchExpression* expr) final {}
void visit(const ModMatchExpression* expr) final {}
void visit(const NorMatchExpression* expr) final {}
- void visit(const NotMatchExpression* expr) final {}
+ void visit(const NotMatchExpression* expr) final {
+ _context->flipInversion();
+ }
void visit(const OrMatchExpression* expr) final {}
void visit(const RegexMatchExpression* expr) final {}
void visit(const SizeMatchExpression* expr) final {}
@@ -287,13 +410,13 @@ const BSONObj& DocumentValidationFailureInfo::getDetails() const {
}
BSONObj generateError(const MatchExpression& validatorExpr, const BSONObj& doc) {
BSONMatchableDocument matchableDoc(doc);
- ValidationErrorContext context;
+ ValidationErrorContext context(&matchableDoc);
ValidationErrorPreVisitor preVisitor{&context};
ValidationErrorInVisitor inVisitor{&context};
ValidationErrorPostVisitor postVisitor{&context};
MatchExpressionWalker walker{&preVisitor, &inVisitor, &postVisitor};
tree_walker::walk<true, MatchExpression>(&validatorExpr, &walker);
- return BSONObj();
+ return context.done();
}
-} // namespace mongo::doc_validation_error
+} // namespace mongo::doc_validation_error \ No newline at end of file
diff --git a/src/mongo/db/matcher/doc_validation_error_test.cpp b/src/mongo/db/matcher/doc_validation_error_test.cpp
new file mode 100644
index 00000000000..cf2199d8529
--- /dev/null
+++ b/src/mongo/db/matcher/doc_validation_error_test.cpp
@@ -0,0 +1,389 @@
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/matcher/doc_validation_error.h"
+#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+/**
+ * Utility function which parses a MatchExpression from 'query' and verifies that the error
+ * generated by the parsed MatchExpression and 'document' matches 'expectedError'.
+ */
+void verifyGeneratedError(BSONObj query, BSONObj document, BSONObj expectedError) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ expCtx->isParsingCollectionValidator = true;
+ StatusWithMatchExpression result = MatchExpressionParser::parse(query, expCtx);
+ ASSERT_OK(result.getStatus());
+ MatchExpression* expr = result.getValue().get();
+ BSONObj generatedError = doc_validation_error::generateError(*expr, document);
+ ASSERT_BSONOBJ_EQ(generatedError, expectedError);
+}
+
+// Comparison operators.
+// $eq
+TEST(ComparisonMatchExpression, BasicEq) {
+ BSONObj query = BSON("a" << BSON("$eq" << 2));
+ BSONObj document = BSON("a" << 1);
+ BSONObj expectedError = BSON("operatorName"
+ << "$eq"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValue" << 1);
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, EqMissingPath) {
+ BSONObj query = BSON("a" << BSON("$eq" << 2));
+ BSONObj document = BSON("b" << 1);
+ BSONObj expectedError = BSON("operatorName"
+ << "$eq"
+ << "specifiedAs" << query << "reason"
+ << "field was missing");
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, EqImplicitArrayTraversal) {
+ BSONObj query = BSON("a" << BSON("$eq" << 2));
+ BSONObj document = BSON("a" << BSON_ARRAY(3 << 4 << 5));
+ BSONObj expectedError = BSON("operatorName"
+ << "$eq"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValues"
+ << BSON_ARRAY(3 << 4 << 5 << BSON_ARRAY(3 << 4 << 5)));
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedDocumentSingleElement) {
+ BSONObj query = BSON("a.b" << BSON("$eq" << 2));
+ BSONObj document = BSON("a" << BSON_ARRAY(BSON("b" << 3)));
+ BSONObj expectedError = BSON("operatorName"
+ << "$eq"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValue" << 3);
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedDocument) {
+ BSONObj query = BSON("a.b" << BSON("$eq" << 2));
+ BSONObj document = BSON("a" << BSON_ARRAY(BSON("b" << 3) << BSON("b" << 4) << BSON("b" << 5)));
+ BSONObj expectedError = BSON("operatorName"
+ << "$eq"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValues" << BSON_ARRAY(3 << 4 << 5));
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, EqImplicitArrayTraversalNestedArrays) {
+ BSONObj query = BSON("a.b" << BSON("$eq" << 2));
+ BSONObj document =
+ BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(1 << 2)) << BSON("b" << BSON_ARRAY(3 << 4))));
+ BSONObj expectedError = BSON("operatorName"
+ << "$eq"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValues"
+ << BSON_ARRAY(1 << 2 << BSON_ARRAY(1 << 2) << 3 << 4
+ << BSON_ARRAY(3 << 4)));
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, EqNoOperator) {
+ BSONObj query = BSON("a" << 2);
+ BSONObj document = BSON("a" << 1);
+ BSONObj expectedError = BSON("operatorName"
+ << "$eq"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValue" << 1);
+ verifyGeneratedError(query, document, expectedError);
+}
+
+// $ne
+TEST(ComparisonMatchExpression, BasicNe) {
+ BSONObj query = BSON("a" << BSON("$ne" << 2));
+ BSONObj document = BSON("a" << 2);
+ BSONObj expectedError = BSON("operatorName"
+ << "$ne"
+ << "specifiedAs" << query << "reason"
+ << "comparison succeeded"
+ << "consideredValue" << 2);
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, NeImplicitArrayTraversal) {
+ BSONObj query = BSON("a" << BSON("$ne" << 2));
+ BSONObj document = BSON("a" << BSON_ARRAY(1 << 2 << 3));
+ BSONObj expectedError = BSON("operatorName"
+ << "$ne"
+ << "specifiedAs" << query << "reason"
+ << "comparison succeeded"
+ << "consideredValues"
+ << BSON_ARRAY(1 << 2 << 3 << BSON_ARRAY(1 << 2 << 3)));
+ verifyGeneratedError(query, document, expectedError);
+}
+
+// $lt
+TEST(ComparisonMatchExpression, BasicLt) {
+ BSONObj query = BSON("a" << BSON("$lt" << 0));
+ BSONObj document = BSON("a" << 1);
+ BSONObj expectedError = BSON("operatorName"
+ << "$lt"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValue" << 1);
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, LtMissingPath) {
+ BSONObj query = BSON("a" << BSON("$lt" << 0));
+ BSONObj document = BSON("b" << 1);
+ BSONObj expectedError = BSON("operatorName"
+ << "$lt"
+ << "specifiedAs" << query << "reason"
+ << "field was missing");
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, LtImplicitArrayTraversal) {
+ BSONObj query = BSON("a" << BSON("$lt" << 0));
+ BSONObj document = BSON("a" << BSON_ARRAY(3 << 4 << 5));
+ BSONObj expectedError = BSON("operatorName"
+ << "$lt"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValues"
+ << BSON_ARRAY(3 << 4 << 5 << BSON_ARRAY(3 << 4 << 5)));
+ verifyGeneratedError(query, document, expectedError);
+}
+
+// $lte
+TEST(ComparisonMatchExpression, BasicLte) {
+ BSONObj query = BSON("a" << BSON("$lte" << 0));
+ BSONObj document = BSON("a" << 1);
+ BSONObj expectedError = BSON("operatorName"
+ << "$lte"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValue" << 1);
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, LteMissingPath) {
+ BSONObj query = BSON("a" << BSON("$lte" << 0));
+ BSONObj document = BSON("b" << 1);
+ BSONObj expectedError = BSON("operatorName"
+ << "$lte"
+ << "specifiedAs" << query << "reason"
+ << "field was missing");
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, LteImplicitArrayTraversal) {
+ BSONObj query = BSON("a" << BSON("$lte" << 0));
+ BSONObj document = BSON("a" << BSON_ARRAY(3 << 4 << 5));
+ BSONObj expectedError = BSON("operatorName"
+ << "$lte"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValues"
+ << BSON_ARRAY(3 << 4 << 5 << BSON_ARRAY(3 << 4 << 5)));
+ verifyGeneratedError(query, document, expectedError);
+}
+
+// $gt
+TEST(ComparisonMatchExpression, BasicGt) {
+ BSONObj query = BSON("a" << BSON("$gt" << 3));
+ BSONObj document = BSON("a" << 0);
+ BSONObj expectedError = BSON("operatorName"
+ << "$gt"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValue" << 0);
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, GtMissingPath) {
+ BSONObj query = BSON("a" << BSON("$gt" << 3));
+ BSONObj document = BSON("b" << 1);
+ BSONObj expectedError = BSON("operatorName"
+ << "$gt"
+ << "specifiedAs" << query << "reason"
+ << "field was missing");
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, GtImplicitArrayTraversal) {
+ BSONObj query = BSON("a" << BSON("$gt" << 3));
+ BSONObj document = BSON("a" << BSON_ARRAY(0 << 1 << 2));
+ BSONObj expectedError = BSON("operatorName"
+ << "$gt"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValues"
+ << BSON_ARRAY(0 << 1 << 2 << BSON_ARRAY(0 << 1 << 2)));
+ verifyGeneratedError(query, document, expectedError);
+}
+
+// $gte
+TEST(ComparisonMatchExpression, BasicGte) {
+ BSONObj query = BSON("a" << BSON("$gte" << 3));
+ BSONObj document = BSON("a" << 0);
+ BSONObj expectedError = BSON("operatorName"
+ << "$gte"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValue" << 0);
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, GteMissingPath) {
+ BSONObj query = BSON("a" << BSON("$gte" << 3));
+ BSONObj document = BSON("b" << 1);
+ BSONObj expectedError = BSON("operatorName"
+ << "$gte"
+ << "specifiedAs" << query << "reason"
+ << "field was missing");
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, GteImplicitArrayTraversal) {
+ BSONObj query = BSON("a" << BSON("$gte" << 3));
+ BSONObj document = BSON("a" << BSON_ARRAY(0 << 1 << 2));
+ BSONObj expectedError = BSON("operatorName"
+ << "$gte"
+ << "specifiedAs" << query << "reason"
+ << "comparison failed"
+ << "consideredValues"
+ << BSON_ARRAY(0 << 1 << 2 << BSON_ARRAY(0 << 1 << 2)));
+ verifyGeneratedError(query, document, expectedError);
+}
+
+// $in
+TEST(ComparisonMatchExpression, BasicIn) {
+ BSONObj query = BSON("a" << BSON("$in" << BSON_ARRAY(1 << 2 << 3)));
+ BSONObj document = BSON("a" << 4);
+ BSONObj expectedError = BSON("operatorName"
+ << "$in"
+ << "specifiedAs" << query << "reason"
+ << "no matching value found in array"
+ << "consideredValue" << 4);
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, InMissingPath) {
+ BSONObj query = BSON("a" << BSON("$in" << BSON_ARRAY(1 << 2 << 3)));
+ BSONObj document = BSON("b" << 1);
+ BSONObj expectedError = BSON("operatorName"
+ << "$in"
+ << "specifiedAs" << query << "reason"
+ << "field was missing");
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, InNestedDocumentsAndArrays) {
+ BSONObj query =
+ BSON("a.b" << BSON("$in" << BSON_ARRAY(5 << 6 << 7 << BSON_ARRAY(2 << 3 << 4))));
+ BSONObj document =
+ BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(1 << 2)) << BSON("b" << BSON_ARRAY(3 << 4))));
+ BSONObj expectedError = BSON("operatorName"
+ << "$in"
+ << "specifiedAs" << query << "reason"
+ << "no matching value found in array"
+ << "consideredValues"
+ << BSON_ARRAY(1 << 2 << BSON_ARRAY(1 << 2) << 3 << 4
+ << BSON_ARRAY(3 << 4)));
+ verifyGeneratedError(query, document, expectedError);
+}
+
+// $nin
+TEST(ComparisonMatchExpression, BasicNin) {
+ BSONObj query = BSON("a" << BSON("$nin" << BSON_ARRAY(1 << 2 << 3)));
+ BSONObj document = BSON("a" << 3);
+ BSONObj expectedError = BSON("operatorName"
+ << "$nin"
+ << "specifiedAs" << query << "reason"
+ << "matching value found in array"
+ << "consideredValue" << 3);
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, NinNestedDocumentsAndArrays) {
+ BSONObj query = BSON("a.b" << BSON("$nin" << BSON_ARRAY(1 << BSON_ARRAY(2 << 3 << 4))));
+ BSONObj document =
+ BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(1 << 2)) << BSON("b" << BSON_ARRAY(3 << 4))));
+ BSONObj expectedError = BSON("operatorName"
+ << "$nin"
+ << "specifiedAs" << query << "reason"
+ << "matching value found in array"
+ << "consideredValues"
+ << BSON_ARRAY(1 << 2 << BSON_ARRAY(1 << 2) << 3 << 4
+ << BSON_ARRAY(3 << 4)));
+ verifyGeneratedError(query, document, expectedError);
+}
+
+// Verify that Comparison operators which accept a regex ($in and $nin) work as expected.
+TEST(ComparisonMatchExpression, InAcceptsRegex) {
+ BSONObj query = BSON("a" << BSON("$in" << BSON_ARRAY("/^v/"
+ << "/^b/"
+ << "/^c/")));
+ BSONObj document = BSON("a"
+ << "Validation");
+ BSONObj expectedError = BSON("operatorName"
+ << "$in"
+ << "specifiedAs" << query << "reason"
+ << "no matching value found in array"
+ << "consideredValue"
+ << "Validation");
+ verifyGeneratedError(query, document, expectedError);
+}
+
+TEST(ComparisonMatchExpression, NinAcceptsRegex) {
+ BSONObj query = BSON("a" << BSON("$nin" << BSON_ARRAY("/^v/"
+ << "/^b/"
+ << "/^c/")));
+ BSONObj document = BSON("a"
+ << "berry");
+ BSONObj expectedError = BSON("operatorName"
+ << "$nin"
+ << "specifiedAs" << query << "reason"
+ << "matching value found in array"
+ << "consideredValue"
+ << "berry");
+ verifyGeneratedError(query, document, expectedError);
+}
+} // namespace
+} // namespace mongo \ No newline at end of file
diff --git a/src/mongo/db/matcher/expression.cpp b/src/mongo/db/matcher/expression.cpp
index 680b75a8001..5c7908ff70f 100644
--- a/src/mongo/db/matcher/expression.cpp
+++ b/src/mongo/db/matcher/expression.cpp
@@ -97,7 +97,8 @@ bool matchExpressionLessThan(const MatchExpression* lhs, const MatchExpression*
} // namespace
-MatchExpression::MatchExpression(MatchType type) : _matchType(type) {}
+MatchExpression::MatchExpression(MatchType type, clonable_ptr<ErrorAnnotation> annotation)
+ : _errorAnnotation(std::move(annotation)), _matchType(type) {}
// static
void MatchExpression::sortTree(MatchExpression* tree) {
diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h
index e84e3ee0982..3207f08d5bf 100644
--- a/src/mongo/db/matcher/expression.h
+++ b/src/mongo/db/matcher/expression.h
@@ -33,6 +33,7 @@
#include <functional>
#include <memory>
+#include "mongo/base/clonable_ptr.h"
#include "mongo/base/status.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
@@ -203,6 +204,51 @@ public:
using ConstIterator = MatchExpressionIterator<true>;
/**
+ * Tracks the information needed to generate a document validation error for a
+ * MatchExpression node.
+ */
+ struct ErrorAnnotation {
+ /**
+ * Enumerated type describing what action to take when generating errors for document
+ * validation failures.
+ */
+ enum class Mode {
+ // Do not generate an error for this MatchExpression or any of its children.
+ kIgnore,
+ // Do not generate an error for this MatchExpression, but iterate over any children
+ // as they may provide useful information. This is particularly useful for translated
+ // jsonSchema keywords.
+ kIgnoreButDescend,
+ // Generate an error message.
+ kGenerateError,
+ };
+
+ /**
+ * Constructs an annotation for a MatchExpression which does not contribute to error output.
+ */
+ ErrorAnnotation(Mode mode) : operatorName(""), annotation(BSONObj()), mode(mode) {
+ invariant(mode != Mode::kGenerateError);
+ }
+
+ /**
+ * Constructs a complete annotation for a MatchExpression which contributes to error output.
+ */
+ ErrorAnnotation(std::string operatorName, BSONObj annotation)
+ : operatorName(std::move(operatorName)),
+ annotation(annotation.getOwned()),
+ mode(Mode::kGenerateError) {}
+
+ std::unique_ptr<ErrorAnnotation> clone() const {
+ return std::make_unique<ErrorAnnotation>(*this);
+ }
+
+ const std::string operatorName;
+ // Tracks the original expression as specified by the user.
+ const BSONObj annotation;
+ const Mode mode;
+ };
+
+ /**
* Make simplifying changes to the structure of a MatchExpression tree without altering its
* semantics. This function may return:
* - a pointer to the original, unmodified MatchExpression,
@@ -243,7 +289,7 @@ public:
return tree;
}
- MatchExpression(MatchType type);
+ MatchExpression(MatchType type, clonable_ptr<ErrorAnnotation> annotation = nullptr);
virtual ~MatchExpression() {}
//
@@ -411,6 +457,11 @@ public:
virtual void acceptVisitor(MatchExpressionMutableVisitor* visitor) = 0;
virtual void acceptVisitor(MatchExpressionConstVisitor* visitor) const = 0;
+ // Returns nullptr if this MatchExpression node has no annotation.
+ ErrorAnnotation* getErrorAnnotation() const {
+ return _errorAnnotation.get();
+ }
+
//
// Debug information
//
@@ -449,6 +500,8 @@ protected:
void _debugAddSpace(StringBuilder& debug, int indentationLevel) const;
+ clonable_ptr<ErrorAnnotation> _errorAnnotation;
+
private:
/**
* Subclasses should implement this function to provide an ExpressionOptimizerFunc specific to
diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp
index 18f850ac1e3..d73a611963b 100644
--- a/src/mongo/db/matcher/expression_leaf.cpp
+++ b/src/mongo/db/matcher/expression_leaf.cpp
@@ -54,8 +54,10 @@ ComparisonMatchExpressionBase::ComparisonMatchExpressionBase(
StringData path,
const BSONElement& rhs,
ElementPath::LeafArrayBehavior leafArrBehavior,
- ElementPath::NonLeafArrayBehavior nonLeafArrBehavior)
- : LeafMatchExpression(type, path, leafArrBehavior, nonLeafArrBehavior), _rhs(rhs) {
+ ElementPath::NonLeafArrayBehavior nonLeafArrBehavior,
+ clonable_ptr<ErrorAnnotation> annotation)
+ : LeafMatchExpression(type, path, leafArrBehavior, nonLeafArrBehavior, std::move(annotation)),
+ _rhs(rhs) {
invariant(_rhs);
}
@@ -93,12 +95,14 @@ BSONObj ComparisonMatchExpressionBase::getSerializedRightHandSide() const {
ComparisonMatchExpression::ComparisonMatchExpression(MatchType type,
StringData path,
- const BSONElement& rhs)
+ const BSONElement& rhs,
+ clonable_ptr<ErrorAnnotation> annotation)
: ComparisonMatchExpressionBase(type,
path,
rhs,
ElementPath::LeafArrayBehavior::kTraverse,
- ElementPath::NonLeafArrayBehavior::kTraverse) {
+ ElementPath::NonLeafArrayBehavior::kTraverse,
+ std::move(annotation)) {
uassert(
ErrorCodes::BadValue, "cannot compare to undefined", _rhs.type() != BSONType::Undefined);
@@ -368,12 +372,12 @@ bool ExistsMatchExpression::equivalent(const MatchExpression* other) const {
// ----
-InMatchExpression::InMatchExpression(StringData path)
- : LeafMatchExpression(MATCH_IN, path),
+InMatchExpression::InMatchExpression(StringData path, clonable_ptr<ErrorAnnotation> annotation)
+ : LeafMatchExpression(MATCH_IN, path, std::move(annotation)),
_eltCmp(BSONElementComparator::FieldNamesMode::kIgnore, _collator) {}
std::unique_ptr<MatchExpression> InMatchExpression::shallowClone() const {
- auto next = std::make_unique<InMatchExpression>(path());
+ auto next = std::make_unique<InMatchExpression>(path(), _errorAnnotation);
next->setCollator(_collator);
if (getTag()) {
next->setTag(getTag()->clone());
diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h
index 1cb2b77e545..8f2ac98ffa5 100644
--- a/src/mongo/db/matcher/expression_leaf.h
+++ b/src/mongo/db/matcher/expression_leaf.h
@@ -50,17 +50,22 @@ class CollatorInterface;
class LeafMatchExpression : public PathMatchExpression {
public:
- LeafMatchExpression(MatchType matchType, StringData path)
+ LeafMatchExpression(MatchType matchType,
+ StringData path,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr)
: LeafMatchExpression(matchType,
path,
ElementPath::LeafArrayBehavior::kTraverse,
- ElementPath::NonLeafArrayBehavior::kTraverse) {}
+ ElementPath::NonLeafArrayBehavior::kTraverse,
+ std::move(annotation)) {}
LeafMatchExpression(MatchType matchType,
StringData path,
ElementPath::LeafArrayBehavior leafArrBehavior,
- ElementPath::NonLeafArrayBehavior nonLeafArrBehavior)
- : PathMatchExpression(matchType, path, leafArrBehavior, nonLeafArrBehavior) {}
+ ElementPath::NonLeafArrayBehavior nonLeafArrBehavior,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr)
+ : PathMatchExpression(
+ matchType, path, leafArrBehavior, nonLeafArrBehavior, std::move(annotation)) {}
virtual ~LeafMatchExpression() = default;
@@ -102,7 +107,8 @@ public:
StringData path,
const BSONElement& rhs,
ElementPath::LeafArrayBehavior,
- ElementPath::NonLeafArrayBehavior);
+ ElementPath::NonLeafArrayBehavior,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr);
virtual ~ComparisonMatchExpressionBase() = default;
@@ -173,7 +179,10 @@ public:
}
}
- ComparisonMatchExpression(MatchType type, StringData path, const BSONElement& rhs);
+ ComparisonMatchExpression(MatchType type,
+ StringData path,
+ const BSONElement& rhs,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr);
virtual ~ComparisonMatchExpression() = default;
@@ -184,8 +193,10 @@ class EqualityMatchExpression final : public ComparisonMatchExpression {
public:
static constexpr StringData kName = "$eq"_sd;
- EqualityMatchExpression(StringData path, const BSONElement& rhs)
- : ComparisonMatchExpression(EQ, path, rhs) {}
+ EqualityMatchExpression(StringData path,
+ const BSONElement& rhs,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr)
+ : ComparisonMatchExpression(EQ, path, rhs, std::move(annotation)) {}
StringData name() const final {
return kName;
@@ -193,7 +204,7 @@ public:
virtual std::unique_ptr<MatchExpression> shallowClone() const {
std::unique_ptr<ComparisonMatchExpression> e =
- std::make_unique<EqualityMatchExpression>(path(), _rhs);
+ std::make_unique<EqualityMatchExpression>(path(), _rhs, _errorAnnotation);
if (getTag()) {
e->setTag(getTag()->clone());
}
@@ -214,8 +225,10 @@ class LTEMatchExpression final : public ComparisonMatchExpression {
public:
static constexpr StringData kName = "$lte"_sd;
- LTEMatchExpression(StringData path, const BSONElement& rhs)
- : ComparisonMatchExpression(LTE, path, rhs) {}
+ LTEMatchExpression(StringData path,
+ const BSONElement& rhs,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr)
+ : ComparisonMatchExpression(LTE, path, rhs, std::move(annotation)) {}
StringData name() const final {
return kName;
@@ -223,7 +236,7 @@ public:
virtual std::unique_ptr<MatchExpression> shallowClone() const {
std::unique_ptr<ComparisonMatchExpression> e =
- std::make_unique<LTEMatchExpression>(path(), _rhs);
+ std::make_unique<LTEMatchExpression>(path(), _rhs, _errorAnnotation);
if (getTag()) {
e->setTag(getTag()->clone());
}
@@ -244,8 +257,10 @@ class LTMatchExpression final : public ComparisonMatchExpression {
public:
static constexpr StringData kName = "$lt"_sd;
- LTMatchExpression(StringData path, const BSONElement& rhs)
- : ComparisonMatchExpression(LT, path, rhs) {}
+ LTMatchExpression(StringData path,
+ const BSONElement& rhs,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr)
+ : ComparisonMatchExpression(LT, path, rhs, std::move(annotation)) {}
StringData name() const final {
return kName;
@@ -253,7 +268,7 @@ public:
virtual std::unique_ptr<MatchExpression> shallowClone() const {
std::unique_ptr<ComparisonMatchExpression> e =
- std::make_unique<LTMatchExpression>(path(), _rhs);
+ std::make_unique<LTMatchExpression>(path(), _rhs, _errorAnnotation);
if (getTag()) {
e->setTag(getTag()->clone());
}
@@ -274,8 +289,10 @@ class GTMatchExpression final : public ComparisonMatchExpression {
public:
static constexpr StringData kName = "$gt"_sd;
- GTMatchExpression(StringData path, const BSONElement& rhs)
- : ComparisonMatchExpression(GT, path, rhs) {}
+ GTMatchExpression(StringData path,
+ const BSONElement& rhs,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr)
+ : ComparisonMatchExpression(GT, path, rhs, std::move(annotation)) {}
StringData name() const final {
return kName;
@@ -283,7 +300,7 @@ public:
virtual std::unique_ptr<MatchExpression> shallowClone() const {
std::unique_ptr<ComparisonMatchExpression> e =
- std::make_unique<GTMatchExpression>(path(), _rhs);
+ std::make_unique<GTMatchExpression>(path(), _rhs, _errorAnnotation);
if (getTag()) {
e->setTag(getTag()->clone());
}
@@ -304,8 +321,10 @@ class GTEMatchExpression final : public ComparisonMatchExpression {
public:
static constexpr StringData kName = "$gte"_sd;
- GTEMatchExpression(StringData path, const BSONElement& rhs)
- : ComparisonMatchExpression(GTE, path, rhs) {}
+ GTEMatchExpression(StringData path,
+ const BSONElement& rhs,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr)
+ : ComparisonMatchExpression(GTE, path, rhs, std::move(annotation)) {}
StringData name() const final {
return kName;
@@ -313,7 +332,7 @@ public:
virtual std::unique_ptr<MatchExpression> shallowClone() const {
std::unique_ptr<ComparisonMatchExpression> e =
- std::make_unique<GTEMatchExpression>(path(), _rhs);
+ std::make_unique<GTEMatchExpression>(path(), _rhs, _errorAnnotation);
if (getTag()) {
e->setTag(getTag()->clone());
}
@@ -474,7 +493,7 @@ private:
*/
class InMatchExpression : public LeafMatchExpression {
public:
- explicit InMatchExpression(StringData path);
+ explicit InMatchExpression(StringData path, clonable_ptr<ErrorAnnotation> annotation = nullptr);
virtual std::unique_ptr<MatchExpression> shallowClone() const;
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index a9d64868e56..ab5df8b97ae 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -89,10 +89,38 @@ bool hasNode(const MatchExpression* root, MatchExpression::MatchType type) {
return false;
}
+/**
+ * Set of functions which create an ErrorAnnotation provided that a validator expression is being
+ * parsed.
+ */
+std::unique_ptr<MatchExpression::ErrorAnnotation> createAnnotation(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ const std::string& operatorName,
+ const BSONObj& annotation) {
+ if (expCtx->isParsingCollectionValidator) {
+ return std::make_unique<MatchExpression::ErrorAnnotation>(operatorName, annotation);
+ } else {
+ return nullptr;
+ }
+}
+
+std::unique_ptr<MatchExpression::ErrorAnnotation> createAnnotation(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ MatchExpression::ErrorAnnotation::Mode mode) {
+ if (expCtx->isParsingCollectionValidator) {
+ return std::make_unique<MatchExpression::ErrorAnnotation>(mode);
+ } else {
+ return nullptr;
+ }
+}
+
} // namespace
namespace mongo {
+using ErrorAnnotation = MatchExpression::ErrorAnnotation;
+using AnnotationMode = ErrorAnnotation::Mode;
+
constexpr StringData AlwaysFalseMatchExpression::kName;
constexpr StringData AlwaysTrueMatchExpression::kName;
constexpr StringData OrMatchExpression::kName;
@@ -299,12 +327,13 @@ StatusWithMatchExpression parse(const BSONObj& obj,
continue;
}
- auto eq =
- parseComparison(e.fieldNameStringData(),
- std::make_unique<EqualityMatchExpression>(e.fieldNameStringData(), e),
- e,
- expCtx,
- allowedFeatures);
+ auto eq = parseComparison(
+ e.fieldNameStringData(),
+ std::make_unique<EqualityMatchExpression>(
+ e.fieldNameStringData(), e, createAnnotation(expCtx, "$eq", e.wrap())),
+ e,
+ expCtx,
+ allowedFeatures);
if (!eq.isOK())
return eq;
@@ -1390,8 +1419,16 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
invariant(e);
if ("$eq"_sd == e.fieldNameStringData()) {
- return parseComparison(
- name, std::make_unique<EqualityMatchExpression>(name, e), e, expCtx, allowedFeatures);
+ return parseComparison(name,
+ std::make_unique<EqualityMatchExpression>(
+ name,
+ e,
+ createAnnotation(expCtx,
+ e.fieldNameStringData().toString(),
+ BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
}
if ("$not"_sd == e.fieldNameStringData()) {
@@ -1411,34 +1448,77 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
switch (*parseExpMatchType) {
case PathAcceptingKeyword::LESS_THAN:
- return parseComparison(
- name, std::make_unique<LTMatchExpression>(name, e), e, expCtx, allowedFeatures);
+ return parseComparison(name,
+ std::make_unique<LTMatchExpression>(
+ name,
+ e,
+ createAnnotation(expCtx,
+ e.fieldNameStringData().toString(),
+ BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
case PathAcceptingKeyword::LESS_THAN_OR_EQUAL:
- return parseComparison(
- name, std::make_unique<LTEMatchExpression>(name, e), e, expCtx, allowedFeatures);
+ return parseComparison(name,
+ std::make_unique<LTEMatchExpression>(
+ name,
+ e,
+ createAnnotation(expCtx,
+ e.fieldNameStringData().toString(),
+ BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
case PathAcceptingKeyword::GREATER_THAN:
- return parseComparison(
- name, std::make_unique<GTMatchExpression>(name, e), e, expCtx, allowedFeatures);
+ return parseComparison(name,
+ std::make_unique<GTMatchExpression>(
+ name,
+ e,
+ createAnnotation(expCtx,
+ e.fieldNameStringData().toString(),
+ BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
case PathAcceptingKeyword::GREATER_THAN_OR_EQUAL:
- return parseComparison(
- name, std::make_unique<GTEMatchExpression>(name, e), e, expCtx, allowedFeatures);
+ return parseComparison(name,
+ std::make_unique<GTEMatchExpression>(
+ name,
+ e,
+ createAnnotation(expCtx,
+ e.fieldNameStringData().toString(),
+ BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
case PathAcceptingKeyword::NOT_EQUAL: {
if (BSONType::RegEx == e.type()) {
// Just because $ne can be rewritten as the negation of an equality does not mean
// that $ne of a regex is allowed. See SERVER-1705.
return {Status(ErrorCodes::BadValue, "Can't have regex as arg to $ne.")};
}
- StatusWithMatchExpression s =
- parseComparison(name,
- std::make_unique<EqualityMatchExpression>(name, e),
- e,
- expCtx,
- allowedFeatures);
- return {std::make_unique<NotMatchExpression>(s.getValue().release())};
+ StatusWithMatchExpression s = parseComparison(
+ name,
+ std::make_unique<EqualityMatchExpression>(
+ name,
+ e,
+ createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap()))),
+ e,
+ expCtx,
+ allowedFeatures);
+ return {std::make_unique<NotMatchExpression>(
+ s.getValue().release(),
+ createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend))};
}
case PathAcceptingKeyword::EQUALITY:
return parseComparison(name,
- std::make_unique<EqualityMatchExpression>(name, e),
+ std::make_unique<EqualityMatchExpression>(
+ name,
+ e,
+ createAnnotation(expCtx,
+ e.fieldNameStringData().toString(),
+ BSON(name << e.wrap()))),
e,
expCtx,
allowedFeatures);
@@ -1447,7 +1527,10 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
if (e.type() != BSONType::Array) {
return {Status(ErrorCodes::BadValue, "$in needs an array")};
}
- auto temp = std::make_unique<InMatchExpression>(name);
+ auto temp = std::make_unique<InMatchExpression>(
+ name,
+ createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())));
auto parseStatus = parseInExpression(temp.get(), e.Obj(), expCtx);
if (!parseStatus.isOK()) {
return parseStatus;
@@ -1459,12 +1542,16 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
if (e.type() != Array) {
return {Status(ErrorCodes::BadValue, "$nin needs an array")};
}
- auto temp = std::make_unique<InMatchExpression>(name);
+ auto temp = std::make_unique<InMatchExpression>(
+ name,
+ createAnnotation(
+ expCtx, e.fieldNameStringData().toString(), BSON(name << e.wrap())));
auto parseStatus = parseInExpression(temp.get(), e.Obj(), expCtx);
if (!parseStatus.isOK()) {
return parseStatus;
}
- return {std::make_unique<NotMatchExpression>(temp.release())};
+ return {std::make_unique<NotMatchExpression>(
+ temp.release(), createAnnotation(expCtx, AnnotationMode::kIgnoreButDescend))};
}
case PathAcceptingKeyword::SIZE: {
diff --git a/src/mongo/db/matcher/expression_path.h b/src/mongo/db/matcher/expression_path.h
index a7f8941b928..1ae09636e8d 100644
--- a/src/mongo/db/matcher/expression_path.h
+++ b/src/mongo/db/matcher/expression_path.h
@@ -45,8 +45,9 @@ public:
PathMatchExpression(MatchType matchType,
StringData path,
ElementPath::LeafArrayBehavior leafArrBehavior,
- ElementPath::NonLeafArrayBehavior nonLeafArrayBehavior)
- : MatchExpression(matchType), _path(path) {
+ ElementPath::NonLeafArrayBehavior nonLeafArrayBehavior,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr)
+ : MatchExpression(matchType, std::move(annotation)), _path(path) {
_elementPath.init(_path);
_elementPath.setLeafArrayBehavior(leafArrBehavior);
_elementPath.setNonLeafArrayBehavior(nonLeafArrayBehavior);
diff --git a/src/mongo/db/matcher/expression_tree.h b/src/mongo/db/matcher/expression_tree.h
index d6d1fec2159..4da0af5d8fc 100644
--- a/src/mongo/db/matcher/expression_tree.h
+++ b/src/mongo/db/matcher/expression_tree.h
@@ -215,11 +215,13 @@ public:
class NotMatchExpression final : public MatchExpression {
public:
- explicit NotMatchExpression(MatchExpression* e) : MatchExpression(NOT), _exp(e) {}
+ explicit NotMatchExpression(MatchExpression* e,
+ clonable_ptr<ErrorAnnotation> annotation = nullptr)
+ : MatchExpression(NOT, std::move(annotation)), _exp(e) {}
virtual std::unique_ptr<MatchExpression> shallowClone() const {
std::unique_ptr<NotMatchExpression> self =
- std::make_unique<NotMatchExpression>(_exp->shallowClone().release());
+ std::make_unique<NotMatchExpression>(_exp->shallowClone().release(), _errorAnnotation);
if (getTag()) {
self->setTag(getTag()->clone());
}