diff options
-rw-r--r-- | src/mongo/client/dbclientcursor.cpp | 161 | ||||
-rw-r--r-- | src/mongo/client/dbclientcursor.h | 43 | ||||
-rw-r--r-- | src/mongo/client/dbclientmockcursor.h | 2 | ||||
-rw-r--r-- | src/mongo/db/commands/find_cmd.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/commands/getmore_cmd.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/dbmessage.h | 2 | ||||
-rw-r--r-- | src/mongo/db/query/cursor_response.h | 4 | ||||
-rw-r--r-- | src/mongo/db/query/query_request.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/query/query_request.h | 12 | ||||
-rw-r--r-- | src/mongo/db/query/query_request_test.cpp | 69 | ||||
-rw-r--r-- | src/mongo/db/repl/oplogreader.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_mongod.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/session.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/transaction_history_iterator.cpp | 3 | ||||
-rw-r--r-- | src/mongo/dbtests/directclienttests.cpp | 14 | ||||
-rw-r--r-- | src/mongo/dbtests/mock/mock_dbclient_cursor.h | 4 | ||||
-rw-r--r-- | src/mongo/dbtests/querytests.cpp | 56 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/mongo.cpp | 7 | ||||
-rw-r--r-- | src/mongo/shell/bench.cpp | 26 |
19 files changed, 277 insertions, 166 deletions
diff --git a/src/mongo/client/dbclientcursor.cpp b/src/mongo/client/dbclientcursor.cpp index a4514dc59d2..9dbf90f3491 100644 --- a/src/mongo/client/dbclientcursor.cpp +++ b/src/mongo/client/dbclientcursor.cpp @@ -37,6 +37,9 @@ #include "mongo/db/client.h" #include "mongo/db/dbmessage.h" #include "mongo/db/namespace_string.h" +#include "mongo/db/query/cursor_response.h" +#include "mongo/db/query/getmore_request.h" +#include "mongo/db/query/query_request.h" #include "mongo/rpc/factory.h" #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/rpc/metadata.h" @@ -87,36 +90,70 @@ int DBClientCursor::nextBatchSize() { return batchSize < nToReturn ? batchSize : nToReturn; } -void DBClientCursor::_assembleInit(Message& toSend) { +Message DBClientCursor::_assembleInit() { + if (cursorId) { + return _assembleGetMore(); + } + // If we haven't gotten a cursorId yet, we need to issue a new query or command. - if (!cursorId) { - if (_isCommand) { - // HACK: - // Unfortunately, this code is used by the shell to run commands, - // so we need to allow the shell to send invalid options so that we can - // test that the server rejects them. Thus, to allow generating commands with - // invalid options, we validate them here, and fall back to generating an OP_QUERY - // through assembleQueryRequest if the options are invalid. - bool hasValidNToReturnForCommand = (nToReturn == 1 || nToReturn == -1); - bool hasValidFlagsForCommand = !(opts & mongo::QueryOption_Exhaust); - bool hasInvalidMaxTimeMs = query.hasField("$maxTimeMS"); - - if (hasValidNToReturnForCommand && hasValidFlagsForCommand && !hasInvalidMaxTimeMs) { - toSend = assembleCommandRequest(_client, nsToDatabaseSubstring(ns), opts, query); - return; + if (_isCommand) { + // HACK: + // Unfortunately, this code is used by the shell to run commands, + // so we need to allow the shell to send invalid options so that we can + // test that the server rejects them. Thus, to allow generating commands with + // invalid options, we validate them here, and fall back to generating an OP_QUERY + // through assembleQueryRequest if the options are invalid. + bool hasValidNToReturnForCommand = (nToReturn == 1 || nToReturn == -1); + bool hasValidFlagsForCommand = !(opts & mongo::QueryOption_Exhaust); + bool hasInvalidMaxTimeMs = query.hasField("$maxTimeMS"); + + if (hasValidNToReturnForCommand && hasValidFlagsForCommand && !hasInvalidMaxTimeMs) { + return assembleCommandRequest(_client, ns.db(), opts, query); + } + } else if (_useFindCommand) { + auto qr = QueryRequest::fromLegacyQuery(ns, + query, + fieldsToReturn ? *fieldsToReturn : BSONObj(), + nToSkip, + nextBatchSize(), + opts); + if (qr.isOK() && !qr.getValue()->isExplain() && !qr.getValue()->isExhaust()) { + BSONObj cmd = qr.getValue()->asFindCommand(); + if (auto readPref = query["$readPreference"]) { + // QueryRequest doesn't handle $readPreference. + cmd = BSONObjBuilder(std::move(cmd)).append(readPref).obj(); } + return assembleCommandRequest(_client, ns.db(), opts, std::move(cmd)); } - assembleQueryRequest(ns, query, nextBatchSize(), nToSkip, fieldsToReturn, opts, toSend); - return; + // else use legacy OP_QUERY request. + } + + _useFindCommand = false; // Make sure we handle the reply correctly. + Message toSend; + assembleQueryRequest(ns.ns(), query, nextBatchSize(), nToSkip, fieldsToReturn, opts, toSend); + return toSend; +} + +Message DBClientCursor::_assembleGetMore() { + invariant(cursorId); + if (_useFindCommand) { + long long batchSize = nextBatchSize(); + auto gmr = GetMoreRequest(ns, + cursorId, + boost::make_optional(batchSize != 0, batchSize), + boost::none, // awaitDataTimeout + boost::none, // term + boost::none); // lastKnownCommittedOptime + return assembleCommandRequest(_client, ns.db(), opts, gmr.toBSON()); + } else { + // Assemble a legacy getMore request. + return makeGetMoreMessage(ns.ns(), cursorId, nextBatchSize(), opts); } - // Assemble a legacy getMore request. - toSend = makeGetMoreMessage(ns, cursorId, nToReturn, opts); } bool DBClientCursor::init() { invariant(!_connectionHasPendingReplies); - Message toSend; - _assembleInit(toSend); + Message toSend = _assembleInit(); verify(_client); Message reply; if (!_client->call(toSend, reply, false, &_originalHost)) { @@ -137,8 +174,7 @@ void DBClientCursor::initLazy(bool isRetry) { massert(15875, "DBClientCursor::initLazy called on a client that doesn't support lazy", _client->lazySupported()); - Message toSend; - _assembleInit(toSend); + Message toSend = _assembleInit(); _client->say(toSend, isRetry, &_originalHost); _lastRequestId = toSend.header().getId(); _connectionHasPendingReplies = true; @@ -176,21 +212,27 @@ void DBClientCursor::requestMore() { verify(nToReturn > 0); } - Message toSend = makeGetMoreMessage(ns, cursorId, nextBatchSize(), opts); + ON_BLOCK_EXIT([ this, origClient = _client ] { _client = origClient; }); + boost::optional<ScopedDbConnection> connHolder; + if (!_client) { + invariant(_scopedHost.size()); + connHolder.emplace(_scopedHost); + _client = connHolder->get(); + } + + Message toSend = _assembleGetMore(); Message response; + _client->call(toSend, response); + + // If call() succeeds, the connection is clean so we can return it to the pool, even if + // dataReceived() throws because the command reported failure. However, we can't return it yet, + // because dataReceived() needs to get the metadata reader from the connection. + ON_BLOCK_EXIT([&] { + if (connHolder) + connHolder->done(); + }); - if (_client) { - _client->call(toSend, response); - dataReceived(response); - } else { - verify(_scopedHost.size()); - ScopedDbConnection conn(_scopedHost); - conn->call(toSend, response); - _client = conn.get(); - ON_BLOCK_EXIT([this] { _client = nullptr; }); - dataReceived(response); - conn.done(); - } + dataReceived(response); } /** with QueryOption_Exhaust, the server just blasts data at us (marked at end with cursorid==0). */ @@ -205,15 +247,11 @@ void DBClientCursor::exhaustReceiveMore() { dataReceived(response); } -void DBClientCursor::commandDataReceived(const Message& reply) { +BSONObj DBClientCursor::commandDataReceived(const Message& reply) { int op = reply.operation(); invariant(op == opReply || op == dbCommandReply || op == dbMsg); - batch.objs.clear(); - batch.pos = 0; - auto commandReply = rpc::makeReply(&reply); - auto commandStatus = getStatusFromCommandResult(commandReply->getCommandReply()); if (ErrorCodes::SendStaleConfig == commandStatus) { @@ -229,13 +267,25 @@ void DBClientCursor::commandDataReceived(const Message& reply) { opCtx, commandReply->getMetadata(), _client->getServerAddress())); } - batch.objs.push_back(commandReply->getCommandReply().getOwned()); + return commandReply->getCommandReply().getOwned(); } void DBClientCursor::dataReceived(const Message& reply, bool& retry, string& host) { + batch.objs.clear(); + batch.pos = 0; + // If this is a reply to our initial command request. if (_isCommand && cursorId == 0) { - commandDataReceived(reply); + batch.objs.push_back(commandDataReceived(reply)); + return; + } + + if (_useFindCommand) { + cursorId = 0; // Don't try to kill cursor if we get back an error. + auto cr = uassertStatusOK(CursorResponse::parseFromBSON(commandDataReceived(reply))); + cursorId = cr.getCursorId(); + ns = cr.getNSS(); // Unlike OP_REPLY, find command can change the ns to use for getMores. + batch.objs = cr.releaseBatch(); return; } @@ -272,8 +322,6 @@ void DBClientCursor::dataReceived(const Message& reply, bool& retry, string& hos _lastRequestId = reply.header().getId(); } - batch.pos = 0; - batch.objs.clear(); batch.objs.reserve(qr.getNReturned()); BufReader data(qr.data(), qr.dataLen()); @@ -460,13 +508,16 @@ DBClientCursor::DBClientCursor(DBClientBase* client, haveLimit(nToReturn > 0 && !(queryOptions & QueryOption_CursorTailable)), nToSkip(nToSkip), fieldsToReturn(fieldsToReturn), - opts(queryOptions), + opts(queryOptions & ~QueryOptionLocal_forceOpQuery), batchSize(batchSize == 1 ? 2 : batchSize), resultFlags(0), cursorId(cursorId), _ownCursor(true), wasError(false), - _enabledBSONVersion(Validator<BSONObj>::enabledBSONVersion()) {} + _enabledBSONVersion(Validator<BSONObj>::enabledBSONVersion()) { + if (queryOptions & QueryOptionLocal_forceOpQuery) + _useFindCommand = false; +} DBClientCursor::~DBClientCursor() { kill(); @@ -475,14 +526,22 @@ DBClientCursor::~DBClientCursor() { void DBClientCursor::kill() { DESTRUCTOR_GUARD({ if (cursorId && _ownCursor && !globalInShutdownDeprecated()) { - auto toSend = makeKillCursorsMessage(cursorId); + auto killCursor = [&](auto& conn) { + if (_useFindCommand) { + conn->killCursor(ns, cursorId); + } else { + auto toSend = makeKillCursorsMessage(cursorId); + conn->say(toSend); + } + }; + if (_client && !_connectionHasPendingReplies) { - _client->say(toSend); + killCursor(_client); } else { // Use a side connection to send the kill cursor request. verify(_scopedHost.size() || (_client && _connectionHasPendingReplies)); ScopedDbConnection conn(_client ? _client->getServerAddress() : _scopedHost); - conn->say(toSend); + killCursor(conn); conn.done(); } } diff --git a/src/mongo/client/dbclientcursor.h b/src/mongo/client/dbclientcursor.h index 40e8cbf92e3..e8c8e423ea2 100644 --- a/src/mongo/client/dbclientcursor.h +++ b/src/mongo/client/dbclientcursor.h @@ -41,28 +41,13 @@ namespace mongo { class AScopedConnection; -/** for mock purposes only -- do not create variants of DBClientCursor, nor hang code here - @see DBClientMockCursor - */ -class DBClientCursorInterface { - MONGO_DISALLOW_COPYING(DBClientCursorInterface); - -public: - virtual ~DBClientCursorInterface() {} - virtual bool more() = 0; - virtual BSONObj next() = 0; - // TODO bring more of the DBClientCursor interface to here -protected: - DBClientCursorInterface() {} -}; - /** Queries return a cursor object */ -class DBClientCursor : public DBClientCursorInterface { +class DBClientCursor { MONGO_DISALLOW_COPYING(DBClientCursor); public: /** If true, safe to call next(). Requests more from server if necessary. */ - bool more(); + virtual bool more(); /** If true, there is more in our local buffers to be fetched via next(). Returns false when a getMore request back to server would be required. You can use this @@ -82,7 +67,7 @@ public: { $err: <std::string> } if you do not want to handle that yourself, call nextSafe(). */ - BSONObj next(); + virtual BSONObj next(); /** restore an object previously returned by next() to the cursor @@ -147,6 +132,13 @@ public: batchSize = newBatchSize; } + + /** + * Fold this in with queryOptions to force the use of legacy query operations. + * This flag is never sent over the wire and is only used locally. + */ + enum { QueryOptionLocal_forceOpQuery = 1 << 30 }; + DBClientCursor(DBClientBase* client, const std::string& ns, const BSONObj& query, @@ -182,7 +174,7 @@ public: } std::string getns() const { - return ns; + return ns.ns(); } /** @@ -244,7 +236,7 @@ private: Batch batch; DBClientBase* _client; std::string _originalHost; - const std::string ns; + NamespaceString ns; const bool _isCommand; BSONObj query; int nToReturn; @@ -261,6 +253,7 @@ private: std::string _lazyHost; bool wasError; BSONVersion _enabledBSONVersion; + bool _useFindCommand = true; bool _connectionHasPendingReplies = false; int _lastRequestId = 0; @@ -272,16 +265,16 @@ private: void dataReceived(const Message& reply, bool& retry, std::string& lazyHost); /** - * Called by dataReceived when the query was actually a command. Parses the command reply - * according to the RPC protocol used to send it, and then fills in the internal field - * of this cursor with the received data. + * Parses and returns command replies regardless of which command protocol was used. + * Does *not* parse replies from non-command OP_QUERY finds. */ - void commandDataReceived(const Message& reply); + BSONObj commandDataReceived(const Message& reply); void requestMore(); // init pieces - void _assembleInit(Message& toSend); + Message _assembleInit(); + Message _assembleGetMore(); }; /** iterate over objects in current batch only - will not cause a network call diff --git a/src/mongo/client/dbclientmockcursor.h b/src/mongo/client/dbclientmockcursor.h index 89f42066b6b..749d8fd837d 100644 --- a/src/mongo/client/dbclientmockcursor.h +++ b/src/mongo/client/dbclientmockcursor.h @@ -33,7 +33,7 @@ namespace mongo { -class DBClientMockCursor : public DBClientCursorInterface { +class DBClientMockCursor { public: DBClientMockCursor(const BSONArray& mockCollection) : _iter(mockCollection) {} virtual ~DBClientMockCursor() {} diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp index 8d89469a27f..38712e3e920 100644 --- a/src/mongo/db/commands/find_cmd.cpp +++ b/src/mongo/db/commands/find_cmd.cpp @@ -227,12 +227,6 @@ public: // Although it is a command, a find command gets counted as a query. globalOpCounters.gotQuery(); - if (opCtx->getClient()->isInDirectClient()) { - return appendCommandStatus( - result, - Status(ErrorCodes::IllegalOperation, "Cannot run find command from eval()")); - } - // Parse the command BSON to a QueryRequest. const bool isExplain = false; auto qrStatus = QueryRequest::makeFromFindCommand(nss, cmdObj, isExplain); diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp index d41867d16b9..58a1622c49b 100644 --- a/src/mongo/db/commands/getmore_cmd.cpp +++ b/src/mongo/db/commands/getmore_cmd.cpp @@ -198,11 +198,6 @@ public: opCtx, *nssForCurOp, Top::LockType::NotLocked, dbProfilingLevel); } } else { - // getMore commands are always unversioned, so prevent AutoGetCollectionForRead from - // checking the shard version. - OperationShardingState::get(opCtx).setShardVersion(request.nss, - ChunkVersion::IGNORED()); - readLock.emplace(opCtx, request.nss); const int doNotChangeProfilingLevel = 0; statsTracker.emplace(opCtx, @@ -399,12 +394,6 @@ public: // Counted as a getMore, not as a command. globalOpCounters.gotGetMore(); - if (opCtx->getClient()->isInDirectClient()) { - return appendCommandStatus( - result, - Status(ErrorCodes::IllegalOperation, "Cannot run getMore command from eval()")); - } - StatusWith<GetMoreRequest> parsedRequest = GetMoreRequest::parseFromBSON(dbname, cmdObj); if (!parsedRequest.isOK()) { return appendCommandStatus(result, parsedRequest.getStatus()); diff --git a/src/mongo/db/dbmessage.h b/src/mongo/db/dbmessage.h index 61dc3429303..a7228cbc4be 100644 --- a/src/mongo/db/dbmessage.h +++ b/src/mongo/db/dbmessage.h @@ -340,6 +340,8 @@ enum QueryOptions { */ QueryOption_PartialResults = 1 << 7, + // DBClientCursor reserves flag 1 << 30 to force the use of OP_QUERY. + QueryOption_AllSupported = QueryOption_CursorTailable | QueryOption_SlaveOk | QueryOption_OplogReplay | QueryOption_NoCursorTimeout | QueryOption_AwaitData | QueryOption_Exhaust | QueryOption_PartialResults, diff --git a/src/mongo/db/query/cursor_response.h b/src/mongo/db/query/cursor_response.h index 2eed3db4901..9811c29b0ba 100644 --- a/src/mongo/db/query/cursor_response.h +++ b/src/mongo/db/query/cursor_response.h @@ -155,6 +155,10 @@ public: return _batch; } + std::vector<BSONObj> releaseBatch() { + return std::move(_batch); + } + boost::optional<long long> getNumReturnedSoFar() const { return _numReturnedSoFar; } diff --git a/src/mongo/db/query/query_request.cpp b/src/mongo/db/query/query_request.cpp index 2bf9f12a15c..6e4d0745b2a 100644 --- a/src/mongo/db/query/query_request.cpp +++ b/src/mongo/db/query/query_request.cpp @@ -726,12 +726,12 @@ StatusWith<unique_ptr<QueryRequest>> QueryRequest::fromLegacyQueryMessage(const return std::move(qr); } -StatusWith<unique_ptr<QueryRequest>> QueryRequest::fromLegacyQueryForTest(NamespaceString nss, - const BSONObj& queryObj, - const BSONObj& proj, - int ntoskip, - int ntoreturn, - int queryOptions) { +StatusWith<unique_ptr<QueryRequest>> QueryRequest::fromLegacyQuery(NamespaceString nss, + const BSONObj& queryObj, + const BSONObj& proj, + int ntoskip, + int ntoreturn, + int queryOptions) { auto qr = stdx::make_unique<QueryRequest>(nss); Status status = qr->init(ntoskip, ntoreturn, queryOptions, queryObj, proj, true); diff --git a/src/mongo/db/query/query_request.h b/src/mongo/db/query/query_request.h index a8ca956c43b..620e50d8804 100644 --- a/src/mongo/db/query/query_request.h +++ b/src/mongo/db/query/query_request.h @@ -404,12 +404,12 @@ public: /** * Parse the provided legacy query object and parameters to construct a QueryRequest. */ - static StatusWith<std::unique_ptr<QueryRequest>> fromLegacyQueryForTest(NamespaceString nss, - const BSONObj& queryObj, - const BSONObj& proj, - int ntoskip, - int ntoreturn, - int queryOptions); + static StatusWith<std::unique_ptr<QueryRequest>> fromLegacyQuery(NamespaceString nss, + const BSONObj& queryObj, + const BSONObj& proj, + int ntoskip, + int ntoreturn, + int queryOptions); private: Status init(int ntoskip, diff --git a/src/mongo/db/query/query_request_test.cpp b/src/mongo/db/query/query_request_test.cpp index f64a5429faf..4910b769af9 100644 --- a/src/mongo/db/query/query_request_test.cpp +++ b/src/mongo/db/query/query_request_test.cpp @@ -32,6 +32,7 @@ #include <boost/optional.hpp> #include <boost/optional/optional_io.hpp> +#include "mongo/db/dbmessage.h" #include "mongo/db/json.h" #include "mongo/db/namespace_string.h" #include "mongo/db/pipeline/aggregation_request.h" @@ -1336,10 +1337,11 @@ TEST(QueryRequestTest, ParseFromLegacyObjMetaOpComment) { "$comment: {b: 2, c: {d: 'ParseFromLegacyObjMetaOpComment'}}}"); const NamespaceString nss("test.testns"); unique_ptr<QueryRequest> qr( - assertGet(QueryRequest::fromLegacyQueryForTest(nss, queryObj, BSONObj(), 0, 0, 0))); + assertGet(QueryRequest::fromLegacyQuery(nss, queryObj, BSONObj(), 0, 0, 0))); // Ensure that legacy comment meta-operator is parsed to a string comment ASSERT_EQ(qr->getComment(), "{ b: 2, c: { d: \"ParseFromLegacyObjMetaOpComment\" } }"); + ASSERT_BSONOBJ_EQ(qr->getFilter(), fromjson("{a: 1}")); } TEST(QueryRequestTest, ParseFromLegacyStringMetaOpComment) { @@ -1348,9 +1350,72 @@ TEST(QueryRequestTest, ParseFromLegacyStringMetaOpComment) { "$comment: 'ParseFromLegacyStringMetaOpComment'}"); const NamespaceString nss("test.testns"); unique_ptr<QueryRequest> qr( - assertGet(QueryRequest::fromLegacyQueryForTest(nss, queryObj, BSONObj(), 0, 0, 0))); + assertGet(QueryRequest::fromLegacyQuery(nss, queryObj, BSONObj(), 0, 0, 0))); ASSERT_EQ(qr->getComment(), "ParseFromLegacyStringMetaOpComment"); + ASSERT_BSONOBJ_EQ(qr->getFilter(), fromjson("{a: 1}")); +} + +TEST(QueryRequestTest, ParseFromLegacyQuery) { + const auto kSkip = 1; + const auto kNToReturn = 2; + + BSONObj queryObj = fromjson(R"({ + query: {query: 1}, + orderby: {sort: 1}, + $hint: {hint: 1}, + $explain: false, + $min: {x: 'min'}, + $max: {x: 'max'}, + $maxScan: 7 + })"); + const NamespaceString nss("test.testns"); + unique_ptr<QueryRequest> qr(assertGet(QueryRequest::fromLegacyQuery( + nss, queryObj, BSON("proj" << 1), kSkip, kNToReturn, QueryOption_Exhaust))); + + ASSERT_EQ(qr->nss(), nss); + ASSERT_BSONOBJ_EQ(qr->getFilter(), fromjson("{query: 1}")); + ASSERT_BSONOBJ_EQ(qr->getProj(), fromjson("{proj: 1}")); + ASSERT_BSONOBJ_EQ(qr->getSort(), fromjson("{sort: 1}")); + ASSERT_BSONOBJ_EQ(qr->getHint(), fromjson("{hint: 1}")); + ASSERT_BSONOBJ_EQ(qr->getMin(), fromjson("{x: 'min'}")); + ASSERT_BSONOBJ_EQ(qr->getMax(), fromjson("{x: 'max'}")); + ASSERT_EQ(qr->getSkip(), boost::optional<long long>(kSkip)); + ASSERT_EQ(qr->getNToReturn(), boost::optional<long long>(kNToReturn)); + ASSERT_EQ(qr->wantMore(), true); + ASSERT_EQ(qr->isExplain(), false); + ASSERT_EQ(qr->getMaxScan(), 7); + ASSERT_EQ(qr->isSlaveOk(), false); + ASSERT_EQ(qr->isOplogReplay(), false); + ASSERT_EQ(qr->isNoCursorTimeout(), false); + ASSERT_EQ(qr->isAwaitData(), false); + ASSERT_EQ(qr->isExhaust(), true); + ASSERT_EQ(qr->isAllowPartialResults(), false); + ASSERT_EQ(qr->getOptions(), QueryOption_Exhaust); +} + +TEST(QueryRequestTest, ParseFromLegacyQueryUnwrapped) { + BSONObj queryObj = fromjson(R"({ + foo: 1 + })"); + const NamespaceString nss("test.testns"); + unique_ptr<QueryRequest> qr(assertGet( + QueryRequest::fromLegacyQuery(nss, queryObj, BSONObj(), 0, 0, QueryOption_Exhaust))); + + ASSERT_EQ(qr->nss(), nss); + ASSERT_BSONOBJ_EQ(qr->getFilter(), fromjson("{foo: 1}")); +} + +TEST(QueryRequestTest, ParseFromLegacyQueryTooNegativeNToReturn) { + BSONObj queryObj = fromjson(R"({ + foo: 1 + })"); + const NamespaceString nss("test.testns"); + + ASSERT_NOT_OK( + QueryRequest::fromLegacyQuery( + nss, queryObj, BSONObj(), 0, std::numeric_limits<int>::min(), QueryOption_Exhaust) + .getStatus()); } } // namespace mongo diff --git a/src/mongo/db/repl/oplogreader.cpp b/src/mongo/db/repl/oplogreader.cpp index 7455ef7e83a..744045df7f3 100644 --- a/src/mongo/db/repl/oplogreader.cpp +++ b/src/mongo/db/repl/oplogreader.cpp @@ -68,6 +68,9 @@ OplogReader::OplogReader() { /* TODO: slaveOk maybe shouldn't use? */ _tailingQueryOptions |= QueryOption_AwaitData; + + // Currently find command doesn't do the cursor tracking that master-slave relies on. + _tailingQueryOptions |= DBClientCursor::QueryOptionLocal_forceOpQuery; } bool OplogReader::connect(const HostAndPort& host) { diff --git a/src/mongo/db/service_entry_point_mongod.cpp b/src/mongo/db/service_entry_point_mongod.cpp index c575d5a05be..55c76621605 100644 --- a/src/mongo/db/service_entry_point_mongod.cpp +++ b/src/mongo/db/service_entry_point_mongod.cpp @@ -592,7 +592,7 @@ void execCommandDatabase(OperationContext* opCtx, repl::ReplicationCoordinator::get(opCtx->getClient()->getServiceContext()); const bool iAmPrimary = replCoord->canAcceptWritesForDatabase_UNSAFE(opCtx, dbname); - { + if (!opCtx->getClient()->isInDirectClient()) { bool commandCanRunOnSecondary = command->slaveOk(); bool commandIsOverriddenToRunOnSecondary = diff --git a/src/mongo/db/session.cpp b/src/mongo/db/session.cpp index 0811996eb8f..060ba2cd133 100644 --- a/src/mongo/db/session.cpp +++ b/src/mongo/db/session.cpp @@ -52,8 +52,10 @@ boost::optional<SessionTxnRecord> loadSessionRecord(OperationContext* opCtx, const LogicalSessionId& sessionId) { DBDirectClient client(opCtx); Query sessionQuery(BSON(SessionTxnRecord::kSessionIdFieldName << sessionId.toBSON())); - auto result = - client.findOne(NamespaceString::kSessionTransactionsTableNamespace.ns(), sessionQuery); + auto result = client.findOne(NamespaceString::kSessionTransactionsTableNamespace.ns(), + sessionQuery, + nullptr, + DBClientCursor::QueryOptionLocal_forceOpQuery); // SERVER-30318 if (result.isEmpty()) { return boost::none; diff --git a/src/mongo/db/transaction_history_iterator.cpp b/src/mongo/db/transaction_history_iterator.cpp index 024204e338f..6bdbb276d40 100644 --- a/src/mongo/db/transaction_history_iterator.cpp +++ b/src/mongo/db/transaction_history_iterator.cpp @@ -54,7 +54,8 @@ repl::OplogEntry TransactionHistoryIterator::next(OperationContext* opCtx) { client.findOne(NamespaceString::kRsOplogNamespace.ns(), BSON(repl::OplogEntryBase::kTimestampFieldName << _nextOpTimeTs), /* fieldsToReturn */ nullptr, - 0 /* QueryOption_OplogReplay */); + DBClientCursor::QueryOptionLocal_forceOpQuery // SERVER-30318 + /* QueryOption_OplogReplay */); uassert(ErrorCodes::IncompleteTransactionHistory, str::stream() << "oplog no longer contains the complete write history of this " diff --git a/src/mongo/dbtests/directclienttests.cpp b/src/mongo/dbtests/directclienttests.cpp index afa371c4694..f62c14aef22 100644 --- a/src/mongo/dbtests/directclienttests.cpp +++ b/src/mongo/dbtests/directclienttests.cpp @@ -137,11 +137,8 @@ public: OperationContext& opCtx = *opCtxPtr; DBDirectClient client(&opCtx); - unique_ptr<DBClientCursor> cursor = client.query("", Query(), 1); - ASSERT(cursor->more()); - BSONObj result = cursor->next().getOwned(); - ASSERT(result.hasField("$err")); - ASSERT_EQUALS(result["code"].Int(), ErrorCodes::InvalidNamespace); + ASSERT_THROWS_CODE( + client.query("", Query(), 1)->nextSafe(), UserException, ErrorCodes::InvalidNamespace); } }; @@ -152,11 +149,8 @@ public: OperationContext& opCtx = *opCtxPtr; DBDirectClient client(&opCtx); - unique_ptr<DBClientCursor> cursor = client.getMore("", 1, 1); - ASSERT(cursor->more()); - BSONObj result = cursor->next().getOwned(); - ASSERT(result.hasField("$err")); - ASSERT_EQUALS(result["code"].Int(), ErrorCodes::InvalidNamespace); + ASSERT_THROWS_CODE( + client.getMore("", 1, 1)->nextSafe(), UserException, ErrorCodes::InvalidNamespace); } }; diff --git a/src/mongo/dbtests/mock/mock_dbclient_cursor.h b/src/mongo/dbtests/mock/mock_dbclient_cursor.h index 19ba5515c76..762bd77b7c2 100644 --- a/src/mongo/dbtests/mock/mock_dbclient_cursor.h +++ b/src/mongo/dbtests/mock/mock_dbclient_cursor.h @@ -43,13 +43,13 @@ class MockDBClientCursor : public mongo::DBClientCursor { public: MockDBClientCursor(mongo::DBClientBase* client, const mongo::BSONArray& mockCollection); - bool more(); + bool more() override; /** * Note: has the same contract as DBClientCursor - returned BSONObj will * become invalid when this cursor is destroyed. */ - mongo::BSONObj next(); + mongo::BSONObj next() override; private: std::unique_ptr<mongo::DBClientMockCursor> _cursor; diff --git a/src/mongo/dbtests/querytests.cpp b/src/mongo/dbtests/querytests.cpp index 772cd354c0b..841086a01f2 100644 --- a/src/mongo/dbtests/querytests.cpp +++ b/src/mongo/dbtests/querytests.cpp @@ -295,8 +295,7 @@ public: }; /** - * An exception triggered during a get more request destroys the ClientCursor used by the get - * more, preventing further iteration of the cursor in subsequent get mores. + * Setting killAllOperations causes further getmores to fail. */ class GetMoreKillOp : public ClientBase { public: @@ -313,7 +312,6 @@ public: // Create a cursor on the collection, with a batch size of 200. unique_ptr<DBClientCursor> cursor = _client.query(ns, "", 0, 0, 0, 0, 200); - CursorId cursorId = cursor->getCursorId(); // Count 500 results, spanning a few batches of documents. for (int i = 0; i < 500; ++i) { @@ -324,23 +322,16 @@ public: // Set the killop kill all flag, forcing the next get more to fail with a kill op // exception. getGlobalServiceContext()->setKillAllOperations(); - while (cursor->more()) { - cursor->next(); - } + ASSERT_THROWS_CODE(([&] { + while (cursor->more()) { + cursor->next(); + } + }()), + UserException, + ErrorCodes::InterruptedAtShutdown); // Revert the killop kill all flag. getGlobalServiceContext()->unsetKillAllOperations(); - - // Check that the cursor has been removed. - { - AutoGetCollectionForReadCommand ctx(&_opCtx, NamespaceString(ns)); - ASSERT(0 == ctx.getCollection()->getCursorManager()->numCursors()); - } - - ASSERT_FALSE(CursorManager::eraseCursorGlobal(&_opCtx, cursorId)); - - // Check that a subsequent get more fails with the cursor removed. - ASSERT_THROWS(_client.getMore(ns, cursorId), UserException); } }; @@ -376,8 +367,10 @@ public: // Send a get more with a namespace that is incorrect ('spoofed') for this cursor id. // This is the invalaid get more request described in the comment preceding this class. - _client.getMore("unittests.querytests.GetMoreInvalidRequest_WRONG_NAMESPACE_FOR_CURSOR", - cursor->getCursorId()); + ASSERT_THROWS( + _client.getMore("unittests.querytests.GetMoreInvalidRequest_WRONG_NAMESPACE_FOR_CURSOR", + cursor->getCursorId()), + UserException); // Check that the cursor still exists { @@ -486,9 +479,7 @@ public: insert(ns, BSON("a" << 3)); // We have overwritten the previous cursor position and should encounter a dead cursor. - if (c->more()) { - ASSERT_THROWS(c->nextSafe(), AssertionException); - } + ASSERT_THROWS(c->more() ? c->nextSafe() : BSONObj(), AssertionException); } }; @@ -512,9 +503,7 @@ public: insert(ns, BSON("a" << 4)); // We have overwritten the previous cursor position and should encounter a dead cursor. - if (c->more()) { - ASSERT_THROWS(c->nextSafe(), AssertionException); - } + ASSERT_THROWS(c->more() ? c->nextSafe() : BSONObj(), AssertionException); } }; @@ -550,9 +539,8 @@ public: void run() { const char* ns = "unittests.querytests.TailCappedOnly"; _client.insert(ns, BSONObj()); - unique_ptr<DBClientCursor> c = - _client.query(ns, BSONObj(), 0, 0, 0, QueryOption_CursorTailable); - ASSERT(c->isDead()); + ASSERT_THROWS(_client.query(ns, BSONObj(), 0, 0, 0, QueryOption_CursorTailable), + UserException); } }; @@ -687,7 +675,8 @@ public: 0, 0, 0, - QueryOption_OplogReplay | QueryOption_CursorTailable); + QueryOption_OplogReplay | QueryOption_CursorTailable | + DBClientCursor::QueryOptionLocal_forceOpQuery); ASSERT(c->more()); ASSERT_EQUALS(two, c->next()["ts"].Date()); long long cursorId = c->getCursorId(); @@ -1348,9 +1337,12 @@ public: insertNext(); } - while (c->more()) { - c->next(); - } + ASSERT_THROWS(([&] { + while (c->more()) { + c->nextSafe(); + } + }()), + UserException); } void insertNext() { diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp index 859c6cddff4..4689325c5b5 100644 --- a/src/mongo/scripting/mozjs/mongo.cpp +++ b/src/mongo/scripting/mozjs/mongo.cpp @@ -289,6 +289,9 @@ void MongoBase::Functions::find::call(JSContext* cx, JS::CallArgs args) { int batchSize = ValueWriter(cx, args.get(5)).toInt32(); int options = ValueWriter(cx, args.get(6)).toInt32(); + // The shell only calls this method when it wants to test OP_QUERY. + options |= DBClientCursor::QueryOptionLocal_forceOpQuery; + std::unique_ptr<DBClientCursor> cursor( conn->query(ns, q, nToReturn, nToSkip, haveFields ? &fields : NULL, options, batchSize)); if (!cursor.get()) { @@ -505,7 +508,9 @@ void MongoBase::Functions::cursorFromId::call(JSContext* cx, JS::CallArgs args) long long cursorId = NumberLongInfo::ToNumberLong(cx, args.get(1)); - auto cursor = stdx::make_unique<DBClientCursor>(conn, ns, cursorId, 0, 0); + // The shell only calls this method when it wants to test OP_GETMORE. + auto cursor = stdx::make_unique<DBClientCursor>( + conn, ns, cursorId, 0, DBClientCursor::QueryOptionLocal_forceOpQuery); if (args.get(2).isNumber()) cursor->setBatchSize(ValueWriter(cx, args.get(2)).toInt32()); diff --git a/src/mongo/shell/bench.cpp b/src/mongo/shell/bench.cpp index 754f2bb091b..0c1ad2eb943 100644 --- a/src/mongo/shell/bench.cpp +++ b/src/mongo/shell/bench.cpp @@ -746,7 +746,10 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) { runQueryWithReadCommands(conn, std::move(qr), &result); } else { BenchRunEventTrace _bret(&stats.findOneCounter); - result = conn->findOne(op.ns, fixedQuery); + result = conn->findOne(op.ns, + fixedQuery, + nullptr, + DBClientCursor::QueryOptionLocal_forceOpQuery); } if (op.useCheck) { @@ -858,17 +861,22 @@ void BenchRunWorker::generateLoadOnConnection(DBClientBase* conn) { BenchRunEventTrace _bret(&stats.queryCounter); stdx::function<void(const BSONObj&)> castedDoNothing(doNothing); count = conn->query( - castedDoNothing, op.ns, fixedQuery, &op.projection, op.options); + castedDoNothing, + op.ns, + fixedQuery, + &op.projection, + op.options | DBClientCursor::QueryOptionLocal_forceOpQuery); } else { BenchRunEventTrace _bret(&stats.queryCounter); unique_ptr<DBClientCursor> cursor; - cursor = conn->query(op.ns, - fixedQuery, - op.limit, - op.skip, - &op.projection, - op.options, - op.batchSize); + cursor = conn->query( + op.ns, + fixedQuery, + op.limit, + op.skip, + &op.projection, + op.options | DBClientCursor::QueryOptionLocal_forceOpQuery, + op.batchSize); count = cursor->itcount(); } } |