diff options
author | David Storch <david.storch@10gen.com> | 2014-11-07 17:19:18 -0500 |
---|---|---|
committer | David Storch <david.storch@10gen.com> | 2014-11-12 15:37:37 -0500 |
commit | fdc16e8d1d2346ca152528b71538942efb3ce4db (patch) | |
tree | 40283dee62d30ebb76584b81b02112455d03b9c0 | |
parent | 578c5c5bdbf43378da11f706be0d9a54daba0929 (diff) | |
download | mongo-fdc16e8d1d2346ca152528b71538942efb3ce4db.tar.gz |
SERVER-15917 fix explain of write commands against a non-existent database
-rw-r--r-- | jstests/core/explain_null_collection.js | 104 | ||||
-rw-r--r-- | src/mongo/db/commands/write_commands/write_commands.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/exec/update.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/exec/update.h | 7 | ||||
-rw-r--r-- | src/mongo/db/ops/delete_executor.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/ops/update_executor.cpp | 32 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.cpp | 18 | ||||
-rw-r--r-- | src/mongo/db/query/get_executor.h | 5 | ||||
-rw-r--r-- | src/mongo/dbtests/query_stage_update.cpp | 6 |
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()); |