summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2014-11-07 17:19:18 -0500
committerDavid Storch <david.storch@10gen.com>2014-11-12 15:37:37 -0500
commitfdc16e8d1d2346ca152528b71538942efb3ce4db (patch)
tree40283dee62d30ebb76584b81b02112455d03b9c0
parent578c5c5bdbf43378da11f706be0d9a54daba0929 (diff)
downloadmongo-fdc16e8d1d2346ca152528b71538942efb3ce4db.tar.gz
SERVER-15917 fix explain of write commands against a non-existent database
-rw-r--r--jstests/core/explain_null_collection.js104
-rw-r--r--src/mongo/db/commands/write_commands/write_commands.cpp25
-rw-r--r--src/mongo/db/exec/update.cpp7
-rw-r--r--src/mongo/db/exec/update.h7
-rw-r--r--src/mongo/db/ops/delete_executor.cpp12
-rw-r--r--src/mongo/db/ops/update_executor.cpp32
-rw-r--r--src/mongo/db/query/get_executor.cpp18
-rw-r--r--src/mongo/db/query/get_executor.h5
-rw-r--r--src/mongo/dbtests/query_stage_update.cpp6
9 files changed, 174 insertions, 42 deletions
diff --git a/jstests/core/explain_null_collection.js b/jstests/core/explain_null_collection.js
new file mode 100644
index 00000000000..47d576e6fbf
--- /dev/null
+++ b/jstests/core/explain_null_collection.js
@@ -0,0 +1,104 @@
+// Test explain of various operations against non-existent databases and collections.
+
+var explain;
+var explainColl;
+
+//
+// Part 1: Non-existent database.
+//
+
+var explainMissingDb = db.getSiblingDB("explainMissingDb");
+
+// .find()
+explainMissingDb.dropDatabase();
+explain = explainMissingDb.collection.explain("executionStats").find().finish();
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .count()
+explainMissingDb.dropDatabase();
+explain = explainMissingDb.collection.explain("executionStats").count();
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .group()
+explainMissingDb.dropDatabase();
+explainColl = explainMissingDb.collection.explain("executionStats");
+explain = explainColl.group({key: "a", initial: {}, reduce: function() { } });
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .remove()
+explainMissingDb.dropDatabase();
+explain = explainMissingDb.collection.explain("executionStats").remove({a: 1});
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .update() with upsert: false
+explainMissingDb.dropDatabase();
+explainColl = explainMissingDb.collection.explain("executionStats");
+explain = explainColl.update({a: 1}, {b: 1});
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .update() with upsert: true
+explainMissingDb.dropDatabase();
+explainColl = explainMissingDb.collection.explain("executionStats");
+explain = explainColl.update({a: 1}, {b: 1}, {upsert: true});
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .aggregate()
+explainMissingDb.dropDatabase();
+explain = explainMissingDb.collection.explain("executionStats").aggregate([{$match: {a: 1}}]);
+assert.commandWorked(explain);
+
+//
+// Part 2: Non-existent collection.
+//
+
+var missingColl = db.explain_null_collection;
+
+// .find()
+missingColl.drop();
+explain = missingColl.explain("executionStats").find().finish();
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .count()
+missingColl.drop();
+explain = missingColl.explain("executionStats").count();
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .group()
+missingColl.drop();
+explainColl = missingColl.explain("executionStats");
+explain = explainColl.group({key: "a", initial: {}, reduce: function() { } });
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .remove()
+missingColl.drop();
+explain = missingColl.explain("executionStats").remove({a: 1});
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .update() with upsert: false
+missingColl.drop();
+explainColl = missingColl.explain("executionStats");
+explain = explainColl.update({a: 1}, {b: 1});
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .update() with upsert: true
+missingColl.drop();
+explainColl = missingColl.explain("executionStats");
+explain = explainColl.update({a: 1}, {b: 1}, {upsert: true});
+assert.commandWorked(explain);
+assert("executionStats" in explain);
+
+// .aggregate()
+missingColl.drop();
+explain = missingColl.explain("executionStats").aggregate([{$match: {a: 1}}]);
+assert.commandWorked(explain);
diff --git a/src/mongo/db/commands/write_commands/write_commands.cpp b/src/mongo/db/commands/write_commands/write_commands.cpp
index f1f7c2a3919..e20ff8c883a 100644
--- a/src/mongo/db/commands/write_commands/write_commands.cpp
+++ b/src/mongo/db/commands/write_commands/write_commands.cpp
@@ -47,6 +47,7 @@
#include "mongo/db/repl/repl_coordinator_global.h"
#include "mongo/db/server_parameters.h"
#include "mongo/db/stats/counters.h"
+#include "mongo/s/d_state.h"
namespace mongo {
@@ -214,11 +215,16 @@ namespace mongo {
// Explains of write commands are read-only, but we take write locks so
// that timing info is more accurate.
- Lock::DBLock dlk(txn->lockState(), nsString.db(), MODE_IX);
- Lock::CollectionLock colLock(txn->lockState(), nsString.ns(), MODE_IX);
- Client::Context ctx(txn, nsString);
+ AutoGetDb autoDb( txn, nsString.db(), MODE_IX );
+ Lock::CollectionLock colLock( txn->lockState(), nsString.ns(), MODE_IX );
+
+ // We check the shard version explicitly here rather than using Client::Context,
+ // as Context can do implicit database creation if the db does not exist. We want
+ // explain to be a no-op that reports a trivial EOF plan against non-existent dbs
+ // or collections.
+ ensureShardVersionOKOrThrow( nsString.ns() );
- Status prepInLockStatus = updateExecutor.prepareInLock(ctx.db());
+ Status prepInLockStatus = updateExecutor.prepareInLock( autoDb.getDb() );
if ( !prepInLockStatus.isOK() ) {
return prepInLockStatus;
}
@@ -255,12 +261,15 @@ namespace mongo {
// Explains of write commands are read-only, but we take write locks so that timing
// info is more accurate.
AutoGetDb autoDb(txn, nsString.db(), MODE_IX);
- if (!autoDb.getDb()) return Status::OK();
-
Lock::CollectionLock colLock(txn->lockState(), nsString.ns(), MODE_IX);
- Client::Context ctx(txn, nsString);
- Status prepInLockStatus = deleteExecutor.prepareInLock(ctx.db());
+ // We check the shard version explicitly here rather than using Client::Context,
+ // as Context can do implicit database creation if the db does not exist. We want
+ // explain to be a no-op that reports a trivial EOF plan against non-existent dbs
+ // or collections.
+ ensureShardVersionOKOrThrow( nsString.ns() );
+
+ Status prepInLockStatus = deleteExecutor.prepareInLock( autoDb.getDb() );
if ( !prepInLockStatus.isOK()) {
return prepInLockStatus;
}
diff --git a/src/mongo/db/exec/update.cpp b/src/mongo/db/exec/update.cpp
index 59ba5593ad6..1c5c8ed4011 100644
--- a/src/mongo/db/exec/update.cpp
+++ b/src/mongo/db/exec/update.cpp
@@ -395,20 +395,17 @@ namespace mongo {
UpdateStage::UpdateStage(const UpdateStageParams& params,
WorkingSet* ws,
- Database* db,
+ Collection* collection,
PlanStage* child)
: _params(params),
_ws(ws),
- _db(db),
+ _collection(collection),
_child(child),
_commonStats(kStageType),
_updatedLocs(params.request->isMulti() ? new DiskLocSet() : NULL),
_doc(params.driver->getDocument()) {
// We are an update until we fall into the insert case.
params.driver->setContext(ModifierInterface::ExecInfo::UPDATE_CONTEXT);
-
- _collection = db->getCollection(params.request->getOpCtx(),
- params.request->getNamespaceString().ns());
}
void UpdateStage::transformAndUpdate(BSONObj& oldObj, DiskLoc& loc) {
diff --git a/src/mongo/db/exec/update.h b/src/mongo/db/exec/update.h
index b543ebb5fdf..8e097bac999 100644
--- a/src/mongo/db/exec/update.h
+++ b/src/mongo/db/exec/update.h
@@ -28,7 +28,7 @@
#pragma once
-#include "mongo/db/catalog/database.h"
+#include "mongo/db/catalog/collection.h"
#include "mongo/db/exec/plan_stage.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/ops/update_driver.h"
@@ -78,7 +78,7 @@ namespace mongo {
public:
UpdateStage(const UpdateStageParams& params,
WorkingSet* ws,
- Database* db,
+ Collection* collection,
PlanStage* child);
virtual bool isEOF();
@@ -135,8 +135,7 @@ namespace mongo {
// Not owned by us.
WorkingSet* _ws;
- // Not owned by us.
- Database* _db;
+ // Not owned by us. May be NULL.
Collection* _collection;
// Owned by us.
diff --git a/src/mongo/db/ops/delete_executor.cpp b/src/mongo/db/ops/delete_executor.cpp
index b66f153dac1..655babffb1c 100644
--- a/src/mongo/db/ops/delete_executor.cpp
+++ b/src/mongo/db/ops/delete_executor.cpp
@@ -110,10 +110,14 @@ namespace mongo {
}
}
- // Note that 'collection' may by NULL in the case that the collection we are trying to
- // delete from does not exist. NULL 'collection' is handled by getExecutorDelete(); we
- // expect to get back a plan executor whose plan is a DeleteStage on top of an EOFStage.
- Collection* collection = db->getCollection(_request->getOpCtx(), ns.ns());
+ // Note that 'collection' may by NULL in the case that the collection or database we are
+ // trying to delete from does not exist. NULL 'collection' is handled by
+ // getExecutorDelete(); we expect to get back a plan executor whose plan is a DeleteStage
+ // on top of an EOFStage.
+ Collection* collection = NULL;
+ if (db) {
+ collection = db->getCollection(_request->getOpCtx(), ns.ns());
+ }
if (collection && collection->isCapped()) {
return Status(ErrorCodes::IllegalOperation,
diff --git a/src/mongo/db/ops/update_executor.cpp b/src/mongo/db/ops/update_executor.cpp
index d403ea9edeb..cd9ea94dd80 100644
--- a/src/mongo/db/ops/update_executor.cpp
+++ b/src/mongo/db/ops/update_executor.cpp
@@ -111,7 +111,17 @@ namespace mongo {
validateUpdate(nsString.ns().c_str(), _request->getUpdates(), _request->getQuery());
- Collection* collection = db->getCollection(_request->getOpCtx(), nsString.ns());
+ // The batch executor is responsible for creating a database if this update is being
+ // performed against a non-existent database. However, it is possible for either the
+ // database or collection to be NULL for an explain. In this case, the explain is
+ // a no-op which returns a trivial EOF plan.
+ Collection* collection = NULL;
+ if (db) {
+ collection = db->getCollection(_request->getOpCtx(), nsString.ns());
+ }
+ else {
+ invariant(_request->isExplain());
+ }
// The update stage does not create its own collection. As such, if the update is
// an upsert, create the collection that the update stage inserts into beforehand.
@@ -177,14 +187,26 @@ namespace mongo {
Status getExecStatus = Status::OK();
if (_canonicalQuery.get()) {
// This is the regular path for when we have a CanonicalQuery.
- getExecStatus = getExecutorUpdate(_request->getOpCtx(), db, _canonicalQuery.release(),
- _request, &_driver, _opDebug, policy, &rawExec);
+ getExecStatus = getExecutorUpdate(_request->getOpCtx(),
+ collection,
+ _canonicalQuery.release(),
+ _request,
+ &_driver,
+ _opDebug,
+ policy,
+ &rawExec);
}
else {
// This is the idhack fast-path for getting a PlanExecutor without doing the work
// to create a CanonicalQuery.
- getExecStatus = getExecutorUpdate(_request->getOpCtx(), db, nsString.ns(), _request,
- &_driver, _opDebug, policy, &rawExec);
+ getExecStatus = getExecutorUpdate(_request->getOpCtx(),
+ collection,
+ nsString.ns(),
+ _request,
+ &_driver,
+ _opDebug,
+ policy,
+ &rawExec);
}
if (!getExecStatus.isOK()) {
diff --git a/src/mongo/db/query/get_executor.cpp b/src/mongo/db/query/get_executor.cpp
index 8c474c0f948..a26118061ec 100644
--- a/src/mongo/db/query/get_executor.cpp
+++ b/src/mongo/db/query/get_executor.cpp
@@ -545,7 +545,7 @@ namespace mongo {
//
Status getExecutorUpdate(OperationContext* txn,
- Database* db,
+ Collection* collection,
CanonicalQuery* rawCanonicalQuery,
const UpdateRequest* request,
UpdateDriver* driver,
@@ -554,8 +554,6 @@ namespace mongo {
PlanExecutor** execOut) {
auto_ptr<CanonicalQuery> canonicalQuery(rawCanonicalQuery);
auto_ptr<WorkingSet> ws(new WorkingSet());
- Collection* collection = db->getCollection(request->getOpCtx(),
- request->getNamespaceString().ns());
PlanStage* root;
QuerySolution* querySolution;
@@ -568,7 +566,7 @@ namespace mongo {
invariant(root);
UpdateStageParams updateStageParams(request, driver, opDebug);
updateStageParams.canonicalQuery = rawCanonicalQuery;
- root = new UpdateStage(updateStageParams, ws.get(), db, root);
+ root = new UpdateStage(updateStageParams, ws.get(), collection, root);
// We must have a tree of stages in order to have a valid plan executor, but the query
// solution may be null. Takes ownership of all args other than 'collection' and 'txn'
return PlanExecutor::make(txn,
@@ -582,7 +580,7 @@ namespace mongo {
}
Status getExecutorUpdate(OperationContext* txn,
- Database* db,
+ Collection* collection,
const std::string& ns,
const UpdateRequest* request,
UpdateDriver* driver,
@@ -590,8 +588,6 @@ namespace mongo {
PlanExecutor::YieldPolicy yieldPolicy,
PlanExecutor** execOut) {
auto_ptr<WorkingSet> ws(new WorkingSet());
- Collection* collection = db->getCollection(request->getOpCtx(),
- request->getNamespaceString().ns());
const BSONObj& unparsedQuery = request->getQuery();
UpdateStageParams updateStageParams(request, driver, opDebug);
@@ -601,7 +597,7 @@ namespace mongo {
// UpdateStage, so in this case we put an UpdateStage on top of an EOFStage.
LOG(2) << "Collection " << ns << " does not exist."
<< " Using EOF stage: " << unparsedQuery.toString();
- UpdateStage* updateStage = new UpdateStage(updateStageParams, ws.get(), db,
+ UpdateStage* updateStage = new UpdateStage(updateStageParams, ws.get(), collection,
new EOFStage());
return PlanExecutor::make(txn, ws.release(), updateStage, ns, yieldPolicy, execOut);
}
@@ -612,7 +608,8 @@ namespace mongo {
PlanStage* idHackStage = new IDHackStage(txn, collection, unparsedQuery["_id"].wrap(),
ws.get());
- UpdateStage* root = new UpdateStage(updateStageParams, ws.get(), db, idHackStage);
+ UpdateStage* root = new UpdateStage(updateStageParams, ws.get(), collection,
+ idHackStage);
return PlanExecutor::make(txn, ws.release(), root, collection, yieldPolicy, execOut);
}
@@ -627,7 +624,8 @@ namespace mongo {
return status;
// Takes ownership of 'cq'.
- return getExecutorUpdate(txn, db, cq, request, driver, opDebug, yieldPolicy, execOut);
+ return getExecutorUpdate(txn, collection, cq, request, driver, opDebug, yieldPolicy,
+ execOut);
}
//
diff --git a/src/mongo/db/query/get_executor.h b/src/mongo/db/query/get_executor.h
index 86e8156dfd6..6828dbe4b13 100644
--- a/src/mongo/db/query/get_executor.h
+++ b/src/mongo/db/query/get_executor.h
@@ -37,7 +37,6 @@
namespace mongo {
class Collection;
- class Database;
struct CountRequest;
struct GroupRequest;
@@ -192,7 +191,7 @@ namespace mongo {
* If the query cannot be executed, returns a Status indicating why.
*/
Status getExecutorUpdate(OperationContext* txn,
- Database* db,
+ Collection* collection,
CanonicalQuery* rawCanonicalQuery,
const UpdateRequest* request,
UpdateDriver* driver,
@@ -212,7 +211,7 @@ namespace mongo {
* If the query cannot be executed, returns a Status indicating why.
*/
Status getExecutorUpdate(OperationContext* txn,
- Database* db,
+ Collection* collection,
const std::string& ns,
const UpdateRequest* request,
UpdateDriver* driver,
diff --git a/src/mongo/dbtests/query_stage_update.cpp b/src/mongo/dbtests/query_stage_update.cpp
index b0e18879361..54357ebc954 100644
--- a/src/mongo/dbtests/query_stage_update.cpp
+++ b/src/mongo/dbtests/query_stage_update.cpp
@@ -185,7 +185,7 @@ namespace QueryStageUpdate {
CurOp& curOp = *c.curop();
OpDebug* opDebug = &curOp.debug();
UpdateDriver driver( (UpdateDriver::Options()) );
- Database* db = ctx.ctx().db();
+ Collection* collection = ctx.getCollection();
// Collection should be empty.
ASSERT_EQUALS(0U, count(BSONObj()));
@@ -213,7 +213,7 @@ namespace QueryStageUpdate {
auto_ptr<EOFStage> eofStage(new EOFStage());
scoped_ptr<UpdateStage> updateStage(
- new UpdateStage(params, ws.get(), db, eofStage.release()));
+ new UpdateStage(params, ws.get(), collection, eofStage.release()));
runUpdate(updateStage.get());
}
@@ -292,7 +292,7 @@ namespace QueryStageUpdate {
new CollectionScan(&_txn, collScanParams, ws.get(), cq->root()));
scoped_ptr<UpdateStage> updateStage(
- new UpdateStage(updateParams, ws.get(), db, cs.release()));
+ new UpdateStage(updateParams, ws.get(), coll, cs.release()));
const UpdateStats* stats =
static_cast<const UpdateStats*>(updateStage->getSpecificStats());