summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/apply_ops1.js41
-rw-r--r--jstests/core/apply_ops_atomicity.js10
-rw-r--r--src/mongo/db/repl/apply_ops.cpp117
3 files changed, 115 insertions, 53 deletions
diff --git a/jstests/core/apply_ops1.js b/jstests/core/apply_ops1.js
index cb440addf5e..6dc551e766f 100644
--- a/jstests/core/apply_ops1.js
+++ b/jstests/core/apply_ops1.js
@@ -286,9 +286,44 @@
'applyOps should fail on unknown operation type "x" with valid "ns" value');
assert.eq(0, t.find().count(), "Non-zero amount of documents in collection to start");
- assert.commandFailed(
- db.adminCommand({applyOps: [{"op": "i", "ns": t.getFullName(), "o": {_id: 5, x: 17}}]}),
- "Applying an insert operation on a non-existent collection should fail");
+
+ /**
+ * Test function for running CRUD operations on non-existent namespaces using various
+ * combinations of invalid namespaces (collection/database), allowAtomic and alwaysUpsert.
+ *
+ * Leave 'expectedErrorCode' undefined if this command is expected to run successfully.
+ */
+ function testCrudOperationOnNonExistentNamespace(optype, o, o2, expectedErrorCode) {
+ expectedErrorCode = expectedErrorCode || ErrorCodes.OK;
+ const t2 = db.getSiblingDB('apply_ops1_no_such_db').getCollection('t');
+ [t, t2].forEach(coll => {
+ const op = {op: optype, ns: coll.getFullName(), o: o, o2: o2};
+ [false, true].forEach(allowAtomic => {
+ [false, true].forEach(alwaysUpsert => {
+ const cmd = {
+ applyOps: [op],
+ allowAtomic: allowAtomic,
+ alwaysUpsert: alwaysUpsert
+ };
+ jsTestLog('Testing applyOps on non-existent namespace: ' + tojson(cmd));
+ if (expectedErrorCode === ErrorCodes.OK) {
+ assert.commandWorked(db.adminCommand(cmd));
+ } else {
+ assert.commandFailedWithCode(db.adminCommand(cmd), expectedErrorCode);
+ }
+ });
+ });
+ });
+ }
+
+ // Insert and update operations on non-existent collections/databases should return
+ // NamespaceNotFound.
+ testCrudOperationOnNonExistentNamespace('i', {_id: 0}, {}, ErrorCodes.NamespaceNotFound);
+ testCrudOperationOnNonExistentNamespace('u', {x: 0}, {_id: 0}, ErrorCodes.NamespaceNotFound);
+
+ // Delete operations on non-existent collections/databases should return OK for idempotency
+ // reasons.
+ testCrudOperationOnNonExistentNamespace('d', {_id: 0}, {});
assert.commandWorked(db.createCollection(t.getName()));
var a = assert.commandWorked(
diff --git a/jstests/core/apply_ops_atomicity.js b/jstests/core/apply_ops_atomicity.js
index 911ce32a311..e704815a889 100644
--- a/jstests/core/apply_ops_atomicity.js
+++ b/jstests/core/apply_ops_atomicity.js
@@ -29,10 +29,12 @@
var newDBName = "apply_ops_atomicity";
var newDB = db.getSiblingDB(newDBName);
assert.commandWorked(newDB.dropDatabase());
- // Do an update on a non-existent database, since only 'u' ops can implicitly create
- // collections.
- assert.commandWorked(newDB.runCommand(
- {applyOps: [{op: "u", ns: newDBName + ".foo", o: {_id: 5, x: 17}, o2: {_id: 5, x: 16}}]}));
+ // Updates on a non-existent database no longer implicitly create collections and will fail with
+ // a NamespaceNotFound error.
+ assert.commandFailedWithCode(newDB.runCommand({
+ applyOps: [{op: "u", ns: newDBName + ".foo", o: {_id: 5, x: 17}, o2: {_id: 5, x: 16}}]
+ }),
+ ErrorCodes.NamespaceNotFound);
var sawTooManyLocksError = false;
diff --git a/src/mongo/db/repl/apply_ops.cpp b/src/mongo/db/repl/apply_ops.cpp
index 30eaee93482..aeb493e610c 100644
--- a/src/mongo/db/repl/apply_ops.cpp
+++ b/src/mongo/db/repl/apply_ops.cpp
@@ -123,8 +123,7 @@ Status _applyOps(OperationContext* opCtx,
if (*opType == 'n')
continue;
- const std::string ns = opObj["ns"].String();
- const NamespaceString nss{ns};
+ const NamespaceString nss(opObj["ns"].String());
// Need to check this here, or OldClientContext may fail an invariant.
if (*opType != 'c' && !nss.isValid())
@@ -136,63 +135,89 @@ Status _applyOps(OperationContext* opCtx,
invariant(opCtx->lockState()->isW());
invariant(*opType != 'c');
- if (!dbHolder().get(opCtx, ns)) {
+ auto db = dbHolder().get(opCtx, nss.ns());
+ if (!db) {
throw DBException(
ErrorCodes::NamespaceNotFound,
"cannot create a database in atomic applyOps mode; will retry without "
"atomicity");
}
- OldClientContext ctx(opCtx, ns);
+ // When processing an update on a non-existent collection, applyOperation_inlock()
+ // returns UpdateOperationFailed on updates and allows the collection to be
+ // implicitly created on upserts. We detect both cases here and fail early with
+ // NamespaceNotFound.
+ // Additionally for inserts, we fail early on non-existent collections.
+ auto collection = db->getCollection(opCtx, nss);
+ if (!collection && !nss.isSystemDotIndexes() && (*opType == 'i' || *opType == 'u')) {
+ throw DBException(
+ ErrorCodes::NamespaceNotFound,
+ str::stream()
+ << "cannot apply insert or update operation on a non-existent namespace "
+ << nss.ns()
+ << " in atomic applyOps mode: "
+ << redact(opObj));
+ }
+
+ OldClientContext ctx(opCtx, nss.ns());
status = repl::applyOperation_inlock(opCtx, ctx.db(), opObj, alwaysUpsert);
if (!status.isOK())
return status;
} else {
try {
- writeConflictRetry(opCtx, "applyOps", ns, [&] {
- if (*opType == 'c') {
- invariant(opCtx->lockState()->isW());
- status = repl::applyCommand_inlock(opCtx, opObj, true);
- } else {
- Lock::DBLock dbWriteLock(opCtx, nss.db(), MODE_IX);
- Lock::CollectionLock lock(opCtx->lockState(), nss.ns(), MODE_IX);
- OldClientContext ctx(opCtx, ns);
- const char* names[] = {"o", "ns"};
- BSONElement fields[2];
- opObj.getFields(2, names, fields);
- BSONElement& fieldO = fields[0];
- BSONElement& fieldNs = fields[1];
- const StringData ns = fieldNs.valueStringData();
- NamespaceString requestNss{ns};
-
- if (nss.isSystemDotIndexes()) {
- BSONObj indexSpec;
- NamespaceString indexNss;
- std::tie(indexSpec, indexNss) =
- repl::prepForApplyOpsIndexInsert(fieldO, opObj, requestNss);
- BSONObjBuilder command;
- command.append("createIndexes", indexNss.coll());
- {
- BSONArrayBuilder indexes(command.subarrayStart("indexes"));
- indexes.append(indexSpec);
- indexes.doneFast();
+ status = writeConflictRetry(
+ opCtx, "applyOps", nss.ns(), [opCtx, nss, opObj, opType, alwaysUpsert] {
+ if (*opType == 'c') {
+ invariant(opCtx->lockState()->isW());
+ return repl::applyCommand_inlock(opCtx, opObj, true);
+ }
+
+ AutoGetCollection autoColl(opCtx, nss, MODE_IX);
+ if (!autoColl.getCollection() && !nss.isSystemDotIndexes()) {
+ // For idempotency reasons, return success on delete operations.
+ if (*opType == 'd') {
+ return Status::OK();
}
- const BSONObj commandObj = command.done();
-
- DBDirectClient client(opCtx);
- BSONObj infoObj;
- client.runCommand(nsToDatabase(ns), commandObj, infoObj);
- status = getStatusFromCommandResult(infoObj);
-
- // Only uassert to stop applyOps only when building indexes,
- // but not for CRUD ops.
- uassertStatusOK(status);
- } else {
- status =
- repl::applyOperation_inlock(opCtx, ctx.db(), opObj, alwaysUpsert);
+ throw DBException(ErrorCodes::NamespaceNotFound,
+ str::stream()
+ << "cannot apply insert or update operation on a "
+ "non-existent namespace "
+ << nss.ns()
+ << ": "
+ << mongo::redact(opObj));
+ }
+
+ OldClientContext ctx(opCtx, nss.ns());
+
+ if (!nss.isSystemDotIndexes()) {
+ return repl::applyOperation_inlock(
+ opCtx, ctx.db(), opObj, alwaysUpsert);
+ }
+
+ auto fieldO = opObj["o"];
+ BSONObj indexSpec;
+ NamespaceString indexNss;
+ std::tie(indexSpec, indexNss) =
+ repl::prepForApplyOpsIndexInsert(fieldO, opObj, nss);
+ BSONObjBuilder command;
+ command.append("createIndexes", indexNss.coll());
+ {
+ BSONArrayBuilder indexes(command.subarrayStart("indexes"));
+ indexes.append(indexSpec);
+ indexes.doneFast();
}
- }
- });
+ const BSONObj commandObj = command.done();
+
+ DBDirectClient client(opCtx);
+ BSONObj infoObj;
+ client.runCommand(nss.db().toString(), commandObj, infoObj);
+
+ // Uassert to stop applyOps only when building indexes, but not for CRUD
+ // ops.
+ uassertStatusOK(getStatusFromCommandResult(infoObj));
+
+ return Status::OK();
+ });
} catch (const DBException& ex) {
ab.append(false);
result->append("applied", ++(*numApplied));