1
// dbclient.cpp - connect to a Mongo database as a database, from C++
3
/* Copyright 2009 10gen Inc.
5
* Licensed under the Apache License, Version 2.0 (the "License");
6
* you may not use this file except in compliance with the License.
7
* You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
19
#include "../db/pdfile.h"
21
#include "../util/builder.h"
22
#include "../db/jsobj.h"
23
#include "../db/json.h"
24
#include "../db/instance.h"
25
#include "../util/md5.hpp"
26
#include "../db/dbmessage.h"
27
#include "../db/cmdline.h"
31
Query& Query::where(const string &jscode, BSONObj scope) {
32
/* use where() before sort() and hint() and explain(), else this will assert. */
33
assert( !obj.hasField("query") );
35
b.appendElements(obj);
36
b.appendWhere(jscode, scope);
41
void Query::makeComplex() {
42
if ( obj.hasElement( "query" ) )
45
b.append( "query", obj );
49
Query& Query::sort(const BSONObj& s) {
50
appendComplex( "orderby", s );
54
Query& Query::hint(BSONObj keyPattern) {
55
appendComplex( "$hint", keyPattern );
59
Query& Query::explain() {
60
appendComplex( "$explain", true );
64
Query& Query::snapshot() {
65
appendComplex( "$snapshot", true );
69
Query& Query::minKey( const BSONObj &val ) {
70
appendComplex( "$min", val );
74
Query& Query::maxKey( const BSONObj &val ) {
75
appendComplex( "$max", val );
79
bool Query::isComplex() const{
80
return obj.hasElement( "query" );
83
BSONObj Query::getFilter() const {
86
return obj.getObjectField( "query" );
88
BSONObj Query::getSort() const {
91
return obj.getObjectField( "orderby" );
93
BSONObj Query::getHint() const {
96
return obj.getObjectField( "$hint" );
98
bool Query::isExplain() const {
99
return isComplex() && obj.getBoolField( "$explain" );
102
string Query::toString() const{
103
return obj.toString();
106
/* --- dbclientcommands --- */
108
inline bool DBClientWithCommands::isOk(const BSONObj& o) {
109
return o.getIntField("ok") == 1;
112
inline bool DBClientWithCommands::runCommand(const string &dbname, const BSONObj& cmd, BSONObj &info, int options) {
113
string ns = dbname + ".$cmd";
114
info = findOne(ns, cmd, 0 , options);
118
/* note - we build a bson obj here -- for something that is super common like getlasterror you
119
should have that object prebuilt as that would be faster.
121
bool DBClientWithCommands::simpleCommand(const string &dbname, BSONObj *info, const string &command) {
126
b.append(command, 1);
127
return runCommand(dbname, b.done(), *info);
130
unsigned long long DBClientWithCommands::count(const string &_ns, const BSONObj& query, int options) {
131
NamespaceString ns(_ns);
132
BSONObj cmd = BSON( "count" << ns.coll << "query" << query );
134
if( !runCommand(ns.db.c_str(), cmd, res, options) )
135
uasserted(11010,string("count fails:") + res.toString());
136
return res.getIntField("n");
139
BSONObj getlasterrorcmdobj = fromjson("{getlasterror:1}");
141
BSONObj DBClientWithCommands::getLastErrorDetailed() {
143
runCommand("admin", getlasterrorcmdobj, info);
147
string DBClientWithCommands::getLastError() {
148
BSONObj info = getLastErrorDetailed();
149
BSONElement e = info["err"];
150
if( e.eoo() ) return "";
151
if( e.type() == Object ) return e.toString();
155
BSONObj getpreverrorcmdobj = fromjson("{getpreverror:1}");
157
BSONObj DBClientWithCommands::getPrevError() {
159
runCommand("admin", getpreverrorcmdobj, info);
163
BSONObj getnoncecmdobj = fromjson("{getnonce:1}");
165
string DBClientWithCommands::createPasswordDigest( const string & username , const string & clearTextPassword ){
170
md5_append(&st, (const md5_byte_t *) username.data(), username.length());
171
md5_append(&st, (const md5_byte_t *) ":mongo:", 7 );
172
md5_append(&st, (const md5_byte_t *) clearTextPassword.data(), clearTextPassword.length());
175
return digestToString( d );
178
bool DBClientWithCommands::auth(const string &dbname, const string &username, const string &password_text, string& errmsg, bool digestPassword) {
179
//cout << "TEMP AUTH " << toString() << dbname << ' ' << username << ' ' << password_text << ' ' << digestPassword << endl;
181
string password = password_text;
183
password = createPasswordDigest( username , password_text );
187
if( !runCommand(dbname, getnoncecmdobj, info) ) {
188
errmsg = "getnonce fails - connection problem?";
192
BSONElement e = info.getField("nonce");
193
assert( e.type() == String );
194
nonce = e.valuestr();
201
b << "authenticate" << 1 << "nonce" << nonce << "user" << username;
206
md5_append(&st, (const md5_byte_t *) nonce.c_str(), nonce.size() );
207
md5_append(&st, (const md5_byte_t *) username.data(), username.length());
208
md5_append(&st, (const md5_byte_t *) password.c_str(), password.size() );
211
b << "key" << digestToString( d );
215
if( runCommand(dbname, authCmd, info) )
218
errmsg = info.toString();
222
BSONObj ismastercmdobj = fromjson("{\"ismaster\":1}");
224
bool DBClientWithCommands::isMaster(bool& isMaster, BSONObj *info) {
226
if ( info == 0 ) info = &o;
227
bool ok = runCommand("admin", ismastercmdobj, *info);
228
isMaster = (info->getIntField("ismaster") == 1);
232
bool DBClientWithCommands::createCollection(const string &ns, unsigned size, bool capped, int max, BSONObj *info) {
234
if ( info == 0 ) info = &o;
236
b.append("create", ns);
237
if ( size ) b.append("size", size);
238
if ( capped ) b.append("capped", true);
239
if ( max ) b.append("max", max);
240
string db = nsToDatabase(ns.c_str());
241
return runCommand(db.c_str(), b.done(), *info);
244
bool DBClientWithCommands::copyDatabase(const string &fromdb, const string &todb, const string &fromhost, BSONObj *info) {
246
if ( info == 0 ) info = &o;
248
b.append("copydb", 1);
249
b.append("fromhost", fromhost);
250
b.append("fromdb", fromdb);
251
b.append("todb", todb);
252
return runCommand("admin", b.done(), *info);
255
bool DBClientWithCommands::setDbProfilingLevel(const string &dbname, ProfilingLevel level, BSONObj *info ) {
257
if ( info == 0 ) info = &o;
260
// Create system.profile collection. If it already exists this does nothing.
261
// TODO: move this into the db instead of here so that all
262
// drivers don't have to do this.
263
string ns = dbname + ".system.profile";
264
createCollection(ns.c_str(), 1024 * 1024, true, 0, info);
268
b.append("profile", (int) level);
269
return runCommand(dbname, b.done(), *info);
272
BSONObj getprofilingcmdobj = fromjson("{\"profile\":-1}");
274
bool DBClientWithCommands::getDbProfilingLevel(const string &dbname, ProfilingLevel& level, BSONObj *info) {
276
if ( info == 0 ) info = &o;
277
if ( runCommand(dbname, getprofilingcmdobj, *info) ) {
278
level = (ProfilingLevel) info->getIntField("was");
284
BSONObj DBClientWithCommands::mapreduce(const string &ns, const string &jsmapf, const string &jsreducef, BSONObj query, const string& outputcolname) {
286
b.append("mapreduce", nsGetCollection(ns));
287
b.appendCode("map", jsmapf.c_str());
288
b.appendCode("reduce", jsreducef.c_str());
289
if( !query.isEmpty() )
290
b.append("query", query);
291
if( !outputcolname.empty() )
292
b.append("out", outputcolname);
294
runCommand(nsGetDB(ns), b.done(), info);
298
bool DBClientWithCommands::eval(const string &dbname, const string &jscode, BSONObj& info, BSONElement& retValue, BSONObj *args) {
300
b.appendCode("$eval", jscode.c_str());
302
b.appendArray("args", *args);
303
bool ok = runCommand(dbname, b.done(), info);
305
retValue = info.getField("retval");
309
bool DBClientWithCommands::eval(const string &dbname, const string &jscode) {
311
BSONElement retValue;
312
return eval(dbname, jscode, info, retValue);
315
list<string> DBClientWithCommands::getDatabaseNames(){
317
uassert( 10005 , "listdatabases failed" , runCommand( "admin" , BSON( "listDatabases" << 1 ) , info ) );
318
uassert( 10006 , "listDatabases.databases not array" , info["databases"].type() == Array );
322
BSONObjIterator i( info["databases"].embeddedObjectUserCheck() );
324
names.push_back( i.next().embeddedObjectUserCheck()["name"].valuestr() );
330
list<string> DBClientWithCommands::getCollectionNames( const string& db ){
333
string ns = db + ".system.namespaces";
334
auto_ptr<DBClientCursor> c = query( ns.c_str() , BSONObj() );
336
string name = c->next()["name"].valuestr();
337
if ( name.find( "$" ) != string::npos )
339
names.push_back( name );
344
bool DBClientWithCommands::exists( const string& ns ){
347
string db = nsGetDB( ns ) + ".system.namespaces";
348
BSONObj q = BSON( "name" << ns );
349
return count( db.c_str() , q );
354
DBClientConnection c;
356
if ( !c.connect("localhost", err) ) {
357
out() << "can't connect to server " << err << endl;
361
cout << "findOne returns:" << endl;
362
cout << c.findOne("test.foo", QUERY( "x" << 3 ) ).toString() << endl;
363
cout << c.findOne("test.foo", QUERY( "x" << 3 ).sort("name") ).toString() << endl;
367
/* TODO: unit tests should run this? */
369
DBClientConnection c;
371
if ( !c.connect("localhost", err) ) {
372
out() << "can't connect to server " << err << endl;
376
if( !c.auth("dwight", "u", "p", err) ) {
377
out() << "can't authenticate " << err << endl;
382
BSONElement retValue;
385
BSONObj args = b.done();
386
bool ok = c.eval("dwight", "function() { return args[0]; }", info, retValue, &args);
387
out() << "eval ok=" << ok << endl;
388
out() << "retvalue=" << retValue.toString() << endl;
389
out() << "info=" << info.toString() << endl;
394
assert( c.eval("dwight", "function() { return 3; }", x) );
398
BSONObj foo = fromjson("{\"x\":7}");
399
out() << foo.toString() << endl;
401
ok = c.eval("dwight", "function(parm1) { return parm1.x; }", foo, res);
402
out() << ok << " retval:" << res << endl;
407
/* --- dbclientconnection --- */
409
bool DBClientConnection::auth(const string &dbname, const string &username, const string &password_text, string& errmsg, bool digestPassword) {
410
string password = password_text;
412
password = createPasswordDigest( username , password_text );
414
if( autoReconnect ) {
415
/* note we remember the auth info before we attempt to auth -- if the connection is broken, we will
416
then have it for the next autoreconnect attempt.
418
pair<string,string> p = pair<string,string>(username, password);
419
authCache[dbname] = p;
422
return DBClientBase::auth(dbname, username, password.c_str(), errmsg, false);
425
BSONObj DBClientInterface::findOne(const string &ns, Query query, const BSONObj *fieldsToReturn, int queryOptions) {
426
auto_ptr<DBClientCursor> c =
427
this->query(ns, query, 1, 0, fieldsToReturn, queryOptions);
429
massert( 10276 , "DBClientBase::findOne: transport error", c.get() );
434
return c->next().copy();
437
bool DBClientConnection::connect(const string &_serverAddress, string& errmsg) {
438
serverAddress = _serverAddress;
442
size_t idx = serverAddress.find( ":" );
443
if ( idx != string::npos ) {
444
port = strtol( serverAddress.substr( idx + 1 ).c_str(), 0, 10 );
445
ip = serverAddress.substr( 0 , idx );
446
ip = hostbyname(ip.c_str());
448
port = CmdLine::DefaultDBPort;
449
ip = hostbyname( serverAddress.c_str() );
451
massert( 10277 , "Unable to parse hostname", !ip.empty() );
453
// we keep around SockAddr for connection life -- maybe MessagingPort
455
server = auto_ptr<SockAddr>(new SockAddr(ip.c_str(), port));
456
p = auto_ptr<MessagingPort>(new MessagingPort());
458
if ( !p->connect(*server) ) {
460
ss << "couldn't connect to server " << serverAddress << " " << ip << ":" << port;
468
void DBClientConnection::_checkConnection() {
471
if ( lastReconnectTry && time(0)-lastReconnectTry < 2 )
473
if ( !autoReconnect )
476
lastReconnectTry = time(0);
477
log() << "trying reconnect to " << serverAddress << endl;
479
string tmp = serverAddress;
481
if ( !connect(tmp.c_str(), errmsg) ) {
482
log() << "reconnect " << serverAddress << " failed " << errmsg << endl;
486
log() << "reconnect " << serverAddress << " ok" << endl;
487
for( map< string, pair<string,string> >::iterator i = authCache.begin(); i != authCache.end(); i++ ) {
488
const char *dbname = i->first.c_str();
489
const char *username = i->second.first.c_str();
490
const char *password = i->second.second.c_str();
491
if( !DBClientBase::auth(dbname, username, password, errmsg, false) )
492
log() << "reconnect: auth failed db:" << dbname << " user:" << username << ' ' << errmsg << '\n';
496
auto_ptr<DBClientCursor> DBClientBase::query(const string &ns, Query query, int nToReturn,
497
int nToSkip, const BSONObj *fieldsToReturn, int queryOptions) {
498
auto_ptr<DBClientCursor> c( new DBClientCursor( this,
499
ns, query.obj, nToReturn, nToSkip,
500
fieldsToReturn, queryOptions ) );
503
return auto_ptr< DBClientCursor >( 0 );
506
auto_ptr<DBClientCursor> DBClientBase::getMore( const string &ns, long long cursorId, int nToReturn, int options ) {
507
auto_ptr<DBClientCursor> c( new DBClientCursor( this, ns, cursorId, nToReturn, options ) );
510
return auto_ptr< DBClientCursor >( 0 );
513
void DBClientBase::insert( const string & ns , BSONObj obj ) {
520
obj.appendSelfToBufBuilder( b );
522
toSend.setData( dbInsert , b.buf() , b.len() );
527
void DBClientBase::insert( const string & ns , const vector< BSONObj > &v ) {
534
for( vector< BSONObj >::const_iterator i = v.begin(); i != v.end(); ++i )
535
i->appendSelfToBufBuilder( b );
537
toSend.setData( dbInsert, b.buf(), b.len() );
542
void DBClientBase::remove( const string & ns , Query obj , bool justOne ) {
555
obj.obj.appendSelfToBufBuilder( b );
557
toSend.setData( dbDelete , b.buf() , b.len() );
562
void DBClientBase::update( const string & ns , Query query , BSONObj obj , bool upsert , bool multi ) {
565
b.append( (int)0 ); // reserverd
569
if ( upsert ) flags |= UpdateOption_Upsert;
570
if ( multi ) flags |= UpdateOption_Multi;
573
query.obj.appendSelfToBufBuilder( b );
574
obj.appendSelfToBufBuilder( b );
577
toSend.setData( dbUpdate , b.buf() , b.len() );
582
auto_ptr<DBClientCursor> DBClientWithCommands::getIndexes( const string &ns ){
583
return query( Namespace( ns.c_str() ).getSisterNS( "system.indexes" ).c_str() , BSON( "ns" << ns ) );
586
void DBClientWithCommands::dropIndex( const string& ns , BSONObj keys ){
587
dropIndex( ns , genIndexName( keys ) );
591
void DBClientWithCommands::dropIndex( const string& ns , const string& indexName ){
593
if ( ! runCommand( nsToDatabase( ns.c_str() ) ,
594
BSON( "deleteIndexes" << NamespaceString( ns ).coll << "index" << indexName ) ,
596
log() << "dropIndex failed: " << info << endl;
597
uassert( 10007 , "dropIndex failed" , 0 );
602
void DBClientWithCommands::dropIndexes( const string& ns ){
604
uassert( 10008 , "dropIndexes failed" , runCommand( nsToDatabase( ns.c_str() ) ,
605
BSON( "deleteIndexes" << NamespaceString( ns ).coll << "index" << "*") ,
610
void DBClientWithCommands::reIndex( const string& ns ){
612
auto_ptr<DBClientCursor> i = getIndexes( ns );
614
all.push_back( i->next().getOwned() );
619
for ( list<BSONObj>::iterator i=all.begin(); i!=all.end(); i++ ){
621
insert( Namespace( ns.c_str() ).getSisterNS( "system.indexes" ).c_str() , o );
627
string DBClientWithCommands::genIndexName( const BSONObj& keys ){
631
for ( BSONObjIterator i(keys); i.more(); ) {
632
BSONElement f = i.next();
639
ss << f.fieldName() << "_";
646
bool DBClientWithCommands::ensureIndex( const string &ns , BSONObj keys , bool unique, const string & name ) {
647
BSONObjBuilder toSave;
648
toSave.append( "ns" , ns );
649
toSave.append( "key" , keys );
655
toSave.append( "name" , name );
659
string nn = genIndexName( keys );
660
toSave.append( "name" , nn );
665
toSave.appendBool( "unique", unique );
667
if ( _seenIndexes.count( cacheKey ) )
669
_seenIndexes.insert( cacheKey );
671
insert( Namespace( ns.c_str() ).getSisterNS( "system.indexes" ).c_str() , toSave.obj() );
675
void DBClientWithCommands::resetIndexCache() {
676
_seenIndexes.clear();
679
/* -- DBClientCursor ---------------------------------------------- */
681
void assembleRequest( const string &ns, BSONObj query, int nToReturn, int nToSkip, const BSONObj *fieldsToReturn, int queryOptions, Message &toSend ) {
682
CHECK_OBJECT( query , "assembleRequest query" );
683
// see query.h for the protocol we are using here.
685
int opts = queryOptions;
687
b.append(ns.c_str());
690
query.appendSelfToBufBuilder(b);
691
if ( fieldsToReturn )
692
fieldsToReturn->appendSelfToBufBuilder(b);
693
toSend.setData(dbQuery, b.buf(), b.len());
696
void DBClientConnection::say( Message &toSend ) {
699
port().say( toSend );
700
} catch( SocketException & ) {
706
void DBClientConnection::sayPiggyBack( Message &toSend ) {
707
port().piggyBack( toSend );
710
bool DBClientConnection::call( Message &toSend, Message &response, bool assertOk ) {
711
/* todo: this is very ugly messagingport::call returns an error code AND can throw
712
an exception. we should make it return void and just throw an exception anytime
716
if ( !port().call(toSend, response) ) {
719
massert( 10278 , "dbclient error communicating with server", false);
723
catch( SocketException & ) {
730
void DBClientConnection::checkResponse( const char *data, int nReturned ) {
731
/* check for errors. the only one we really care about at
732
this stage is "not master" */
733
if ( clientPaired && nReturned ) {
735
BSONElement e = o.firstElement();
736
if ( strcmp(e.fieldName(), "$err") == 0 &&
737
e.type() == String && strncmp(e.valuestr(), "not master", 10) == 0 ) {
738
clientPaired->isntMaster();
743
bool DBClientCursor::init() {
746
assembleRequest( ns, query, nToReturn, nToSkip, fieldsToReturn, opts, toSend );
750
b.append( ns.c_str() );
751
b.append( nToReturn );
752
b.append( cursorId );
753
toSend.setData( dbGetMore, b.buf(), b.len() );
755
if ( !connector->call( toSend, *m, false ) )
761
void DBClientCursor::requestMore() {
762
assert( cursorId && pos == nReturned );
766
b.append(ns.c_str());
771
toSend.setData(dbGetMore, b.buf(), b.len());
772
auto_ptr<Message> response(new Message());
773
connector->call( toSend, *response );
779
void DBClientCursor::dataReceived() {
780
QueryResult *qr = (QueryResult *) m->data;
781
resultFlags = qr->resultFlags();
782
if ( qr->resultFlags() & QueryResult::ResultFlag_CursorNotFound ) {
783
// cursor id no longer valid at the server.
784
assert( qr->cursorId == 0 );
785
cursorId = 0; // 0 indicates no longer valid (dead)
786
// TODO: should we throw a UserException here???
788
if ( cursorId == 0 || ! ( opts & QueryOption_CursorTailable ) ) {
789
// only set initially: we don't want to kill it on end of data
790
// if it's a tailable cursor
791
cursorId = qr->cursorId;
793
nReturned = qr->nReturned;
797
connector->checkResponse( data, nReturned );
798
/* this assert would fire the way we currently work:
799
assert( nReturned || cursorId == 0 );
803
/** If true, safe to call next(). Requests more from server if necessary. */
804
bool DBClientCursor::more() {
805
if ( pos < nReturned )
812
return pos < nReturned;
815
BSONObj DBClientCursor::next() {
823
DBClientCursor::~DBClientCursor() {
824
if ( cursorId && _ownCursor ) {
826
b.append( (int)0 ); // reserved
827
b.append( (int)1 ); // number
828
b.append( cursorId );
831
m.setData( dbKillCursors , b.buf() , b.len() );
833
connector->sayPiggyBack( m );
838
/* --- class dbclientpaired --- */
840
string DBClientPaired::toString() {
842
ss << "state: " << master << '\n';
843
ss << "left: " << left.toStringLong() << '\n';
844
ss << "right: " << right.toStringLong() << '\n';
848
#pragma warning(disable: 4355)
849
DBClientPaired::DBClientPaired() :
850
left(true, this), right(true, this)
854
#pragma warning(default: 4355)
856
/* find which server, the left or right, is currently master mode */
857
void DBClientPaired::_checkMaster() {
858
for ( int retry = 0; retry < 2; retry++ ) {
860
for ( int pass = 0; pass < 2; pass++ ) {
861
DBClientConnection& c = x == 0 ? left : right;
867
log() << "checkmaster: " << c.toString() << ' ' << o.toString() << '\n';
869
master = (State) (x + 2);
873
catch (AssertionException&) {
875
log() << "checkmaster: caught exception " << c.toString() << '\n';
882
uassert( 10009 , "checkmaster: no master found", false);
885
inline DBClientConnection& DBClientPaired::checkMaster() {
886
if ( master > NotSetR ) {
887
// a master is selected. let's just make sure connection didn't die
888
DBClientConnection& c = master == Left ? left : right;
891
// after a failure, on the next checkMaster, start with the other
892
// server -- presumably it took over. (not critical which we check first,
893
// just will make the failover slightly faster if we guess right)
894
master = master == Left ? NotSetR : NotSetL;
898
assert( master > NotSetR );
899
return master == Left ? left : right;
902
DBClientConnection& DBClientPaired::slaveConn(){
903
DBClientConnection& m = checkMaster();
904
assert( ! m.isFailed() );
905
return master == Left ? right : left;
908
bool DBClientPaired::connect(const string &serverHostname1, const string &serverHostname2) {
910
bool l = left.connect(serverHostname1, errmsg);
911
bool r = right.connect(serverHostname2, errmsg);
912
master = l ? NotSetL : NotSetR;
913
if ( !l && !r ) // it would be ok to fall through, but checkMaster will then try an immediate reconnect which is slow
918
catch (AssertionException&) {
924
bool DBClientPaired::connect(string hostpairstring) {
925
size_t comma = hostpairstring.find( "," );
926
uassert( 10010 , "bad hostpairstring", comma != string::npos);
927
return connect( hostpairstring.substr( 0 , comma ) , hostpairstring.substr( comma + 1 ) );
930
bool DBClientPaired::auth(const string &dbname, const string &username, const string &pwd, string& errmsg) {
931
DBClientConnection& m = checkMaster();
932
if( !m.auth(dbname, username, pwd, errmsg) )
934
/* we try to authentiate with the other half of the pair -- even if down, that way the authInfo is cached. */
938
right.auth(dbname, username, pwd, e);
940
left.auth(dbname, username, pwd, e);
942
catch( AssertionException&) {
947
auto_ptr<DBClientCursor> DBClientPaired::query(const string &a, Query b, int c, int d,
948
const BSONObj *e, int f)
950
return checkMaster().query(a,b,c,d,e,f);
953
BSONObj DBClientPaired::findOne(const string &a, Query b, const BSONObj *c, int d) {
954
return checkMaster().findOne(a,b,c,d);
959
log() << "connect returns " << p.connect("localhost:27017", "localhost:27018") << endl;
961
//DBClientConnection p(true);
963
// log() << "connect " << p.connect("localhost", errmsg) << endl;
964
log() << "auth " << p.auth("dwight", "u", "p", errmsg) << endl;
969
log() << "findone returns " << p.findOne("dwight.foo", BSONObj()).toString() << endl;
973
log() << "ismaster returns " << p.isMaster(im,&info) << " info: " << info.toString() << endl;
976
cout << "caught exception" << endl;