summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/client/dbclientcursor.cpp161
-rw-r--r--src/mongo/client/dbclientcursor.h43
-rw-r--r--src/mongo/client/dbclientmockcursor.h2
-rw-r--r--src/mongo/db/commands/find_cmd.cpp6
-rw-r--r--src/mongo/db/commands/getmore_cmd.cpp11
-rw-r--r--src/mongo/db/dbmessage.h2
-rw-r--r--src/mongo/db/query/cursor_response.h4
-rw-r--r--src/mongo/db/query/query_request.cpp12
-rw-r--r--src/mongo/db/query/query_request.h12
-rw-r--r--src/mongo/db/query/query_request_test.cpp69
-rw-r--r--src/mongo/db/repl/oplogreader.cpp3
-rw-r--r--src/mongo/db/service_entry_point_mongod.cpp2
-rw-r--r--src/mongo/db/session.cpp6
-rw-r--r--src/mongo/db/transaction_history_iterator.cpp3
-rw-r--r--src/mongo/dbtests/directclienttests.cpp14
-rw-r--r--src/mongo/dbtests/mock/mock_dbclient_cursor.h4
-rw-r--r--src/mongo/dbtests/querytests.cpp56
-rw-r--r--src/mongo/scripting/mozjs/mongo.cpp7
-rw-r--r--src/mongo/shell/bench.cpp26
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();
}
}