summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMihai Andrei <mihai.andrei@10gen.com>2020-07-02 19:21:35 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-07-07 13:38:05 +0000
commite87f6f53c58a75229b40fff7b32de202f2dcccc7 (patch)
treeeafb77ccd24ee11879f904c042eec41e4a3ad76e
parentff4465b905b331fc82edb8194092917ad0c7255f (diff)
downloadmongo-e87f6f53c58a75229b40fff7b32de202f2dcccc7.tar.gz
SERVER-48781 Introduce 'DocumentValidationErrorExtraInfo' and shell support for document validation errors
-rw-r--r--jstests/core/doc_validation.js23
-rw-r--r--src/mongo/base/error_codes.yml4
-rw-r--r--src/mongo/db/catalog/collection_impl.cpp5
-rw-r--r--src/mongo/db/commands/write_commands/write_commands.cpp5
-rw-r--r--src/mongo/db/matcher/doc_validation_error.cpp21
-rw-r--r--src/mongo/db/matcher/doc_validation_error.h17
-rw-r--r--src/mongo/shell/bulk_api.js11
7 files changed, 71 insertions, 15 deletions
diff --git a/jstests/core/doc_validation.js b/jstests/core/doc_validation.js
index 1b74032c7c3..b45531af62d 100644
--- a/jstests/core/doc_validation.js
+++ b/jstests/core/doc_validation.js
@@ -11,21 +11,26 @@
(function() {
"use strict";
-function assertFailsValidation(res) {
- if (res instanceof WriteResult) {
- assert.writeErrorWithCode(res, ErrorCodes.DocumentValidationFailure, tojson(res));
- } else {
- assert.commandFailedWithCode(res, ErrorCodes.DocumentValidationFailure, tojson(res));
- }
-}
+const collName = "doc_validation";
+const coll = db[collName];
const array = [];
for (let i = 0; i < 2048; i++) {
array.push({arbitrary: i});
}
-const collName = "doc_validation";
-const coll = db[collName];
+function assertFailsValidation(res) {
+ // Assert that validation fails with a 'DocumentValidationFailure' error.
+ assert.commandFailedWithCode(res, ErrorCodes.DocumentValidationFailure, tojson(res));
+ // Verify that the 'errInfo' field is propagated as part of the document validation failure
+ // for WriteErrors.
+ // We don't currently support detailed error info for 'OP_INSERT' and 'OP_UPDATE'.
+ if (coll.getMongo().writeMode() === "commands") {
+ const error = res instanceof WriteResult ? res.getWriteError() : res;
+ assert(error.hasOwnProperty("errInfo"), tojson(error));
+ assert.eq(typeof error["errInfo"], "object", tojson(error));
+ }
+}
/**
* Runs a series of document validation tests using the validator 'validator', which should
diff --git a/src/mongo/base/error_codes.yml b/src/mongo/base/error_codes.yml
index 8fa58c88d44..9c806fce610 100644
--- a/src/mongo/base/error_codes.yml
+++ b/src/mongo/base/error_codes.yml
@@ -150,7 +150,9 @@ error_codes:
- {code: 118,name: NamespaceNotSharded}
- {code: 119,name: InvalidSyncSource}
- {code: 120,name: OplogStartMissing}
- - {code: 121,name: DocumentValidationFailure} # Only for the document validator on collections.
+ # Error code 121 is only for the document validator on collections.
+ - {code: 121,name: DocumentValidationFailure,
+ extra: 'doc_validation_error::DocumentValidationFailureInfo'}
- {code: 122,name: OBSOLETE_ReadAfterOptimeTimeout}
- {code: 123,name: NotAReplicaSet}
- {code: 124,name: IncompatibleElectionProtocol}
diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp
index 52d8330fa8a..39a0ef9af1e 100644
--- a/src/mongo/db/catalog/collection_impl.cpp
+++ b/src/mongo/db/catalog/collection_impl.cpp
@@ -53,6 +53,7 @@
#include "mongo/db/index/index_access_method.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/keypattern.h"
+#include "mongo/db/matcher/doc_validation_error.h"
#include "mongo/db/matcher/expression_always_boolean.h"
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/op_observer.h"
@@ -397,7 +398,9 @@ Status CollectionImpl::checkValidation(OperationContext* opCtx, const BSONObj& d
return Status::OK();
}
- return {ErrorCodes::DocumentValidationFailure, "Document failed validation"};
+ return {doc_validation_error::DocumentValidationFailureInfo(
+ doc_validation_error::generateError(*validatorMatchExpr, document)),
+ "Document failed validation"};
}
Collection::Validator CollectionImpl::parseValidator(
diff --git a/src/mongo/db/commands/write_commands/write_commands.cpp b/src/mongo/db/commands/write_commands/write_commands.cpp
index 77b3294cd41..011cf666d41 100644
--- a/src/mongo/db/commands/write_commands/write_commands.cpp
+++ b/src/mongo/db/commands/write_commands/write_commands.cpp
@@ -40,6 +40,7 @@
#include "mongo/db/db_raii.h"
#include "mongo/db/json.h"
#include "mongo/db/lasterror.h"
+#include "mongo/db/matcher/doc_validation_error.h"
#include "mongo/db/matcher/extensions_callback_real.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/ops/delete_request_gen.h"
@@ -152,6 +153,10 @@ void serializeReply(OperationContext* opCtx,
BSONObjBuilder errInfo(error.subobjStart("errInfo"));
staleInfo->serialize(&errInfo);
}
+ } else if (auto docValidationError =
+ status.extraInfo<doc_validation_error::DocumentValidationFailureInfo>()) {
+ error.append("code", static_cast<int>(ErrorCodes::DocumentValidationFailure));
+ error.append("errInfo", docValidationError->getDetails());
} else {
error.append("code", int(status.code()));
if (auto const extraInfo = status.extraInfo()) {
diff --git a/src/mongo/db/matcher/doc_validation_error.cpp b/src/mongo/db/matcher/doc_validation_error.cpp
index b81846d5297..4018443dbd4 100644
--- a/src/mongo/db/matcher/doc_validation_error.cpp
+++ b/src/mongo/db/matcher/doc_validation_error.cpp
@@ -29,14 +29,16 @@
#include "mongo/platform/basic.h"
+#include "mongo/base/init.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/db/matcher/doc_validation_error.h"
#include "mongo/db/matcher/expression_visitor.h"
#include "mongo/db/matcher/match_expression_walker.h"
namespace mongo::doc_validation_error {
-
namespace {
+MONGO_INIT_REGISTER_ERROR_EXTRA_INFO(DocumentValidationFailureInfo);
+
/**
* Enumerated type which describes whether an error should be described normally or in an
* inverted sense when in a negated context. More precisely, when a MatchExpression fails to match a
@@ -201,7 +203,6 @@ private:
class ValidationErrorPostVisitor final : public MatchExpressionConstVisitor {
public:
ValidationErrorPostVisitor(ValidationErrorContext* context) : _context(context) {}
-
void visit(const AlwaysFalseMatchExpression* expr) final {}
void visit(const AlwaysTrueMatchExpression* expr) final {}
void visit(const AndMatchExpression* expr) final {}
@@ -270,6 +271,20 @@ private:
} // namespace
+std::shared_ptr<const ErrorExtraInfo> DocumentValidationFailureInfo::parse(const BSONObj& obj) {
+ auto errInfo = obj["errInfo"];
+ uassert(4878100,
+ "DocumentValidationFailureInfo must have a field 'errInfo' of type object",
+ errInfo.type() == BSONType::Object);
+ return std::make_shared<DocumentValidationFailureInfo>(errInfo.embeddedObject());
+}
+
+void DocumentValidationFailureInfo::serialize(BSONObjBuilder* bob) const {
+ bob->append("errInfo", _details);
+}
+const BSONObj& DocumentValidationFailureInfo::getDetails() const {
+ return _details;
+}
BSONObj generateError(const MatchExpression& validatorExpr, const BSONObj& doc) {
BSONMatchableDocument matchableDoc(doc);
ValidationErrorContext context;
@@ -281,4 +296,4 @@ BSONObj generateError(const MatchExpression& validatorExpr, const BSONObj& doc)
return BSONObj();
}
-} // namespace mongo::doc_validation_error \ No newline at end of file
+} // namespace mongo::doc_validation_error
diff --git a/src/mongo/db/matcher/doc_validation_error.h b/src/mongo/db/matcher/doc_validation_error.h
index 30d29be8516..ae632080179 100644
--- a/src/mongo/db/matcher/doc_validation_error.h
+++ b/src/mongo/db/matcher/doc_validation_error.h
@@ -29,14 +29,29 @@
#pragma once
+#include "mongo/base/error_extra_info.h"
#include "mongo/db/matcher/expression.h"
namespace mongo::doc_validation_error {
/**
+ * Represents information about a document validation error.
+ */
+class DocumentValidationFailureInfo final : public ErrorExtraInfo {
+public:
+ static constexpr auto code = ErrorCodes::DocumentValidationFailure;
+ static std::shared_ptr<const ErrorExtraInfo> parse(const BSONObj& obj);
+ explicit DocumentValidationFailureInfo(const BSONObj& err) : _details(err.getOwned()) {}
+ const BSONObj& getDetails() const;
+ void serialize(BSONObjBuilder* bob) const override;
+
+private:
+ BSONObj _details;
+};
+
+/**
* Given a pointer to a MatchExpression corresponding to a collection's validator expression and a
* reference to a BSONObj corresponding to the document that failed to match against the validator
* expression, returns a BSONObj that describes why 'doc' failed to match against 'validatorExpr'.
*/
BSONObj generateError(const MatchExpression& validatorExpr, const BSONObj& doc);
-
} // namespace mongo::doc_validation_error \ No newline at end of file
diff --git a/src/mongo/shell/bulk_api.js b/src/mongo/shell/bulk_api.js
index eac2c063374..0b880175648 100644
--- a/src/mongo/shell/bulk_api.js
+++ b/src/mongo/shell/bulk_api.js
@@ -187,6 +187,10 @@ var _bulk_api_module = (function() {
result.writeError = {};
result.writeError.code = this.getWriteError().code;
result.writeError.errmsg = this.getWriteError().errmsg;
+ let errInfo = this.getWriteError().errInfo;
+ if (errInfo) {
+ result.writeError.errInfo = errInfo;
+ }
}
if (this.getWriteConcernError() != null) {
@@ -438,6 +442,9 @@ var _bulk_api_module = (function() {
defineReadOnlyProperty(this, "code", err.code);
defineReadOnlyProperty(this, "index", err.index);
defineReadOnlyProperty(this, "errmsg", err.errmsg);
+ // errInfo field is optional.
+ if (err.hasOwnProperty("errInfo"))
+ defineReadOnlyProperty(this, "errInfo", err.errInfo);
//
// Define access methods
@@ -851,6 +858,10 @@ var _bulk_api_module = (function() {
errmsg: result.writeErrors[i].errmsg,
op: batch.operations[result.writeErrors[i].index]
};
+ var errInfo = result.writeErrors[i].errInfo;
+ if (errInfo) {
+ writeError['errInfo'] = errInfo;
+ }
bulkResult.writeErrors.push(new WriteError(writeError));
}