summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandolph Tan <randolph@10gen.com>2013-02-07 13:34:44 -0500
committerRandolph Tan <randolph@10gen.com>2013-02-13 18:16:16 -0500
commitae2256441b1c96768b270ee6a866d9669d4b15ec (patch)
treea2021bb9b399f04b969c66c61e8935f4bd7cf5eb
parent66bc2d1afbff04c304c8072175044fa8d2bdf371 (diff)
downloadmongo-ae2256441b1c96768b270ee6a866d9669d4b15ec.tar.gz
SERVER-7636 Do not use cached connection when read preference changes
-rw-r--r--src/mongo/SConscript12
-rw-r--r--src/mongo/client/dbclient.cpp44
-rw-r--r--src/mongo/client/dbclient_rs.cpp99
-rw-r--r--src/mongo/client/dbclient_rs.h54
-rw-r--r--src/mongo/client/dbclient_rs_test.cpp245
-rw-r--r--src/mongo/client/dbclientinterface.h12
-rw-r--r--src/mongo/db/queryutil.h3
-rw-r--r--src/mongo/s/strategy_single.cpp5
8 files changed, 416 insertions, 58 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 4d4ba8f04b4..a17abb5e486 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -487,6 +487,18 @@ env.CppUnitTest( "balancer_policy_test" , [ "s/balancer_policy_tests.cpp" ] ,
LIBDEPS=["mongoscore", "coreshard", "mongocommon","coreserver","coredb","dbcmdline","mongodandmongos"] ,
NO_CRUTCH=True)
+env.CppUnitTest("dbclient_rs_test", [ "client/dbclient_rs_test.cpp" ],
+ LIBDEPS=[
+ "coredb",
+ "coreserver",
+ "coreshard",
+ "dbcmdline",
+ "mocklib",
+ "mongocommon",
+ "mongodandmongos",
+ "mongoscore"],
+ NO_CRUTCH=True)
+
env.CppUnitTest("scoped_db_conn_test", [ "client/scoped_db_conn_test.cpp" ],
LIBDEPS=[
"coredb",
diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp
index 30ffb07ab68..22de7f4167f 100644
--- a/src/mongo/client/dbclient.cpp
+++ b/src/mongo/client/dbclient.cpp
@@ -238,6 +238,9 @@ namespace mongo {
return "";
}
+ const BSONField<BSONObj> Query::ReadPrefField("$readPreference");
+ const BSONField<string> Query::ReadPrefModeField("mode");
+ const BSONField<BSONArray> Query::ReadPrefTagsField("tags");
Query::Query( const string &json ) : obj( fromjson( json ) ) {}
@@ -307,14 +310,51 @@ namespace mongo {
return false;
}
+ Query& Query::readPref(ReadPreference pref, const BSONArray& tags) {
+ string mode;
+
+ switch (pref) {
+ case ReadPreference_PrimaryOnly:
+ mode = "primary";
+ break;
+
+ case ReadPreference_PrimaryPreferred:
+ mode = "primaryPreferred";
+ break;
+
+ case ReadPreference_SecondaryOnly:
+ mode = "secondary";
+ break;
+
+ case ReadPreference_SecondaryPreferred:
+ mode = "secondaryPreferred";
+ break;
+
+ case ReadPreference_Nearest:
+ mode = "nearest";
+ break;
+ }
+
+ BSONObjBuilder readPrefDocBuilder;
+ readPrefDocBuilder << ReadPrefModeField(mode);
+
+ if (!tags.isEmpty()) {
+ readPrefDocBuilder << ReadPrefTagsField(tags);
+ }
+
+ appendComplex(ReadPrefField.name().c_str(), readPrefDocBuilder.done());
+ return *this;
+ }
+
bool Query::isComplex( bool * hasDollar ) const {
return isComplex(obj, hasDollar);
}
bool Query::hasReadPreference(const BSONObj& queryObj) {
const bool hasReadPrefOption = queryObj["$queryOptions"].isABSONObj() &&
- queryObj["$queryOptions"].Obj().hasField("$readPreference");
- return (Query::isComplex(queryObj) && queryObj.hasField("$readPreference")) ||
+ queryObj["$queryOptions"].Obj().hasField(ReadPrefField.name());
+ return (Query::isComplex(queryObj) &&
+ queryObj.hasField(ReadPrefField.name())) ||
hasReadPrefOption;
}
diff --git a/src/mongo/client/dbclient_rs.cpp b/src/mongo/client/dbclient_rs.cpp
index 0947d1c2a8d..b0a7a753208 100644
--- a/src/mongo/client/dbclient_rs.cpp
+++ b/src/mongo/client/dbclient_rs.cpp
@@ -192,76 +192,71 @@ namespace mongo {
* { <actual query>, $queryOptions: { $readPreference: <read pref obj> }}
*
* @param query the raw query document
- * @param pref an out parameter and will contain the read preference mode extracted
- * from the query object.
*
- * @return the tag set list. If the tags field was not present, it will contain one
- * empty tag document {} which matches any tag. Caller owns the TagSet object
- * and is responsible for deletion.
+ * @return the read preference setting. If the tags field was not present, it will contain one
+ * empty tag document {} which matches any tag.
*
* @throws AssertionException if the read preference object is malformed
*/
- TagSet* _extractReadPref(const BSONObj& query, ReadPreference* pref) {
- if (!Query::hasReadPreference(query)) {
- *pref = mongo::ReadPreference_SecondaryPreferred;
- }
- else {
+ ReadPreferenceSetting* _extractReadPref(const BSONObj& query) {
+ ReadPreference pref = mongo::ReadPreference_SecondaryPreferred;
+
+ if (Query::hasReadPreference(query)) {
BSONElement readPrefElement;
- if (query.hasField("$readPreference")) {
- readPrefElement = query["$readPreference"];
+ if (query.hasField(Query::ReadPrefField.name())) {
+ readPrefElement = query[Query::ReadPrefField.name()];
}
else {
- readPrefElement = query["$queryOptions"]["$readPreference"];
+ readPrefElement = query["$queryOptions"][Query::ReadPrefField.name()];
}
uassert(16381, "$readPreference should be an object",
readPrefElement.isABSONObj());
const BSONObj& prefDoc = readPrefElement.Obj();
- uassert(16382, "mode not specified for read preference", prefDoc.hasField("mode"));
+ uassert(16382, "mode not specified for read preference",
+ prefDoc.hasField(Query::ReadPrefModeField.name()));
- const string mode = prefDoc["mode"].String();
+ const string mode = prefDoc[Query::ReadPrefModeField.name()].String();
if (mode == "primary") {
- *pref = mongo::ReadPreference_PrimaryOnly;
+ pref = mongo::ReadPreference_PrimaryOnly;
}
else if (mode == "primaryPreferred") {
- *pref = mongo::ReadPreference_PrimaryPreferred;
+ pref = mongo::ReadPreference_PrimaryPreferred;
}
else if (mode == "secondary") {
- *pref = mongo::ReadPreference_SecondaryOnly;
+ pref = mongo::ReadPreference_SecondaryOnly;
}
else if (mode == "secondaryPreferred") {
- *pref = mongo::ReadPreference_SecondaryPreferred;
+ pref = mongo::ReadPreference_SecondaryPreferred;
}
else if (mode == "nearest") {
- *pref = mongo::ReadPreference_Nearest;
+ pref = mongo::ReadPreference_Nearest;
}
else {
uasserted(16383, str::stream() << "Unknown read preference mode: " << mode);
}
- if (prefDoc.hasField("tags")) {
-
-
- const BSONElement& tagsElem = prefDoc["tags"];
+ if (prefDoc.hasField(Query::ReadPrefTagsField.name())) {
+ const BSONElement& tagsElem = prefDoc[Query::ReadPrefTagsField.name()];
uassert(16385, "tags for read preference should be an array",
tagsElem.type() == mongo::Array);
std::auto_ptr<TagSet> tags(new TagSet(BSONArray(tagsElem.Obj())));
- if (*pref == mongo::ReadPreference_PrimaryOnly && !tags->isExhausted()) {
+ if (pref == mongo::ReadPreference_PrimaryOnly && !tags->isExhausted()) {
uassert(16384, "Only empty tags are allowed with primary read preference",
tags->getCurrentTag().isEmpty());
}
- return tags.release();
+ return new ReadPreferenceSetting(pref, tags.release());
}
}
BSONArrayBuilder arrayBuilder;
arrayBuilder.append(BSONObj());
- return new TagSet(arrayBuilder.arr());
+ return new ReadPreferenceSetting(pref, new TagSet(arrayBuilder.arr()));
}
/**
@@ -1508,7 +1503,7 @@ namespace mongo {
return _master.get();
}
- bool DBClientReplicaSet::checkLastHost(ReadPreference preference, const TagSet* tags) {
+ bool DBClientReplicaSet::checkLastHost(const ReadPreferenceSetting* readPref) {
if (_lastSlaveOkHost.empty()) {
return false;
}
@@ -1520,7 +1515,7 @@ namespace mongo {
return false;
}
- return _lastSlaveOkConn && monitor->isHostCompatible(_lastSlaveOkHost, preference, tags);
+ return _lastSlaveOkConn && _lastReadPref && _lastReadPref->equals(*readPref);
}
void DBClientReplicaSet::_auth( DBClientConnection * conn ) {
@@ -1542,9 +1537,10 @@ namespace mongo {
DBClientConnection& DBClientReplicaSet::slaveConn() {
BSONArray emptyArray;
- TagSet tags( emptyArray );
- DBClientConnection* conn = selectNodeUsingTags( ReadPreference_SecondaryPreferred,
- &tags );
+ TagSet tags(emptyArray);
+ shared_ptr<ReadPreferenceSetting> readPref(new ReadPreferenceSetting(
+ ReadPreference_SecondaryPreferred, new TagSet(emptyArray)));
+ DBClientConnection* conn = selectNodeUsingTags(readPref);
uassert( 16369, str::stream() << "No good nodes available for set: "
<< _getMonitor()->getName(), conn != NULL );
@@ -1631,12 +1627,11 @@ namespace mongo {
int queryOptions,
int batchSize) {
if (_isQueryOkToSecondary(ns, queryOptions, query.obj)) {
- ReadPreference pref;
- scoped_ptr<TagSet> tags(_extractReadPref(query.obj, &pref));
+ shared_ptr<ReadPreferenceSetting> readPref(_extractReadPref(query.obj));
for (size_t retry = 0; retry < MAX_RETRY; retry++) {
try {
- DBClientConnection* conn = selectNodeUsingTags(pref, tags.get());
+ DBClientConnection* conn = selectNodeUsingTags(readPref);
if (conn == NULL) {
break;
@@ -1668,12 +1663,11 @@ namespace mongo {
const BSONObj *fieldsToReturn,
int queryOptions) {
if (_isQueryOkToSecondary(ns, queryOptions, query.obj)) {
- ReadPreference pref;
- scoped_ptr<TagSet> tags(_extractReadPref(query.obj, &pref));
+ shared_ptr<ReadPreferenceSetting> readPref(_extractReadPref(query.obj));
for (size_t retry = 0; retry < MAX_RETRY; retry++) {
try {
- DBClientConnection* conn = selectNodeUsingTags(pref, tags.get());
+ DBClientConnection* conn = selectNodeUsingTags(readPref);
if (conn == NULL) {
break;
@@ -1741,20 +1735,23 @@ namespace mongo {
_lastSlaveOkConn.reset();
}
- DBClientConnection* DBClientReplicaSet::selectNodeUsingTags(ReadPreference preference,
- TagSet* tags) {
- if (checkLastHost(preference, tags)) {
+ DBClientConnection* DBClientReplicaSet::selectNodeUsingTags(
+ shared_ptr<ReadPreferenceSetting> readPref) {
+ if (checkLastHost(readPref.get())) {
return _lastSlaveOkConn.get();
}
ReplicaSetMonitorPtr monitor = _getMonitor();
bool isPrimarySelected = false;
- _lastSlaveOkHost = monitor->selectAndCheckNode(preference, tags, &isPrimarySelected);
+ _lastSlaveOkHost = monitor->selectAndCheckNode(readPref->pref, readPref->tags,
+ &isPrimarySelected);
if ( _lastSlaveOkHost.empty() ){
return NULL;
}
+ _lastReadPref = readPref;
+
// Primary connection is special because it is the only connection that is
// versioned in mongos. Therefore, we have to make sure that this object
// maintains only one connection to the primary and use that connection
@@ -1802,13 +1799,12 @@ namespace mongo {
const bool slaveOk = qm.queryOptions & QueryOption_SlaveOk;
if (_isQueryOkToSecondary(qm.ns, qm.queryOptions, qm.query)) {
- ReadPreference pref;
- scoped_ptr<TagSet> tags(_extractReadPref(qm.query, &pref));
+ shared_ptr<ReadPreferenceSetting> readPref(_extractReadPref(qm.query));
for (size_t retry = 0; retry < MAX_RETRY; retry++) {
_lazyState._retries = retry;
try {
- DBClientConnection* conn = selectNodeUsingTags(pref, tags.get());
+ DBClientConnection* conn = selectNodeUsingTags(readPref);
if (conn == NULL) {
break;
@@ -1945,12 +1941,11 @@ namespace mongo {
ns = qm.ns;
if (_isQueryOkToSecondary(ns, qm.queryOptions, qm.query)) {
- ReadPreference pref;
- scoped_ptr<TagSet> tags(_extractReadPref(qm.query, &pref));
+ shared_ptr<ReadPreferenceSetting> readPref(_extractReadPref(qm.query));
for (size_t retry = 0; retry < MAX_RETRY; retry++) {
try {
- DBClientConnection* conn = selectNodeUsingTags(pref, tags.get());
+ DBClientConnection* conn = selectNodeUsingTags(readPref);
if (conn == NULL) {
return false;
@@ -2046,4 +2041,12 @@ namespace mongo {
BSONObjIterator* TagSet::getIterator() const {
return new BSONObjIterator(_tags);
}
+
+ TagSet* TagSet::clone() const {
+ return new TagSet(BSONArray(_tags.copy()));
+ }
+
+ bool TagSet::equals(const TagSet& other) const {
+ return _tags.equal(other._tags);
+ }
}
diff --git a/src/mongo/client/dbclient_rs.h b/src/mongo/client/dbclient_rs.h
index 34737b479ef..45eabead460 100644
--- a/src/mongo/client/dbclient_rs.h
+++ b/src/mongo/client/dbclient_rs.h
@@ -31,6 +31,7 @@ namespace mongo {
class ReplicaSetMonitor;
class TagSet;
+ struct ReadPreferenceSetting;
typedef shared_ptr<ReplicaSetMonitor> ReplicaSetMonitorPtr;
typedef pair<set<string>,set<int> > NodeDiff;
@@ -470,6 +471,14 @@ namespace mongo {
* @return the reference to the address that points to the master connection.
*/
DBClientConnection& masterConn();
+
+ /**
+ * WARNING: this method is very dangerous - this object can decide to free the
+ * returned master connection any time. This can also unpin the cached
+ * slaveOk/read preference connection.
+ *
+ * @return the reference to the address that points to a secondary connection.
+ */
DBClientConnection& slaveConn();
// ---- callback pieces -------
@@ -530,8 +539,7 @@ namespace mongo {
* Helper method for selecting a node based on the read preference. Will advance
* the tag tags object if it cannot find a node that matches the current tag.
*
- * @param preference the preference to use for selecting a node
- * @param tags pointer to the list of tags.
+ * @param readPref the preference to use for selecting a node.
*
* @return a pointer to the new connection object if it can find a good connection.
* Otherwise it returns NULL.
@@ -539,14 +547,13 @@ namespace mongo {
* @throws DBException when an error occurred either when trying to connect to
* a node that was thought to be ok or when an assertion happened.
*/
- DBClientConnection* selectNodeUsingTags(ReadPreference preference,
- TagSet* tags);
+ DBClientConnection* selectNodeUsingTags(shared_ptr<ReadPreferenceSetting> readPref);
/**
* @return true if the last host used in the last slaveOk query is still in the
* set and can be used for the given read preference.
*/
- bool checkLastHost( ReadPreference preference, const TagSet* tags );
+ bool checkLastHost(const ReadPreferenceSetting* readPref);
/**
* Destroys all cached information about the last slaveOk operation.
@@ -579,6 +586,7 @@ namespace mongo {
HostAndPort _lastSlaveOkHost;
// Last used connection in a slaveOk query (can be a primary)
boost::shared_ptr<DBClientConnection> _lastSlaveOkConn;
+ boost::shared_ptr<ReadPreferenceSetting> _lastReadPref;
double _so_timeout;
@@ -652,6 +660,18 @@ namespace mongo {
*/
BSONObjIterator* getIterator() const;
+ /**
+ * Create a new copy of this tag set and wuth the iterator pointing at the
+ * head.
+ */
+ TagSet* clone() const;
+
+ /**
+ * @returns true if the other TagSet has the same tag set specification with
+ * this tag set, disregarding where the current iterator is pointing to.
+ */
+ bool equals(const TagSet& other) const;
+
private:
BSONObj _currentTag;
bool _isExhausted;
@@ -660,4 +680,28 @@ namespace mongo {
BSONArray _tags;
BSONArrayIteratorSorted _tagIterator;
};
+
+ struct ReadPreferenceSetting {
+ /**
+ * @param tag cannot be NULL.
+ */
+ ReadPreferenceSetting(ReadPreference pref, TagSet* tag):
+ pref(pref), tags(tag->clone()) {
+ }
+
+ ~ReadPreferenceSetting() {
+ delete tags;
+ }
+
+ inline bool equals(const ReadPreferenceSetting& other) const {
+ return pref == other.pref && tags->equals(*other.tags);
+ }
+
+ const ReadPreference pref;
+
+ /**
+ * Note: This object owns this memory.
+ */
+ TagSet* tags;
+ };
}
diff --git a/src/mongo/client/dbclient_rs_test.cpp b/src/mongo/client/dbclient_rs_test.cpp
new file mode 100644
index 00000000000..1e71afebc56
--- /dev/null
+++ b/src/mongo/client/dbclient_rs_test.cpp
@@ -0,0 +1,245 @@
+/**
+ * Copyright (C) 2013 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * This file contains tests for DBClientReplicaSet.
+ */
+
+#include "mongo/bson/bson_field.h"
+#include "mongo/client/connpool.h"
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/client/dbclient_rs.h"
+#include "mongo/dbtests/mock/mock_conn_registry.h"
+#include "mongo/dbtests/mock/mock_replica_set.h"
+#include "mongo/unittest/unittest.h"
+//#include "mongo/util/mongoutils/str.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+using std::map;
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::vector;
+using boost::scoped_ptr;
+
+using mongo::BSONField;
+using mongo::BSONObj;
+using mongo::BSONArray;
+using mongo::BSONElement;
+using mongo::ConnectionString;
+using mongo::DBClientReplicaSet;
+using mongo::HostAndPort;
+using mongo::MockReplicaSet;
+using mongo::Query;
+using mongo::ReadPreference;
+using mongo::ReplicaSetMonitor;
+using mongo::ScopedDbConnection;
+using mongo::TagSet;
+
+namespace mongo {
+ // Symbols defined to build the binary correctly.
+ CmdLine cmdLine;
+
+ bool inShutdown() {
+ return false;
+ }
+
+ DBClientBase *createDirectClient() { return NULL; }
+
+ void dbexit(ExitCode rc, const char *why){
+ ::_exit(rc);
+ }
+
+ bool haveLocalShardingInfo(const string& ns) {
+ return false;
+ }
+}
+
+namespace mongo_test {
+ /**
+ * Warning: Tests running this fixture cannot be run in parallel with other tests
+ * that uses ConnectionString::setConnectionHook
+ */
+ class TaggedFiveMemberRS: public mongo::unittest::Test {
+ protected:
+ static const string IdentityNS;
+ static const BSONField<string> HostField;
+
+ void setUp() {
+ _replSet.reset(new MockReplicaSet("test", 5));
+ _originalConnectionHook = ConnectionString::getConnectionHook();
+ ConnectionString::setConnectionHook(
+ mongo::MockConnRegistry::get()->getConnStrHook());
+
+ {
+ mongo::MockReplicaSet::ReplConfigMap config = _replSet->getReplConfig();
+
+ {
+ const string host(_replSet->getPrimary());
+ map<string, string>& tag = config[host].tags;
+ tag.clear();
+ tag["dc"] = "ny";
+ tag["p"] = "1";
+ _replSet->getNode(host)->insert(IdentityNS, BSON(HostField(host)));
+ }
+
+ vector<string> secNodes = _replSet->getSecondaries();
+ vector<string>::const_iterator secIter = secNodes.begin();
+
+ {
+ const string host(*secIter);
+ map<string, string>& tag = config[host].tags;
+ tag.clear();
+ tag["dc"] = "sf";
+ tag["s"] = "1";
+ tag["group"] = "1";
+ _replSet->getNode(host)->insert(IdentityNS, BSON(HostField(host)));
+ }
+
+ {
+ ++secIter;
+ const string host(*secIter);
+ map<string, string>& tag = config[host].tags;
+ tag.clear();
+ tag["dc"] = "ma";
+ tag["s"] = "2";
+ tag["group"] = "1";
+ _replSet->getNode(host)->insert(IdentityNS, BSON(HostField(host)));
+ }
+
+ {
+ ++secIter;
+ const string host(*secIter);
+ map<string, string>& tag = config[host].tags;
+ tag.clear();
+ tag["dc"] = "eu";
+ tag["s"] = "3";
+ _replSet->getNode(host)->insert(IdentityNS, BSON(HostField(host)));
+ }
+
+ {
+ ++secIter;
+ const string host(*secIter);
+ map<string, string>& tag = config[host].tags;
+ tag.clear();
+ tag["dc"] = "jp";
+ tag["s"] = "4";
+ _replSet->getNode(host)->insert(IdentityNS, BSON(HostField(host)));
+ }
+
+ _replSet->setConfig(config);
+ }
+ }
+
+ void tearDown() {
+ ConnectionString::setConnectionHook(_originalConnectionHook);
+ ReplicaSetMonitor::remove(_replSet->getSetName(), true);
+ _replSet.reset();
+ mongo::ScopedDbConnection::clearPool();
+ }
+
+ MockReplicaSet* getReplSet() {
+ return _replSet.get();
+ }
+
+ private:
+ ConnectionString::ConnectionHook* _originalConnectionHook;
+ boost::scoped_ptr<MockReplicaSet> _replSet;
+ };
+
+ const string TaggedFiveMemberRS::IdentityNS("local.me");
+ const BSONField<string> TaggedFiveMemberRS::HostField("host", "bad");
+
+ TEST_F(TaggedFiveMemberRS, ConnShouldPinIfSameSettings) {
+ MockReplicaSet* replSet = getReplSet();
+ vector<HostAndPort> seedList;
+ seedList.push_back(HostAndPort(replSet->getPrimary()));
+
+ DBClientReplicaSet replConn(replSet->getSetName(), seedList);
+
+ string dest;
+ {
+ Query query;
+ query.readPref(mongo::ReadPreference_PrimaryPreferred, BSONArray());
+ BSONObj doc = replConn.query(IdentityNS, query)->next();
+ dest = doc[HostField.name()].str();
+ }
+
+ {
+ Query query;
+ query.readPref(mongo::ReadPreference_PrimaryPreferred, BSONArray());
+ BSONObj doc = replConn.query(IdentityNS, query)->next();
+ const string newDest = doc[HostField.name()].str();
+ ASSERT_EQUALS(dest, newDest);
+ }
+ }
+
+ TEST_F(TaggedFiveMemberRS, ConnShouldNotPinIfDiffMode) {
+ MockReplicaSet* replSet = getReplSet();
+ vector<HostAndPort> seedList;
+ seedList.push_back(HostAndPort(replSet->getPrimary()));
+
+ DBClientReplicaSet replConn(replSet->getSetName(), seedList);
+
+ string dest;
+ {
+ Query query;
+ query.readPref(mongo::ReadPreference_SecondaryPreferred, BSONArray());
+ BSONObj doc = replConn.query(IdentityNS, query)->next();
+ dest = doc[HostField.name()].str();
+ ASSERT_NOT_EQUALS(dest, replSet->getPrimary());
+ }
+
+ {
+ Query query;
+ query.readPref(mongo::ReadPreference_SecondaryOnly, BSONArray());
+ BSONObj doc = replConn.query(IdentityNS, query)->next();
+ const string newDest = doc[HostField.name()].str();
+ ASSERT_NOT_EQUALS(dest, newDest);
+ }
+ }
+
+ TEST_F(TaggedFiveMemberRS, ConnShouldNotPinIfDiffTag) {
+ MockReplicaSet* replSet = getReplSet();
+ vector<HostAndPort> seedList;
+ seedList.push_back(HostAndPort(replSet->getPrimary()));
+
+ DBClientReplicaSet replConn(replSet->getSetName(), seedList);
+
+ string dest;
+ {
+ Query query;
+ query.readPref(mongo::ReadPreference_SecondaryPreferred,
+ BSON_ARRAY(BSON("dc" << "sf")));
+ BSONObj doc = replConn.query(IdentityNS, query)->next();
+ dest = doc[HostField.name()].str();
+ ASSERT_NOT_EQUALS(dest, replSet->getPrimary());
+ }
+
+ {
+ Query query;
+ vector<pair<string, string> > tagSet;
+ query.readPref(mongo::ReadPreference_SecondaryPreferred,
+ BSON_ARRAY(BSON("group" << 1)));
+ BSONObj doc = replConn.query(IdentityNS, query)->next();
+ const string newDest = doc[HostField.name()].str();
+ ASSERT_NOT_EQUALS(dest, newDest);
+ }
+ }
+}
diff --git a/src/mongo/client/dbclientinterface.h b/src/mongo/client/dbclientinterface.h
index 95d7e1c6a23..37ad9533078 100644
--- a/src/mongo/client/dbclientinterface.h
+++ b/src/mongo/client/dbclientinterface.h
@@ -324,6 +324,10 @@ namespace mongo {
*/
class Query {
public:
+ static const BSONField<BSONObj> ReadPrefField;
+ static const BSONField<std::string> ReadPrefModeField;
+ static const BSONField<BSONArray> ReadPrefTagsField;
+
BSONObj obj;
Query() : obj(BSONObj()) { }
Query(const BSONObj& b) : obj(b) { }
@@ -399,6 +403,14 @@ namespace mongo {
Query& where(const string &jscode) { return where(jscode, BSONObj()); }
/**
+ * Sets the read preference for this query.
+ *
+ * @param pref the read preference mode for this query.
+ * @param tags the set of tags to use for this query.
+ */
+ Query& readPref(ReadPreference pref, const BSONArray& tags);
+
+ /**
* @return true if this query has an orderby, hint, or some other field
*/
bool isComplex( bool * hasDollar = 0 ) const;
diff --git a/src/mongo/db/queryutil.h b/src/mongo/db/queryutil.h
index 8ca5620d37f..6e5c86976b3 100644
--- a/src/mongo/db/queryutil.h
+++ b/src/mongo/db/queryutil.h
@@ -20,6 +20,7 @@
#include "jsobj.h"
#include "indexkey.h"
#include "projection.h"
+#include "mongo/client/dbclientinterface.h"
namespace mongo {
@@ -156,7 +157,7 @@ namespace mongo {
_filter = _filter.getOwned();
- _hasReadPref = q.hasField("$readPreference");
+ _hasReadPref = q.hasField(Query::ReadPrefField.name());
}
void _reset() {
diff --git a/src/mongo/s/strategy_single.cpp b/src/mongo/s/strategy_single.cpp
index 88c6089f367..9eb7ca37edb 100644
--- a/src/mongo/s/strategy_single.cpp
+++ b/src/mongo/s/strategy_single.cpp
@@ -19,6 +19,7 @@
#include "pch.h"
#include "mongo/client/connpool.h"
+#include "mongo/client/dbclientinterface.h"
#include "mongo/db/commands.h"
#include "mongo/s/request.h"
#include "mongo/s/cursors.h"
@@ -54,7 +55,7 @@ namespace mongo {
: str::equals("query", e.fieldName()))) {
// Extract the embedded query object.
- if (cmdObj.hasField("$readPreference")) {
+ if (cmdObj.hasField(Query::ReadPrefField.name())) {
// The command has a read preference setting. We don't want
// to lose this information so we copy this to a new field
// called $queryOptions.$readPreference
@@ -63,7 +64,7 @@ namespace mongo {
BSONObjBuilder queryOptionsBuilder(
finalCmdObjBuilder.subobjStart("$queryOptions"));
- queryOptionsBuilder.append(cmdObj["$readPreference"]);
+ queryOptionsBuilder.append(cmdObj[Query::ReadPrefField.name()]);
queryOptionsBuilder.done();
cmdObj = finalCmdObjBuilder.obj();