diff options
author | Mihai Andrei <mihai.andrei@10gen.com> | 2020-07-02 19:21:35 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-07-07 13:38:05 +0000 |
commit | e87f6f53c58a75229b40fff7b32de202f2dcccc7 (patch) | |
tree | eafb77ccd24ee11879f904c042eec41e4a3ad76e | |
parent | ff4465b905b331fc82edb8194092917ad0c7255f (diff) | |
download | mongo-e87f6f53c58a75229b40fff7b32de202f2dcccc7.tar.gz |
SERVER-48781 Introduce 'DocumentValidationErrorExtraInfo' and shell support for document validation errors
-rw-r--r-- | jstests/core/doc_validation.js | 23 | ||||
-rw-r--r-- | src/mongo/base/error_codes.yml | 4 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_impl.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/commands/write_commands/write_commands.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/matcher/doc_validation_error.cpp | 21 | ||||
-rw-r--r-- | src/mongo/db/matcher/doc_validation_error.h | 17 | ||||
-rw-r--r-- | src/mongo/shell/bulk_api.js | 11 |
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)); } |