summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenety Goh <benety@mongodb.com>2018-02-08 13:19:04 -0500
committerBenety Goh <benety@mongodb.com>2018-02-08 13:27:06 -0500
commit2e287a4bc99f3c774a5322eae477f626638c6a81 (patch)
treeb777c139eac49e1e575ceed84d4b0be51ca9ebe3
parent4cc459b6eec57202943fb56d79327f5731f2bfef (diff)
downloadmongo-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/SConscript6
-rw-r--r--src/mongo/db/repl/apply_ops.cpp95
-rw-r--r--src/mongo/db/repl/apply_ops.h29
-rw-r--r--src/mongo/db/repl/apply_ops.idl68
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."