// dbclient.h - connect to a Mongo database as a database, from C++
/**
* Copyright (C) 2008 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 .
*/
#pragma once
#include "../grid/message.h"
#include "../db/jsobj.h"
/* the query field 'options' can have these bits set: */
enum {
/* Tailable means cursor is not closed when the last data is retrieved. rather, the cursor marks
the final object's position. you can resume using the cursor later, from where it was located,
if more data were received. Set on dbQuery and dbGetMore.
like any "latent cursor", the cursor may become invalid at some point -- for example if that
final object it references were deleted. Thus, you should be prepared to requery if you get back
ResultFlag_CursorNotFound.
*/
Option_CursorTailable = 2,
/* allow query of replica slave. normally these return an error except for namespace "local".
*/
Option_SlaveOk = 4,
Option_ALLMASK = 6
};
class BSONObj;
#pragma pack(push,1)
struct QueryResult : public MsgData {
long long cursorId;
int startingFrom;
int nReturned;
const char *data() {
return (char *) (((int *)&nReturned)+1);
}
int& resultFlags() {
return dataAsInt();
}
};
#pragma pack(pop)
class DBClientCursor : boost::noncopyable {
public:
bool more(); // if true, safe to call next()
/* returns next object in the result cursor.
on an error at the remote server, you will get back:
{ $err: }
if you do not want to handle that yourself, call nextSafe().
*/
BSONObj next();
BSONObj nextSafe() {
BSONObj o = next();
BSONElement e = o.firstElement();
assert( strcmp(e.fieldName(), "$err") != 0 );
return o;
}
/* cursor no longer valid -- use with tailable cursors.
note you should only rely on this once more() returns false;
'dead' may be preset yet some data still queued and locally
available from the dbclientcursor.
*/
bool isDead() const {
return cursorId == 0;
}
bool tailable() const {
return (opts & Option_CursorTailable) != 0;
}
bool init();
class Connector {
public:
virtual bool send( Message &toSend, Message &response, bool assertOk=true ) = 0;
virtual void checkResponse( const char *data, int nReturned ) {}
};
DBClientCursor( Connector *_connector, const char * _ns, BSONObj _query, int _nToReturn,
int _nToSkip, BSONObj *_fieldsToReturn, int queryOptions ) :
connector(_connector),
ns(_ns),
query(_query),
nToReturn(_nToReturn),
nToSkip(_nToSkip),
fieldsToReturn(_fieldsToReturn),
opts(queryOptions),
m(new Message()) {
cursorId = 0;
}
private:
auto_ptr< Connector > connector;
string ns;
BSONObj query;
int nToReturn;
int nToSkip;
BSONObj *fieldsToReturn;
int opts;
auto_ptr m;
long long cursorId;
int nReturned;
int pos;
const char *data;
void dataReceived();
void requestMore();
};
class DBClientInterface : boost::noncopyable {
public:
virtual
auto_ptr query(const char *ns, BSONObj query, int nToReturn = 0, int nToSkip = 0,
BSONObj *fieldsToReturn = 0, int queryOptions = 0) = 0;
virtual
BSONObj findOne(const char *ns, BSONObj query, BSONObj *fieldsToReturn = 0, int queryOptions = 0) = 0;
};
/* db "commands"
basically just invocations of connection.$cmd.findOne({...});
*/
class DBClientWithCommands : public DBClientInterface {
public:
/* returns true in isMaster parm if this db is the master instance.
BSONObj contains more details e.g.:
{ "ismaster" : 1.0 , "msg" : "not paired" , "ok" : 1.0 }
*/
virtual
BSONObj cmdIsMaster(bool& isMaster);
virtual string toString() = 0;
};
class DBClientPaired;
/* A basic connection to the database. */
class DBClientConnection : public DBClientWithCommands {
DBClientPaired *clientPaired;
auto_ptr p;
auto_ptr server;
bool failed; // true if some sort of fatal error has ever happened
bool autoReconnect;
time_t lastReconnectTry;
string serverAddress; // remember for reconnects
void checkConnection();
public:
string toStringLong() const {
stringstream ss;
ss << serverAddress;
if ( failed ) ss << " failed";
return ss.str();
}
string toString() {
return serverAddress;
}
MessagingPort& port() {
return *p.get();
}
bool isFailed() const {
return failed;
}
DBClientConnection(bool _autoReconnect=false,DBClientPaired* cp=0) :
clientPaired(cp), failed(false), autoReconnect(_autoReconnect), lastReconnectTry(0) { }
/* Returns false if fails to connect.
If autoReconnect is true, you can try to use the DBClientConnection even when
false was returned -- it will try to connect again.
*/
virtual
bool connect(const char *serverHostname, string& errmsg);
/* send a query to the database.
ns: namespace to query, format is .[.]*
query: query to perform on the collection. this is a BSONObj (binary JSON)
You may format as
{ query: { ... }, order: { ... } }
to specify a sort order.
nToReturn: n to return. 0 = unlimited
nToSkip: start with the nth item
fieldsToReturn:
optional template of which fields to select. if unspecified, returns all fields
queryOptions: see options enum at top of this file
returns: cursor.
0 if error (connection failure)
*/
/*throws AssertionException*/
auto_ptr query(const char *ns, BSONObj query, int nToReturn = 0, int nToSkip = 0,
BSONObj *fieldsToReturn = 0, int queryOptions = 0);
/*throws AssertionException*/
virtual
BSONObj findOne(const char *ns, BSONObj query, BSONObj *fieldsToReturn = 0, int queryOptions = 0);
void insert( const char * ns , BSONObj obj );
void remove( const char * ns , BSONObj obj , bool justOne = 0 );
private:
class CursorConnector : public DBClientCursor::Connector {
DBClientConnection *conn;
virtual bool send( Message &toSend, Message &response, bool assertOk = true );
virtual void checkResponse( const char *data, int nReturned );
public:
CursorConnector( DBClientConnection *_conn ) :
conn( _conn ) {
}
};
};
/* Use this class to connect to a replica pair of servers. The class will manage
checking for which is master, and do failover automatically.
*/
class DBClientPaired : public DBClientWithCommands {
DBClientConnection left,right;
enum State {
NotSetL=0,
NotSetR=1,
Left, Right
} master;
void _checkMaster();
DBClientConnection& checkMaster();
public:
DBClientPaired();
/* Returns false is neither member of the pair were reachable, or neither is
master, although,
when false returned, you can still try to use this connection object, it will
try reconnects.
*/
bool connect(const char *serverHostname1, const char *serverHostname2);
/* throws userassertion "no master found" */
virtual
auto_ptr query(const char *ns, BSONObj query, int nToReturn = 0, int nToSkip = 0,
BSONObj *fieldsToReturn = 0, int queryOptions = 0);
/* throws userassertion "no master found" */
virtual
BSONObj findOne(const char *ns, BSONObj query, BSONObj *fieldsToReturn = 0, int queryOptions = 0);
string toString();
/* notification that we got a "not master" error.
*/
void isntMaster() {
master = ( ( master == Left ) ? NotSetR : NotSetL );
}
};