diff options
author | Benety Goh <benety@mongodb.com> | 2018-02-08 13:19:04 -0500 |
---|---|---|
committer | Benety Goh <benety@mongodb.com> | 2018-02-08 13:27:06 -0500 |
commit | 2e287a4bc99f3c774a5322eae477f626638c6a81 (patch) | |
tree | b777c139eac49e1e575ceed84d4b0be51ca9ebe3 | |
parent | 4cc459b6eec57202943fb56d79327f5731f2bfef (diff) | |
download | mongo-2e287a4bc99f3c774a5322eae477f626638c6a81.tar.gz |
SERVER-32913 add ApplyOpsCommandInfo
This consolidates the command option parsing and some metadata for the applyOps command.
-rw-r--r-- | src/mongo/db/repl/SConscript | 6 | ||||
-rw-r--r-- | src/mongo/db/repl/apply_ops.cpp | 95 | ||||
-rw-r--r-- | src/mongo/db/repl/apply_ops.h | 29 | ||||
-rw-r--r-- | src/mongo/db/repl/apply_ops.idl | 68 |
4 files changed, 156 insertions, 42 deletions
diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index ddcc2ab2c5b..cb0d219cd12 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -10,8 +10,11 @@ env.Library( 'apply_ops.cpp', 'do_txn.cpp', 'oplog.cpp', + env.Idlc('apply_ops.idl')[0], ], LIBDEPS_PRIVATE=[ + 'dbcheck', + 'repl_coordinator_interface', '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/background', '$BUILD_DIR/mongo/db/catalog/catalog_helpers', @@ -20,8 +23,7 @@ env.Library( '$BUILD_DIR/mongo/db/dbdirectclient', '$BUILD_DIR/mongo/db/dbhelpers', '$BUILD_DIR/mongo/db/index_d', - 'dbcheck', - 'repl_coordinator_interface', + '$BUILD_DIR/mongo/idl/idl_parser', ], ) diff --git a/src/mongo/db/repl/apply_ops.cpp b/src/mongo/db/repl/apply_ops.cpp index 60501e1ba7e..9ed88b5ab03 100644 --- a/src/mongo/db/repl/apply_ops.cpp +++ b/src/mongo/db/repl/apply_ops.cpp @@ -68,7 +68,7 @@ MONGO_FP_DECLARE(applyOpsPauseBetweenOperations); /** * Return true iff the applyOpsCmd can be executed in a single WriteUnitOfWork. */ -bool _areOpsCrudOnly(const BSONObj& applyOpCmd) { +bool _parseAreOpsCrudOnly(const BSONObj& applyOpCmd) { for (const auto& elem : applyOpCmd.firstElement().Obj()) { const char* names[] = {"ns", "op"}; BSONElement fields[2]; @@ -104,26 +104,22 @@ bool _areOpsCrudOnly(const BSONObj& applyOpCmd) { Status _applyOps(OperationContext* opCtx, const std::string& dbName, const BSONObj& applyOpCmd, + const ApplyOpsCommandInfo& info, repl::OplogApplication::Mode oplogApplicationMode, BSONObjBuilder* result, int* numApplied, BSONArrayBuilder* opsBuilder) { - BSONObj ops = applyOpCmd.firstElement().Obj(); + const auto& ops = info.getOperations(); // apply *numApplied = 0; int errors = 0; - BSONObjIterator i(ops); BSONArrayBuilder ab; - const bool alwaysUpsert = - applyOpCmd.hasField("alwaysUpsert") ? applyOpCmd["alwaysUpsert"].trueValue() : true; + const auto& alwaysUpsert = info.getAlwaysUpsert(); const bool haveWrappingWUOW = opCtx->lockState()->inAWriteUnitOfWork(); // Apply each op in the given 'applyOps' command object. - while (i.more()) { - BSONElement e = i.next(); - const BSONObj& opObj = e.Obj(); - + for (const auto& opObj : ops) { // Ignore 'n' operations. const char* opType = opObj["op"].valuestrsafe(); if (*opType == 'n') @@ -318,18 +314,12 @@ Status _applyOps(OperationContext* opCtx, return Status::OK(); } -bool _hasPrecondition(const BSONObj& applyOpCmd) { - return applyOpCmd[ApplyOps::kPreconditionFieldName].type() == Array; -} - Status _checkPrecondition(OperationContext* opCtx, - const BSONObj& applyOpCmd, + const std::vector<BSONObj>& preConditions, BSONObjBuilder* result) { invariant(opCtx->lockState()->isW()); - invariant(_hasPrecondition(applyOpCmd)); - for (auto elem : applyOpCmd[ApplyOps::kPreconditionFieldName].Obj()) { - auto preCondition = elem.Obj(); + for (const auto& preCondition : preConditions) { if (preCondition["ns"].type() != BSONType::String) { return {ErrorCodes::InvalidNamespace, str::stream() << "ns in preCondition must be a string, but found type: " @@ -369,33 +359,51 @@ Status _checkPrecondition(OperationContext* opCtx, } } // namespace +// static +ApplyOpsCommandInfo ApplyOpsCommandInfo::parse(const BSONObj& applyOpCmd) { + try { + return ApplyOpsCommandInfo(applyOpCmd); + } catch (DBException& ex) { + ex.addContext(str::stream() << "Failed to parse applyOps command: " << redact(applyOpCmd)); + throw; + } +} + +bool ApplyOpsCommandInfo::areOpsCrudOnly() const { + return _areOpsCrudOnly; +} + +bool ApplyOpsCommandInfo::isAtomic() const { + return getAllowAtomic() && areOpsCrudOnly(); +} + +ApplyOpsCommandInfo::ApplyOpsCommandInfo(const BSONObj& applyOpCmd) + : _areOpsCrudOnly(_parseAreOpsCrudOnly(applyOpCmd)) { + parseProtected(IDLParserErrorContext("applyOps"), applyOpCmd); + + if (getPreCondition()) { + uassert(ErrorCodes::InvalidOptions, + "Cannot use preCondition with {allowAtomic: false}", + getAllowAtomic()); + uassert(ErrorCodes::InvalidOptions, + "Cannot use preCondition when operations include commands.", + areOpsCrudOnly()); + } +} + Status applyOps(OperationContext* opCtx, const std::string& dbName, const BSONObj& applyOpCmd, repl::OplogApplication::Mode oplogApplicationMode, BSONObjBuilder* result) { - bool allowAtomic = false; - uassertStatusOK( - bsonExtractBooleanFieldWithDefault(applyOpCmd, "allowAtomic", true, &allowAtomic)); - auto areOpsCrudOnly = _areOpsCrudOnly(applyOpCmd); - auto isAtomic = allowAtomic && areOpsCrudOnly; - auto hasPrecondition = _hasPrecondition(applyOpCmd); - - if (hasPrecondition) { - uassert(ErrorCodes::InvalidOptions, - "Cannot use preCondition with {allowAtomic: false}.", - allowAtomic); - uassert(ErrorCodes::InvalidOptions, - "Cannot use preCondition when operations include commands.", - areOpsCrudOnly); - } + auto info = ApplyOpsCommandInfo::parse(applyOpCmd); boost::optional<Lock::GlobalWrite> globalWriteLock; boost::optional<Lock::DBLock> dbWriteLock; // There's only one case where we are allowed to take the database lock instead of the global // lock - no preconditions; only CRUD ops; and non-atomic mode. - if (!hasPrecondition && areOpsCrudOnly && !allowAtomic) { + if (!info.getPreCondition() && info.areOpsCrudOnly() && !info.getAllowAtomic()) { dbWriteLock.emplace(opCtx, dbName, MODE_IX); } else { globalWriteLock.emplace(opCtx); @@ -409,18 +417,18 @@ Status applyOps(OperationContext* opCtx, return Status(ErrorCodes::NotMaster, str::stream() << "Not primary while applying ops to database " << dbName); - if (hasPrecondition) { - invariant(isAtomic); - auto status = _checkPrecondition(opCtx, applyOpCmd, result); + if (auto preCondition = info.getPreCondition()) { + invariant(info.isAtomic()); + auto status = _checkPrecondition(opCtx, *preCondition, result); if (!status.isOK()) { return status; } } int numApplied = 0; - if (!isAtomic) { + if (!info.isAtomic()) { return _applyOps( - opCtx, dbName, applyOpCmd, oplogApplicationMode, result, &numApplied, nullptr); + opCtx, dbName, applyOpCmd, info, oplogApplicationMode, result, &numApplied, nullptr); } // Perform write ops atomically @@ -442,6 +450,7 @@ Status applyOps(OperationContext* opCtx, uassertStatusOK(_applyOps(opCtx, dbName, applyOpCmd, + info, oplogApplicationMode, &intermediateResult, &numApplied, @@ -480,8 +489,14 @@ Status applyOps(OperationContext* opCtx, } catch (const DBException& ex) { if (ex.code() == ErrorCodes::AtomicityFailure) { // Retry in non-atomic mode. - return _applyOps( - opCtx, dbName, applyOpCmd, oplogApplicationMode, result, &numApplied, nullptr); + return _applyOps(opCtx, + dbName, + applyOpCmd, + info, + oplogApplicationMode, + result, + &numApplied, + nullptr); } BSONArrayBuilder ab; ++numApplied; diff --git a/src/mongo/db/repl/apply_ops.h b/src/mongo/db/repl/apply_ops.h index e12e2f5982f..45400998c9e 100644 --- a/src/mongo/db/repl/apply_ops.h +++ b/src/mongo/db/repl/apply_ops.h @@ -30,6 +30,7 @@ #include "mongo/base/status.h" #include "mongo/bson/bsonobj.h" +#include "mongo/db/repl/apply_ops_gen.h" #include "mongo/db/repl/multiapplier.h" #include "mongo/db/repl/oplog.h" #include "mongo/db/repl/oplog_entry.h" @@ -52,6 +53,34 @@ public: }; /** + * Holds information about an applyOps command object. + */ +class ApplyOpsCommandInfo : public ApplyOpsCommandInfoBase { +public: + /** + * Parses the object in the 'o' field of an applyOps command. + * May throw UserException. + */ + static ApplyOpsCommandInfo parse(const BSONObj& applyOpCmd); + + /** + * Returns true if all operations described by this applyOps command are CRUD only. + */ + bool areOpsCrudOnly() const; + + /** + * Returns true if applyOps will try to process all operations in a single batch atomically. + * Derived from getAllowAtomic() and areOpsCrudOnly(). + */ + bool isAtomic() const; + +private: + explicit ApplyOpsCommandInfo(const BSONObj& applyOpCmd); + + const bool _areOpsCrudOnly; +}; + +/** * Applies ops contained in 'applyOpCmd' and populates fields in 'result' to be returned to the * caller. The information contained in 'result' can be returned to the user if called as part * of the execution of an 'applyOps' command. diff --git a/src/mongo/db/repl/apply_ops.idl b/src/mongo/db/repl/apply_ops.idl new file mode 100644 index 00000000000..947aa543e46 --- /dev/null +++ b/src/mongo/db/repl/apply_ops.idl @@ -0,0 +1,68 @@ +# Copyright (C) 2018 MongoDB Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3, +# as published by the Free Software Foundation. +# +# 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 +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# 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 GNU Affero General 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. + +# applyOps IDL File + +global: + cpp_namespace: "mongo::repl" + +imports: + - "mongo/idl/basic_types.idl" + +structs: + ApplyOpsCommandInfoBase: + description: A document that represents an applyOps command. + immutable: true + strict: false + fields: + applyOps: + cpp_name: operations + type: array<object> + description: "The operations to apply" + + allowAtomic: + cpp_name: allowAtomic + type: bool + default: true + description: "If 'allowAtomic' is true, applyOps() determines if all the given + operations can be processed inside one WriteUnitOfWork. Set this flag + to false to disable this 'atomic' mode." + + alwaysUpsert: + cpp_name: alwaysUpsert + type: bool + default: true + description: "Affects update operations. Set 'alwaysUpsert' to false in the command + object to never insert a new document if no document matches the query + criteria in an update operation." + + preCondition: + cpp_name: preCondition + type: array<object> + optional: true + description: "applyOps supports checking the documents of existing collections + before proceeding to execute the given operations. This flag is set to + true if the 'preCondition' option is provided." |