diff options
author | Randolph Tan <randolph@10gen.com> | 2013-02-07 13:34:44 -0500 |
---|---|---|
committer | Randolph Tan <randolph@10gen.com> | 2013-02-13 18:16:16 -0500 |
commit | ae2256441b1c96768b270ee6a866d9669d4b15ec (patch) | |
tree | a2021bb9b399f04b969c66c61e8935f4bd7cf5eb | |
parent | 66bc2d1afbff04c304c8072175044fa8d2bdf371 (diff) | |
download | mongo-ae2256441b1c96768b270ee6a866d9669d4b15ec.tar.gz |
SERVER-7636 Do not use cached connection when read preference changes
-rw-r--r-- | src/mongo/SConscript | 12 | ||||
-rw-r--r-- | src/mongo/client/dbclient.cpp | 44 | ||||
-rw-r--r-- | src/mongo/client/dbclient_rs.cpp | 99 | ||||
-rw-r--r-- | src/mongo/client/dbclient_rs.h | 54 | ||||
-rw-r--r-- | src/mongo/client/dbclient_rs_test.cpp | 245 | ||||
-rw-r--r-- | src/mongo/client/dbclientinterface.h | 12 | ||||
-rw-r--r-- | src/mongo/db/queryutil.h | 3 | ||||
-rw-r--r-- | src/mongo/s/strategy_single.cpp | 5 |
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(); |