Couldn't find wdiff. Falling back to builtin diff colouring...
| assert_util.h | | assert_util.h | |
| | | | |
| skipping to change at line 22 | | skipping to change at line 22 | |
| * distributed under the License is distributed on an "AS IS" BASIS, | | * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | |
| * See the License for the specific language governing permissions and | | * See the License for the specific language governing permissions and | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../db/lasterror.h" | | #include "../db/lasterror.h" | |
| | | | |
|
| | | // MONGO_NORETURN undefed at end of file | |
| | | #ifdef __GNUC__ | |
| | | # define MONGO_NORETURN __attribute__((__noreturn__)) | |
| | | #else | |
| | | # define MONGO_NORETURN | |
| | | #endif | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| enum CommonErrorCodes { | | enum CommonErrorCodes { | |
| DatabaseDifferCaseCode = 13297 , | | DatabaseDifferCaseCode = 13297 , | |
| StaleConfigInContextCode = 13388 | | StaleConfigInContextCode = 13388 | |
| }; | | }; | |
| | | | |
|
| /* these are manipulated outside of mutexes, so be careful */ | | | |
| struct Assertion { | | | |
| Assertion() { | | | |
| msg[0] = msg[127] = 0; | | | |
| context[0] = context[127] = 0; | | | |
| file = ""; | | | |
| line = 0; | | | |
| when = 0; | | | |
| } | | | |
| private: | | | |
| static mongo::mutex *_mutex; | | | |
| char msg[128]; | | | |
| char context[128]; | | | |
| const char *file; | | | |
| unsigned line; | | | |
| time_t when; | | | |
| public: | | | |
| void set(const char *m, const char *ctxt, const char *f, unsigned l
) { | | | |
| if( _mutex == 0 ) { | | | |
| /* asserted during global variable initialization */ | | | |
| return; | | | |
| } | | | |
| scoped_lock lk(*_mutex); | | | |
| strncpy(msg, m, 127); | | | |
| strncpy(context, ctxt, 127); | | | |
| file = f; | | | |
| line = l; | | | |
| when = time(0); | | | |
| } | | | |
| std::string toString(); | | | |
| bool isSet() { | | | |
| return when != 0; | | | |
| } | | | |
| }; | | | |
| | | | |
| enum { | | | |
| AssertRegular = 0, | | | |
| AssertW = 1, | | | |
| AssertMsg = 2, | | | |
| AssertUser = 3 | | | |
| }; | | | |
| | | | |
| /* last assert of diff types: regular, wassert, msgassert, uassert: */ | | | |
| extern Assertion lastAssert[4]; | | | |
| | | | |
| class AssertionCount { | | class AssertionCount { | |
| public: | | public: | |
| AssertionCount(); | | AssertionCount(); | |
| void rollover(); | | void rollover(); | |
| void condrollover( int newValue ); | | void condrollover( int newValue ); | |
| | | | |
| int regular; | | int regular; | |
| int warning; | | int warning; | |
| int msg; | | int msg; | |
| int user; | | int user; | |
| int rollovers; | | int rollovers; | |
| }; | | }; | |
| | | | |
| extern AssertionCount assertionCount; | | extern AssertionCount assertionCount; | |
| | | | |
| struct ExceptionInfo { | | struct ExceptionInfo { | |
|
| ExceptionInfo() : msg(""),code(-1){} | | ExceptionInfo() : msg(""),code(-1) {} | |
| ExceptionInfo( const char * m , int c ) | | ExceptionInfo( const char * m , int c ) | |
|
| : msg( m ) , code( c ){ | | : msg( m ) , code( c ) { | |
| } | | } | |
| ExceptionInfo( const string& m , int c ) | | ExceptionInfo( const string& m , int c ) | |
|
| : msg( m ) , code( c ){ | | : msg( m ) , code( c ) { | |
| } | | } | |
|
| | | | |
| void append( BSONObjBuilder& b , const char * m = "$err" , const ch
ar * c = "code" ) const ; | | void append( BSONObjBuilder& b , const char * m = "$err" , const ch
ar * c = "code" ) const ; | |
|
| | | | |
| string toString() const { stringstream ss; ss << "exception: " << c
ode << " " << msg; return ss.str(); } | | string toString() const { stringstream ss; ss << "exception: " << c
ode << " " << msg; return ss.str(); } | |
|
| | | | |
| bool empty() const { return msg.empty(); } | | bool empty() const { return msg.empty(); } | |
| | | | |
|
| | | void reset(){ msg = ""; code=-1; } | |
| | | | |
| string msg; | | string msg; | |
| int code; | | int code; | |
| }; | | }; | |
| | | | |
|
| | | /** helper class that builds error strings. lighter weight than a Stri
ngBuilder, albeit less flexible. | |
| | | NOINLINE_DECL used in the constructor implementations as we are ass
uming this is a cold code path when used. | |
| | | | |
| | | example: | |
| | | throw UserException(123, ErrorMsg("blah", num_val)); | |
| | | */ | |
| | | class ErrorMsg { | |
| | | public: | |
| | | ErrorMsg(const char *msg, char ch); | |
| | | ErrorMsg(const char *msg, unsigned val); | |
| | | operator string() const { return buf; } | |
| | | private: | |
| | | char buf[256]; | |
| | | }; | |
| | | | |
| class DBException : public std::exception { | | class DBException : public std::exception { | |
| public: | | public: | |
|
| DBException( const ExceptionInfo& ei ) : _ei(ei){} | | DBException( const ExceptionInfo& ei ) : _ei(ei) {} | |
| DBException( const char * msg , int code ) : _ei(msg,code){} | | DBException( const char * msg , int code ) : _ei(msg,code) {} | |
| DBException( const string& msg , int code ) : _ei(msg,code){} | | DBException( const string& msg , int code ) : _ei(msg,code) {} | |
| virtual ~DBException() throw() { } | | virtual ~DBException() throw() { } | |
| | | | |
|
| virtual const char* what() const throw(){ return _ei.msg.c_str(); } | | virtual const char* what() const throw() { return _ei.msg.c_str();
} | |
| virtual int getCode() const { return _ei.code; } | | virtual int getCode() const { return _ei.code; } | |
| | | | |
| virtual void appendPrefix( stringstream& ss ) const { } | | virtual void appendPrefix( stringstream& ss ) const { } | |
| | | | |
| virtual string toString() const { | | virtual string toString() const { | |
| stringstream ss; ss << getCode() << " " << what(); return ss.st
r(); | | stringstream ss; ss << getCode() << " " << what(); return ss.st
r(); | |
| return ss.str(); | | return ss.str(); | |
| } | | } | |
| | | | |
| const ExceptionInfo& getInfo() const { return _ei; } | | const ExceptionInfo& getInfo() const { return _ei; } | |
| | | | |
| protected: | | protected: | |
| ExceptionInfo _ei; | | ExceptionInfo _ei; | |
| }; | | }; | |
| | | | |
| class AssertionException : public DBException { | | class AssertionException : public DBException { | |
| public: | | public: | |
| | | | |
|
| AssertionException( const ExceptionInfo& ei ) : DBException(ei){} | | AssertionException( const ExceptionInfo& ei ) : DBException(ei) {} | |
| AssertionException( const char * msg , int code ) : DBException(msg
,code){} | | AssertionException( const char * msg , int code ) : DBException(msg
,code) {} | |
| AssertionException( const string& msg , int code ) : DBException(ms
g,code){} | | AssertionException( const string& msg , int code ) : DBException(ms
g,code) {} | |
| | | | |
| virtual ~AssertionException() throw() { } | | virtual ~AssertionException() throw() { } | |
| | | | |
| virtual bool severe() { return true; } | | virtual bool severe() { return true; } | |
| virtual bool isUserAssertion() { return false; } | | virtual bool isUserAssertion() { return false; } | |
| | | | |
| /* true if an interrupted exception - see KillCurrentOp */ | | /* true if an interrupted exception - see KillCurrentOp */ | |
| bool interrupted() { | | bool interrupted() { | |
| return _ei.code == 11600 || _ei.code == 11601; | | return _ei.code == 11600 || _ei.code == 11601; | |
| } | | } | |
| }; | | }; | |
| | | | |
| /* UserExceptions are valid errors that a user can cause, like out of d
isk space or duplicate key */ | | /* UserExceptions are valid errors that a user can cause, like out of d
isk space or duplicate key */ | |
| class UserException : public AssertionException { | | class UserException : public AssertionException { | |
| public: | | public: | |
|
| UserException(int c , const string& m) : AssertionException( m , c
){} | | UserException(int c , const string& m) : AssertionException( m , c
) {} | |
| | | | |
| virtual bool severe() { return false; } | | virtual bool severe() { return false; } | |
| virtual bool isUserAssertion() { return true; } | | virtual bool isUserAssertion() { return true; } | |
| virtual void appendPrefix( stringstream& ss ) const { ss << "useras
sert:"; } | | virtual void appendPrefix( stringstream& ss ) const { ss << "useras
sert:"; } | |
| }; | | }; | |
| | | | |
| class MsgAssertionException : public AssertionException { | | class MsgAssertionException : public AssertionException { | |
| public: | | public: | |
|
| MsgAssertionException( const ExceptionInfo& ei ) : AssertionExcepti
on( ei ){} | | MsgAssertionException( const ExceptionInfo& ei ) : AssertionExcepti
on( ei ) {} | |
| MsgAssertionException(int c, const string& m) : AssertionException(
m , c ){} | | MsgAssertionException(int c, const string& m) : AssertionException(
m , c ) {} | |
| virtual bool severe() { return false; } | | virtual bool severe() { return false; } | |
| virtual void appendPrefix( stringstream& ss ) const { ss << "masser
t:"; } | | virtual void appendPrefix( stringstream& ss ) const { ss << "masser
t:"; } | |
| }; | | }; | |
| | | | |
|
| void asserted(const char *msg, const char *file, unsigned line); | | void asserted(const char *msg, const char *file, unsigned line) MONGO_N
ORETURN; | |
| void wasserted(const char *msg, const char *file, unsigned line); | | void wasserted(const char *msg, const char *file, unsigned line); | |
|
| void uasserted(int msgid, const char *msg); | | void verifyFailed( int msgid ); | |
| | | | |
| | | /** a "user assertion". throws UserAssertion. logs. typically used f
or errors that a user | |
| | | could cause, such as duplicate key, disk full, etc. | |
| | | */ | |
| | | void uasserted(int msgid, const char *msg) MONGO_NORETURN; | |
| inline void uasserted(int msgid , string msg) { uasserted(msgid, msg.c_
str()); } | | inline void uasserted(int msgid , string msg) { uasserted(msgid, msg.c_
str()); } | |
|
| void uassert_nothrow(const char *msg); // reported via lasterror, but d
on't throw exception | | | |
| void msgassertedNoTrace(int msgid, const char *msg); | | /** reported via lasterror, but don't throw exception */ | |
| void msgasserted(int msgid, const char *msg); | | void uassert_nothrow(const char *msg); | |
| | | | |
| | | /** msgassert and massert are for errors that are internal but have a w
ell defined error text string. | |
| | | a stack trace is logged. | |
| | | */ | |
| | | void msgassertedNoTrace(int msgid, const char *msg) MONGO_NORETURN; | |
| | | inline void msgassertedNoTrace(int msgid, const string& msg) { msgasser
tedNoTrace( msgid , msg.c_str() ); } | |
| | | void msgasserted(int msgid, const char *msg) MONGO_NORETURN; | |
| inline void msgasserted(int msgid, string msg) { msgasserted(msgid, msg
.c_str()); } | | inline void msgasserted(int msgid, string msg) { msgasserted(msgid, msg
.c_str()); } | |
| | | | |
|
| | | /* convert various types of exceptions to strings */ | |
| | | inline string causedBy( const char* e ){ return (string)" :: caused by
:: " + e; } | |
| | | inline string causedBy( const DBException& e ){ return causedBy( e.toSt
ring().c_str() ); } | |
| | | inline string causedBy( const std::exception& e ){ return causedBy( e.w
hat() ); } | |
| | | inline string causedBy( const string& e ){ return causedBy( e.c_str() )
; } | |
| | | | |
| | | /** in the mongodb source, use verify() instead of assert(). verify is
always evaluated even in release builds. */ | |
| | | inline void verify( int msgid , bool testOK ) { if ( ! testOK ) verifyF
ailed( msgid ); } | |
| | | | |
| #ifdef assert | | #ifdef assert | |
| #undef assert | | #undef assert | |
| #endif | | #endif | |
| | | | |
|
| #define MONGO_assert(_Expression) (void)( (!!(_Expression)) || (mongo::asse
rted(#_Expression, __FILE__, __LINE__), 0) ) | | #define MONGO_assert(_Expression) (void)( MONGO_likely(!!(_Expression)) ||
(mongo::asserted(#_Expression, __FILE__, __LINE__), 0) ) | |
| #define assert MONGO_assert | | #define assert MONGO_assert | |
| | | | |
| /* "user assert". if asserts, user did something wrong, not our code *
/ | | /* "user assert". if asserts, user did something wrong, not our code *
/ | |
|
| #define MONGO_uassert(msgid, msg, expr) (void)( (!!(expr)) || (mongo::uasse
rted(msgid, msg), 0) ) | | #define MONGO_uassert(msgid, msg, expr) (void)( MONGO_likely(!!(expr)) || (
mongo::uasserted(msgid, msg), 0) ) | |
| #define uassert MONGO_uassert | | #define uassert MONGO_uassert | |
| | | | |
| /* warning only - keeps going */ | | /* warning only - keeps going */ | |
|
| #define MONGO_wassert(_Expression) (void)( (!!(_Expression)) || (mongo::was
serted(#_Expression, __FILE__, __LINE__), 0) ) | | #define MONGO_wassert(_Expression) (void)( MONGO_likely(!!(_Expression)) ||
(mongo::wasserted(#_Expression, __FILE__, __LINE__), 0) ) | |
| #define wassert MONGO_wassert | | #define wassert MONGO_wassert | |
| | | | |
| /* display a message, no context, and throw assertionexception | | /* display a message, no context, and throw assertionexception | |
| | | | |
| easy way to throw an exception and log something without our stack t
race | | easy way to throw an exception and log something without our stack t
race | |
| display happening. | | display happening. | |
| */ | | */ | |
|
| #define MONGO_massert(msgid, msg, expr) (void)( (!!(expr)) || (mongo::msgas
serted(msgid, msg), 0) ) | | #define MONGO_massert(msgid, msg, expr) (void)( MONGO_likely(!!(expr)) || (
mongo::msgasserted(msgid, msg), 0) ) | |
| #define massert MONGO_massert | | #define massert MONGO_massert | |
| | | | |
| /* dassert is 'debug assert' -- might want to turn off for production a
s these | | /* dassert is 'debug assert' -- might want to turn off for production a
s these | |
| could be slow. | | could be slow. | |
| */ | | */ | |
| #if defined(_DEBUG) | | #if defined(_DEBUG) | |
| # define MONGO_dassert assert | | # define MONGO_dassert assert | |
| #else | | #else | |
| # define MONGO_dassert(x) | | # define MONGO_dassert(x) | |
| #endif | | #endif | |
| #define dassert MONGO_dassert | | #define dassert MONGO_dassert | |
| | | | |
| // some special ids that we want to duplicate | | // some special ids that we want to duplicate | |
| | | | |
| // > 10000 asserts | | // > 10000 asserts | |
| // < 10000 UserException | | // < 10000 UserException | |
| | | | |
| enum { ASSERT_ID_DUPKEY = 11000 }; | | enum { ASSERT_ID_DUPKEY = 11000 }; | |
| | | | |
| /* throws a uassertion with an appropriate msg */ | | /* throws a uassertion with an appropriate msg */ | |
|
| void streamNotGood( int code , string msg , std::ios& myios ); | | void streamNotGood( int code , string msg , std::ios& myios ) MONGO_NOR
ETURN; | |
| | | | |
| inline void assertStreamGood(unsigned msgid, string msg, std::ios& myio
s) { | | inline void assertStreamGood(unsigned msgid, string msg, std::ios& myio
s) { | |
| if( !myios.good() ) streamNotGood(msgid, msg, myios); | | if( !myios.good() ) streamNotGood(msgid, msg, myios); | |
| } | | } | |
| | | | |
| string demangleName( const type_info& typeinfo ); | | string demangleName( const type_info& typeinfo ); | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
| #define BOOST_CHECK_EXCEPTION MONGO_BOOST_CHECK_EXCEPTION | | #define BOOST_CHECK_EXCEPTION MONGO_BOOST_CHECK_EXCEPTION | |
| #define MONGO_BOOST_CHECK_EXCEPTION( expression ) \ | | #define MONGO_BOOST_CHECK_EXCEPTION( expression ) \ | |
|
| try { \ | | try { \ | |
| expression; \ | | expression; \ | |
| } catch ( const std::exception &e ) { \ | | } catch ( const std::exception &e ) { \ | |
| stringstream ss; \ | | stringstream ss; \ | |
|
| ss << "caught boost exception: " << e.what(); \ | | ss << "caught boost exception: " << e.what() << ' ' << __FILE__ <<
' ' << __LINE__; \ | |
| msgasserted( 13294 , ss.str() ); \ | | msgasserted( 13294 , ss.str() ); \ | |
| } catch ( ... ) { \ | | } catch ( ... ) { \ | |
| massert( 10437 , "unknown boost failed" , false ); \ | | massert( 10437 , "unknown boost failed" , false ); \ | |
| } | | } | |
| | | | |
| | | #define MONGO_BOOST_CHECK_EXCEPTION_WITH_MSG( expression, msg ) \ | |
| | | try { \ | |
| | | expression; \ | |
| | | } catch ( const std::exception &e ) { \ | |
| | | stringstream ss; \ | |
| | | ss << msg << " caught boost exception: " << e.what(); \ | |
| | | msgasserted( 14043 , ss.str() ); \ | |
| | | } catch ( ... ) { \ | |
| | | msgasserted( 14044 , string("unknown boost failed ") + msg ); \ | |
| | | } | |
| | | | |
| #define DESTRUCTOR_GUARD MONGO_DESTRUCTOR_GUARD | | #define DESTRUCTOR_GUARD MONGO_DESTRUCTOR_GUARD | |
| #define MONGO_DESTRUCTOR_GUARD( expression ) \ | | #define MONGO_DESTRUCTOR_GUARD( expression ) \ | |
| try { \ | | try { \ | |
| expression; \ | | expression; \ | |
| } catch ( const std::exception &e ) { \ | | } catch ( const std::exception &e ) { \ | |
| problem() << "caught exception (" << e.what() << ") in destructor (
" << __FUNCTION__ << ")" << endl; \ | | problem() << "caught exception (" << e.what() << ") in destructor (
" << __FUNCTION__ << ")" << endl; \ | |
| } catch ( ... ) { \ | | } catch ( ... ) { \ | |
| problem() << "caught unknown exception in destructor (" << __FUNCTI
ON__ << ")" << endl; \ | | problem() << "caught unknown exception in destructor (" << __FUNCTI
ON__ << ")" << endl; \ | |
| } | | } | |
|
| | | | |
| | | #undef MONGO_NORETURN | |
| | | | |
End of changes. 27 change blocks. |
| 79 lines changed or deleted | | 87 lines changed or added | |
|
| bsonelement.h | | bsonelement.h | |
| | | | |
| skipping to change at line 23 | | skipping to change at line 23 | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | |
| * See the License for the specific language governing permissions and | | * See the License for the specific language governing permissions and | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include <vector> | | #include <vector> | |
| #include <string.h> | | #include <string.h> | |
| #include "util/builder.h" | | #include "util/builder.h" | |
|
| | | #include "bsontypes.h" | |
| | | | |
| | | namespace mongo { | |
| | | class OpTime; | |
| | | class BSONObj; | |
| | | class BSONElement; | |
| | | class BSONObjBuilder; | |
| | | } | |
| | | | |
| namespace bson { | | namespace bson { | |
| typedef mongo::BSONElement be; | | typedef mongo::BSONElement be; | |
| typedef mongo::BSONObj bo; | | typedef mongo::BSONObj bo; | |
| typedef mongo::BSONObjBuilder bob; | | typedef mongo::BSONObjBuilder bob; | |
| } | | } | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| class OpTime; | | | |
| class BSONElement; | | | |
| | | | |
| /* l and r MUST have same type when called: check that first. */ | | /* l and r MUST have same type when called: check that first. */ | |
| int compareElementValues(const BSONElement& l, const BSONElement& r); | | int compareElementValues(const BSONElement& l, const BSONElement& r); | |
| | | | |
|
| /** BSONElement represents an "element" in a BSONObj. So for the object {
a : 3, b : "abc" }, | | /** BSONElement represents an "element" in a BSONObj. So for the objec
t { a : 3, b : "abc" }, | |
| 'a : 3' is the first element (key+value). | | 'a : 3' is the first element (key+value). | |
| | | | |
| The BSONElement object points into the BSONObj's data. Thus the BSONOb
j must stay in scope | | | |
| for the life of the BSONElement. | | | |
| | | | |
|
| internals: | | The BSONElement object points into the BSONObj's data. Thus the BS
ONObj must stay in scope | |
| <type><fieldName ><value> | | for the life of the BSONElement. | |
| -------- size() ------------ | | | |
| -fieldNameSize- | | | |
| value() | | | |
| type() | | | |
| */ | | | |
| class BSONElement { | | | |
| public: | | | |
| /** These functions, which start with a capital letter, throw a UserExc
eption if the | | | |
| element is not of the required type. Example: | | | |
| | | | |
|
| string foo = obj["foo"].String(); // exception if not a string type
or DNE | | internals: | |
| | | <type><fieldName ><value> | |
| | | -------- size() ------------ | |
| | | -fieldNameSize- | |
| | | value() | |
| | | type() | |
| */ | | */ | |
|
| string String() const { return chk(mongo::String).valuestr(
); } | | class BSONElement { | |
| Date_t Date() const { return chk(mongo::Date).date(); } | | public: | |
| double Number() const { return chk(isNumber()).number(); } | | /** These functions, which start with a capital letter, throw a Use
rException if the | |
| double Double() const { return chk(NumberDouble)._numberDou
ble(); } | | element is not of the required type. Example: | |
| long long Long() const { return chk(NumberLong)._numberLong(
); } | | | |
| int Int() const { return chk(NumberInt)._numberInt();
} | | | |
| bool Bool() const { return chk(mongo::Bool).boolean();
} | | | |
| BSONObj Obj() const; | | | |
| vector<BSONElement> Array() const; // see implementation for detailed c
omments | | | |
| mongo::OID OID() const { return chk(jstOID).__oid(); } | | | |
| void Null() const { chk(isNull()); } | | | |
| void OK() const { chk(ok()); } | | | |
| | | | |
|
| /** populate v with the value of the element. If type does not match,
throw exception. | | string foo = obj["foo"].String(); // exception if not a string
type or DNE | |
| useful in templates -- see also BSONObj::Vals(). | | | |
| */ | | */ | |
|
| void Val(Date_t& v) const { v = Date(); } | | string String() const { return chk(mongo::String).value
str(); } | |
| void Val(long long& v) const { v = Long(); } | | Date_t Date() const { return chk(mongo::Date).date();
} | |
| void Val(bool& v) const { v = Bool(); } | | double Number() const { return chk(isNumber()).number()
; } | |
| void Val(BSONObj& v) const; | | double Double() const { return chk(NumberDouble)._numbe
rDouble(); } | |
| void Val(mongo::OID& v) const { v = OID(); } | | long long Long() const { return chk(NumberLong)._numberL
ong(); } | |
| void Val(int& v) const { v = Int(); } | | int Int() const { return chk(NumberInt)._numberIn
t(); } | |
| void Val(double& v) const { v = Double(); } | | bool Bool() const { return chk(mongo::Bool).boolean
(); } | |
| void Val(string& v) const { v = String(); } | | vector<BSONElement> Array() const; // see implementation for detail
ed comments | |
| | | mongo::OID OID() const { return chk(jstOID).__oid(); } | |
| | | void Null() const { chk(isNull()); } // throw UserE
xception if not null | |
| | | void OK() const { chk(ok()); } // throw UserE
xception if element DNE | |
| | | | |
|
| /** Use ok() to check if a value is assigned: | | /** @return the embedded object associated with this field. | |
| if( myObj["foo"].ok() ) ... | | Note the returned object is a reference to within the parent bs
on object. If that | |
| */ | | object is out of scope, this pointer will no longer be valid. Ca
ll getOwned() on the | |
| bool ok() const { return !eoo(); } | | returned BSONObj if you need your own copy. | |
| | | throws UserException if the element is not of type object. | |
| | | */ | |
| | | BSONObj Obj() const; | |
| | | | |
|
| string toString( bool includeFieldName = true, bool full=false) const; | | /** populate v with the value of the element. If type does not mat
ch, throw exception. | |
| void toString(StringBuilder& s, bool includeFieldName = true, bool full
=false) const; | | useful in templates -- see also BSONObj::Vals(). | |
| string jsonString( JsonStringFormat format, bool includeFieldNames = tr
ue, int pretty = 0 ) const; | | */ | |
| operator string() const { return toString(); } | | void Val(Date_t& v) const { v = Date(); } | |
| | | void Val(long long& v) const { v = Long(); } | |
| | | void Val(bool& v) const { v = Bool(); } | |
| | | void Val(BSONObj& v) const; | |
| | | void Val(mongo::OID& v) const { v = OID(); } | |
| | | void Val(int& v) const { v = Int(); } | |
| | | void Val(double& v) const { v = Double(); } | |
| | | void Val(string& v) const { v = String(); } | |
| | | | |
|
| /** Returns the type of the element */ | | /** Use ok() to check if a value is assigned: | |
| BSONType type() const { return (BSONType) *data; } | | if( myObj["foo"].ok() ) ... | |
| | | */ | |
| | | bool ok() const { return !eoo(); } | |
| | | | |
|
| /** retrieve a field within this element | | string toString( bool includeFieldName = true, bool full=false) con
st; | |
| throws exception if *this is not an embedded object | | void toString(StringBuilder& s, bool includeFieldName = true, bool
full=false) const; | |
| */ | | string jsonString( JsonStringFormat format, bool includeFieldNames
= true, int pretty = 0 ) const; | |
| BSONElement operator[] (const string& field) const; | | operator string() const { return toString(); } | |
| | | | |
|
| /** returns the tyoe of the element fixed for the main type | | /** Returns the type of the element */ | |
| the main purpose is numbers. any numeric type will return NumberDo
uble | | BSONType type() const { return (BSONType) *data; } | |
| Note: if the order changes, indexes have to be re-built or than can
be corruption | | | |
| */ | | | |
| int canonicalType() const; | | | |
| | | | |
|
| /** Indicates if it is the end-of-object element, which is present at t
he end of | | /** retrieve a field within this element | |
| every BSON object. | | throws exception if *this is not an embedded object | |
| */ | | */ | |
| bool eoo() const { return type() == EOO; } | | BSONElement operator[] (const string& field) const; | |
| | | | |
|
| /** Size of the element. | | /** returns the tyoe of the element fixed for the main type | |
| @param maxLen If maxLen is specified, don't scan more than maxLen b
ytes to calculate size. | | the main purpose is numbers. any numeric type will return Numb
erDouble | |
| */ | | Note: if the order changes, indexes have to be re-built or than
can be corruption | |
| int size( int maxLen = -1 ) const; | | */ | |
| | | int canonicalType() const; | |
| | | | |
|
| /** Wrap this element up as a singleton object. */ | | /** Indicates if it is the end-of-object element, which is present
at the end of | |
| BSONObj wrap() const; | | every BSON object. | |
| | | */ | |
| | | bool eoo() const { return type() == EOO; } | |
| | | | |
|
| /** Wrap this element up as a singleton object with a new name. */ | | /** Size of the element. | |
| BSONObj wrap( const char* newName) const; | | @param maxLen If maxLen is specified, don't scan more than maxL
en bytes to calculate size. | |
| | | */ | |
| | | int size( int maxLen ) const; | |
| | | int size() const; | |
| | | | |
|
| /** field name of the element. e.g., for | | /** Wrap this element up as a singleton object. */ | |
| name : "Joe" | | BSONObj wrap() const; | |
| "name" is the fieldname | | | |
| */ | | | |
| const char * fieldName() const { | | | |
| if ( eoo() ) return ""; // no fieldname for it. | | | |
| return data + 1; | | | |
| } | | | |
| | | | |
|
| /** raw data of the element's value (so be careful). */ | | /** Wrap this element up as a singleton object with a new name. */ | |
| const char * value() const { | | BSONObj wrap( const char* newName) const; | |
| return (data + fieldNameSize() + 1); | | | |
| } | | | |
| /** size in bytes of the element's value (when applicable). */ | | | |
| int valuesize() const { | | | |
| return size() - fieldNameSize() - 1; | | | |
| } | | | |
| | | | |
|
| bool isBoolean() const { return type() == mongo::Bool; } | | /** field name of the element. e.g., for | |
| | | name : "Joe" | |
| | | "name" is the fieldname | |
| | | */ | |
| | | const char * fieldName() const { | |
| | | if ( eoo() ) return ""; // no fieldname for it. | |
| | | return data + 1; | |
| | | } | |
| | | | |
|
| /** @return value of a boolean element. | | /** raw data of the element's value (so be careful). */ | |
| You must assure element is a boolean before | | const char * value() const { | |
| calling. */ | | return (data + fieldNameSize() + 1); | |
| bool boolean() const { | | } | |
| return *value() ? true : false; | | /** size in bytes of the element's value (when applicable). */ | |
| } | | int valuesize() const { | |
| | | return size() - fieldNameSize() - 1; | |
| | | } | |
| | | | |
|
| /** Retrieve a java style date value from the element. | | bool isBoolean() const { return type() == mongo::Bool; } | |
| Ensure element is of type Date before calling. | | | |
| */ | | | |
| Date_t date() const { | | | |
| return *reinterpret_cast< const Date_t* >( value() ); | | | |
| } | | | |
| | | | |
|
| /** Convert the value to boolean, regardless of its type, in a javascri
pt-like fashion | | /** @return value of a boolean element. | |
| (i.e., treat zero and null as false). | | You must assure element is a boolean before | |
| */ | | calling. */ | |
| bool trueValue() const; | | bool boolean() const { | |
| | | return *value() ? true : false; | |
| | | } | |
| | | | |
|
| /** True if number, string, bool, date, OID */ | | bool booleanSafe() const { return isBoolean() && boolean(); } | |
| bool isSimpleType() const; | | | |
| | | | |
|
| /** True if element is of a numeric type. */ | | /** Retrieve a java style date value from the element. | |
| bool isNumber() const; | | Ensure element is of type Date before calling. | |
| | | @see Bool(), trueValue() | |
| | | */ | |
| | | Date_t date() const { | |
| | | return *reinterpret_cast< const Date_t* >( value() ); | |
| | | } | |
| | | | |
|
| /** Return double value for this field. MUST be NumberDouble type. */ | | /** Convert the value to boolean, regardless of its type, in a java
script-like fashion | |
| double _numberDouble() const {return *reinterpret_cast< const double* >
( value() ); } | | (i.e., treats zero and null and eoo as false). | |
| /** Return double value for this field. MUST be NumberInt type. */ | | */ | |
| int _numberInt() const {return *reinterpret_cast< const int* >( value()
); } | | bool trueValue() const; | |
| /** Return double value for this field. MUST be NumberLong type. */ | | | |
| long long _numberLong() const {return *reinterpret_cast< const long lon
g* >( value() ); } | | | |
| | | | |
|
| /** Retrieve int value for the element safely. Zero returned if not a
number. */ | | /** True if number, string, bool, date, OID */ | |
| int numberInt() const; | | bool isSimpleType() const; | |
| /** Retrieve long value for the element safely. Zero returned if not a
number. */ | | | |
| long long numberLong() const; | | | |
| /** Retrieve the numeric value of the element. If not of a numeric typ
e, returns 0. | | | |
| Note: casts to double, data loss may occur with large (>52 bit) Num
berLong values. | | | |
| */ | | | |
| double numberDouble() const; | | | |
| /** Retrieve the numeric value of the element. If not of a numeric typ
e, returns 0. | | | |
| Note: casts to double, data loss may occur with large (>52 bit) Num
berLong values. | | | |
| */ | | | |
| double number() const { return numberDouble(); } | | | |
| | | | |
|
| /** Retrieve the object ID stored in the object. | | /** True if element is of a numeric type. */ | |
| You must ensure the element is of type jstOID first. */ | | bool isNumber() const; | |
| const mongo::OID &__oid() const { return *reinterpret_cast< const mongo
::OID* >( value() ); } | | | |
| | | | |
|
| /** True if element is null. */ | | /** Return double value for this field. MUST be NumberDouble type.
*/ | |
| bool isNull() const { | | double _numberDouble() const {return *reinterpret_cast< const doubl
e* >( value() ); } | |
| return type() == jstNULL; | | /** Return double value for this field. MUST be NumberInt type. */ | |
| } | | int _numberInt() const {return *reinterpret_cast< const int* >( val
ue() ); } | |
| | | /** Return double value for this field. MUST be NumberLong type. */ | |
| | | long long _numberLong() const {return *reinterpret_cast< const long
long* >( value() ); } | |
| | | | |
|
| /** Size (length) of a string element. | | /** Retrieve int value for the element safely. Zero returned if no
t a number. */ | |
| You must assure of type String first. */ | | int numberInt() const; | |
| int valuestrsize() const { | | /** Retrieve long value for the element safely. Zero returned if n
ot a number. */ | |
| return *reinterpret_cast< const int* >( value() ); | | long long numberLong() const; | |
| } | | /** Retrieve the numeric value of the element. If not of a numeric
type, returns 0. | |
| | | Note: casts to double, data loss may occur with large (>52 bit)
NumberLong values. | |
| | | */ | |
| | | double numberDouble() const; | |
| | | /** Retrieve the numeric value of the element. If not of a numeric
type, returns 0. | |
| | | Note: casts to double, data loss may occur with large (>52 bit)
NumberLong values. | |
| | | */ | |
| | | double number() const { return numberDouble(); } | |
| | | | |
|
| // for objects the size *includes* the size of the size field | | /** Retrieve the object ID stored in the object. | |
| int objsize() const { | | You must ensure the element is of type jstOID first. */ | |
| return *reinterpret_cast< const int* >( value() ); | | const mongo::OID &__oid() const { return *reinterpret_cast< const m
ongo::OID* >( value() ); } | |
| } | | | |
| | | | |
|
| /** Get a string's value. Also gives you start of the real data for an
embedded object. | | /** True if element is null. */ | |
| You must assure data is of an appropriate type first -- see also va
luestrsafe(). | | bool isNull() const { | |
| */ | | return type() == jstNULL; | |
| const char * valuestr() const { | | } | |
| return value() + 4; | | | |
| } | | | |
| | | | |
|
| /** Get the string value of the element. If not a string returns "". *
/ | | /** Size (length) of a string element. | |
| const char *valuestrsafe() const { | | You must assure of type String first. | |
| return type() == mongo::String ? valuestr() : ""; | | @return string size including terminating null | |
| } | | */ | |
| /** Get the string value of the element. If not a string returns "". *
/ | | int valuestrsize() const { | |
| string str() const { | | return *reinterpret_cast< const int* >( value() ); | |
| return type() == mongo::String ? string(valuestr(), valuestrsize()-
1) : string(); | | } | |
| } | | | |
| | | | |
|
| /** Get javascript code of a CodeWScope data element. */ | | // for objects the size *includes* the size of the size field | |
| const char * codeWScopeCode() const { | | int objsize() const { | |
| return value() + 8; | | return *reinterpret_cast< const int* >( value() ); | |
| } | | } | |
| /** Get the scope SavedContext of a CodeWScope data element. */ | | | |
| const char * codeWScopeScopeData() const { | | | |
| // TODO fix | | | |
| return codeWScopeCode() + strlen( codeWScopeCode() ) + 1; | | | |
| } | | | |
| | | | |
|
| /** Get the embedded object this element holds. */ | | /** Get a string's value. Also gives you start of the real data fo
r an embedded object. | |
| BSONObj embeddedObject() const; | | You must assure data is of an appropriate type first -- see als
o valuestrsafe(). | |
| | | */ | |
| | | const char * valuestr() const { | |
| | | return value() + 4; | |
| | | } | |
| | | | |
|
| /* uasserts if not an object */ | | /** Get the string value of the element. If not a string returns "
". */ | |
| BSONObj embeddedObjectUserCheck() const; | | const char *valuestrsafe() const { | |
| | | return type() == mongo::String ? valuestr() : ""; | |
| | | } | |
| | | /** Get the string value of the element. If not a string returns "
". */ | |
| | | string str() const { | |
| | | return type() == mongo::String ? string(valuestr(), valuestrsiz
e()-1) : string(); | |
| | | } | |
| | | | |
|
| BSONObj codeWScopeObject() const; | | /** Get javascript code of a CodeWScope data element. */ | |
| | | const char * codeWScopeCode() const { | |
| | | return value() + 8; | |
| | | } | |
| | | /** Get the scope SavedContext of a CodeWScope data element. */ | |
| | | const char * codeWScopeScopeData() const { | |
| | | // TODO fix | |
| | | return codeWScopeCode() + strlen( codeWScopeCode() ) + 1; | |
| | | } | |
| | | | |
|
| /** Get raw binary data. Element must be of type BinData. Doesn't hand
le type 2 specially */ | | /** Get the embedded object this element holds. */ | |
| const char *binData(int& len) const { | | BSONObj embeddedObject() const; | |
| // BinData: <int len> <byte subtype> <byte[len] data> | | | |
| assert( type() == BinData ); | | /* uasserts if not an object */ | |
| len = valuestrsize(); | | BSONObj embeddedObjectUserCheck() const; | |
| return value() + 5; | | | |
| } | | BSONObj codeWScopeObject() const; | |
| /** Get binary data. Element must be of type BinData. Handles type 2 *
/ | | | |
| const char *binDataClean(int& len) const { | | /** Get raw binary data. Element must be of type BinData. Doesn't
handle type 2 specially */ | |
| // BinData: <int len> <byte subtype> <byte[len] data> | | const char *binData(int& len) const { | |
| if (binDataType() != ByteArrayDeprecated){ | | // BinData: <int len> <byte subtype> <byte[len] data> | |
| return binData(len); | | assert( type() == BinData ); | |
| } else { | | len = valuestrsize(); | |
| // Skip extra size | | return value() + 5; | |
| len = valuestrsize() - 4; | | } | |
| return value() + 5 + 4; | | /** Get binary data. Element must be of type BinData. Handles type
2 */ | |
| | | const char *binDataClean(int& len) const { | |
| | | // BinData: <int len> <byte subtype> <byte[len] data> | |
| | | if (binDataType() != ByteArrayDeprecated) { | |
| | | return binData(len); | |
| | | } | |
| | | else { | |
| | | // Skip extra size | |
| | | len = valuestrsize() - 4; | |
| | | return value() + 5 + 4; | |
| | | } | |
| } | | } | |
|
| } | | | |
| | | | |
|
| BinDataType binDataType() const { | | BinDataType binDataType() const { | |
| // BinData: <int len> <byte subtype> <byte[len] data> | | // BinData: <int len> <byte subtype> <byte[len] data> | |
| assert( type() == BinData ); | | assert( type() == BinData ); | |
| unsigned char c = (value() + 4)[0]; | | unsigned char c = (value() + 4)[0]; | |
| return (BinDataType)c; | | return (BinDataType)c; | |
| } | | } | |
| | | | |
|
| /** Retrieve the regex string for a Regex element */ | | /** Retrieve the regex string for a Regex element */ | |
| const char *regex() const { | | const char *regex() const { | |
| assert(type() == RegEx); | | assert(type() == RegEx); | |
| return value(); | | return value(); | |
| } | | } | |
| | | | |
|
| /** Retrieve the regex flags (options) for a Regex element */ | | /** Retrieve the regex flags (options) for a Regex element */ | |
| const char *regexFlags() const { | | const char *regexFlags() const { | |
| const char *p = regex(); | | const char *p = regex(); | |
| return p + strlen(p) + 1; | | return p + strlen(p) + 1; | |
| } | | } | |
| | | | |
|
| /** like operator== but doesn't check the fieldname, | | /** like operator== but doesn't check the fieldname, | |
| just the value. | | just the value. | |
| */ | | */ | |
| bool valuesEqual(const BSONElement& r) const { | | bool valuesEqual(const BSONElement& r) const { | |
| return woCompare( r , false ) == 0; | | return woCompare( r , false ) == 0; | |
| } | | } | |
| | | | |
|
| /** Returns true if elements are equal. */ | | /** Returns true if elements are equal. */ | |
| bool operator==(const BSONElement& r) const { | | bool operator==(const BSONElement& r) const { | |
| return woCompare( r , true ) == 0; | | return woCompare( r , true ) == 0; | |
| } | | } | |
| | | | |
|
| /** Well ordered comparison. | | /** Well ordered comparison. | |
| @return <0: l<r. 0:l==r. >0:l>r | | @return <0: l<r. 0:l==r. >0:l>r | |
| order by type, field name, and field value. | | order by type, field name, and field value. | |
| If considerFieldName is true, pay attention to the field name. | | If considerFieldName is true, pay attention to the field name. | |
| */ | | */ | |
| int woCompare( const BSONElement &e, bool considerFieldName = true ) co
nst; | | int woCompare( const BSONElement &e, bool considerFieldName = true
) const; | |
| | | | |
|
| const char * rawdata() const { | | const char * rawdata() const { return data; } | |
| return data; | | | |
| } | | | |
| | | | |
|
| /** 0 == Equality, just not defined yet */ | | /** 0 == Equality, just not defined yet */ | |
| int getGtLtOp( int def = 0 ) const; | | int getGtLtOp( int def = 0 ) const; | |
| | | | |
|
| /** Constructs an empty element */ | | /** Constructs an empty element */ | |
| BSONElement(); | | BSONElement(); | |
| | | | |
|
| /** Check that data is internally consistent. */ | | /** Check that data is internally consistent. */ | |
| void validate() const; | | void validate() const; | |
| | | | |
|
| /** True if this element may contain subobjects. */ | | /** True if this element may contain subobjects. */ | |
| bool mayEncapsulate() const { | | bool mayEncapsulate() const { | |
| switch ( type() ){ | | switch ( type() ) { | |
| case Object: | | case Object: | |
| case mongo::Array: | | case mongo::Array: | |
| case CodeWScope: | | case CodeWScope: | |
| return true; | | return true; | |
| default: | | default: | |
| return false; | | return false; | |
| | | } | |
| } | | } | |
|
| } | | | |
| | | | |
|
| /** True if this element can be a BSONObj */ | | /** True if this element can be a BSONObj */ | |
| bool isABSONObj() const { | | bool isABSONObj() const { | |
| switch( type() ){ | | switch( type() ) { | |
| case Object: | | case Object: | |
| case mongo::Array: | | case mongo::Array: | |
| return true; | | return true; | |
| default: | | default: | |
| return false; | | return false; | |
| | | } | |
| } | | } | |
|
| } | | | |
| | | | |
|
| Date_t timestampTime() const{ | | Date_t timestampTime() const { | |
| unsigned long long t = ((unsigned int*)(value() + 4 ))[0]; | | unsigned long long t = ((unsigned int*)(value() + 4 ))[0]; | |
| return t * 1000; | | return t * 1000; | |
| } | | } | |
| unsigned int timestampInc() const{ | | unsigned int timestampInc() const { | |
| return ((unsigned int*)(value() ))[0]; | | return ((unsigned int*)(value() ))[0]; | |
| } | | } | |
| | | | |
|
| const char * dbrefNS() const { | | const char * dbrefNS() const { | |
| uassert( 10063 , "not a dbref" , type() == DBRef ); | | uassert( 10063 , "not a dbref" , type() == DBRef ); | |
| return value() + 4; | | return value() + 4; | |
| } | | } | |
| | | | |
|
| const mongo::OID& dbrefOID() const { | | const mongo::OID& dbrefOID() const { | |
| uassert( 10064 , "not a dbref" , type() == DBRef ); | | uassert( 10064 , "not a dbref" , type() == DBRef ); | |
| const char * start = value(); | | const char * start = value(); | |
| start += 4 + *reinterpret_cast< const int* >( start ); | | start += 4 + *reinterpret_cast< const int* >( start ); | |
| return *reinterpret_cast< const mongo::OID* >( start ); | | return *reinterpret_cast< const mongo::OID* >( start ); | |
| } | | } | |
| | | | |
|
| bool operator<( const BSONElement& other ) const { | | /** this does not use fieldName in the comparison, just the value *
/ | |
| int x = (int)canonicalType() - (int)other.canonicalType(); | | bool operator<( const BSONElement& other ) const { | |
| if ( x < 0 ) return true; | | int x = (int)canonicalType() - (int)other.canonicalType(); | |
| else if ( x > 0 ) return false; | | if ( x < 0 ) return true; | |
| return compareElementValues(*this,other) < 0; | | else if ( x > 0 ) return false; | |
| } | | return compareElementValues(*this,other) < 0; | |
| | | } | |
| | | | |
|
| // If maxLen is specified, don't scan more than maxLen bytes. | | // @param maxLen don't scan more than maxLen bytes | |
| explicit BSONElement(const char *d, int maxLen = -1) : data(d) { | | explicit BSONElement(const char *d, int maxLen) : data(d) { | |
| fieldNameSize_ = -1; | | if ( eoo() ) { | |
| if ( eoo() ) | | totalSize = 1; | |
| fieldNameSize_ = 0; | | fieldNameSize_ = 0; | |
| else { | | } | |
| if ( maxLen != -1 ) { | | else { | |
| int size = (int) strnlen( fieldName(), maxLen - 1 ); | | totalSize = -1; | |
| massert( 10333 , "Invalid field name", size != -1 ); | | fieldNameSize_ = -1; | |
| fieldNameSize_ = size + 1; | | if ( maxLen != -1 ) { | |
| | | int size = (int) strnlen( fieldName(), maxLen - 1 ); | |
| | | massert( 10333 , "Invalid field name", size != -1 ); | |
| | | fieldNameSize_ = size + 1; | |
| | | } | |
| } | | } | |
| } | | } | |
|
| totalSize = -1; | | | |
| } | | | |
| | | | |
|
| string _asCode() const; | | explicit BSONElement(const char *d) : data(d) { | |
| OpTime _opTime() const; | | fieldNameSize_ = -1; | |
| | | totalSize = -1; | |
| | | if ( eoo() ) { | |
| | | fieldNameSize_ = 0; | |
| | | totalSize = 1; | |
| | | } | |
| | | } | |
| | | | |
|
| private: | | string _asCode() const; | |
| const char *data; | | OpTime _opTime() const; | |
| mutable int fieldNameSize_; // cached value | | | |
| int fieldNameSize() const { | | | |
| if ( fieldNameSize_ == -1 ) | | | |
| fieldNameSize_ = (int)strlen( fieldName() ) + 1; | | | |
| return fieldNameSize_; | | | |
| } | | | |
| mutable int totalSize; /* caches the computed size */ | | | |
| | | | |
|
| friend class BSONObjIterator; | | private: | |
| friend class BSONObj; | | const char *data; | |
| const BSONElement& chk(int t) const { | | mutable int fieldNameSize_; // cached value | |
| if ( t != type() ){ | | int fieldNameSize() const { | |
| StringBuilder ss; | | if ( fieldNameSize_ == -1 ) | |
| ss << "wrong type for BSONElement (" << fieldName() << ") " <<
type() << " != " << t; | | fieldNameSize_ = (int)strlen( fieldName() ) + 1; | |
| uasserted(13111, ss.str() ); | | return fieldNameSize_; | |
| } | | } | |
|
| return *this; | | mutable int totalSize; /* caches the computed size */ | |
| } | | | |
| const BSONElement& chk(bool expr) const { | | friend class BSONObjIterator; | |
| uassert(13118, "unexpected or missing type value in BSON object", e
xpr); | | friend class BSONObj; | |
| return *this; | | const BSONElement& chk(int t) const { | |
| } | | if ( t != type() ) { | |
| }; | | StringBuilder ss; | |
| | | if( eoo() ) | |
| | | ss << "field not found, expected type " << t; | |
| | | else | |
| | | ss << "wrong type for field (" << fieldName() << ") " <
< type() << " != " << t; | |
| | | uasserted(13111, ss.str() ); | |
| | | } | |
| | | return *this; | |
| | | } | |
| | | const BSONElement& chk(bool expr) const { | |
| | | uassert(13118, "unexpected or missing type value in BSON object
", expr); | |
| | | return *this; | |
| | | } | |
| | | }; | |
| | | | |
| inline int BSONElement::canonicalType() const { | | inline int BSONElement::canonicalType() const { | |
| BSONType t = type(); | | BSONType t = type(); | |
|
| switch ( t ){ | | switch ( t ) { | |
| case MinKey: | | case MinKey: | |
| case MaxKey: | | case MaxKey: | |
| return t; | | return t; | |
| case EOO: | | case EOO: | |
| case Undefined: | | case Undefined: | |
| return 0; | | return 0; | |
| case jstNULL: | | case jstNULL: | |
| return 5; | | return 5; | |
| case NumberDouble: | | case NumberDouble: | |
| case NumberInt: | | case NumberInt: | |
| | | | |
| skipping to change at line 449 | | skipping to change at line 481 | |
| case DBRef: | | case DBRef: | |
| return 55; | | return 55; | |
| case Code: | | case Code: | |
| return 60; | | return 60; | |
| case CodeWScope: | | case CodeWScope: | |
| return 65; | | return 65; | |
| default: | | default: | |
| assert(0); | | assert(0); | |
| return -1; | | return -1; | |
| } | | } | |
|
| } | | } | |
| | | | |
| inline bool BSONElement::trueValue() const { | | inline bool BSONElement::trueValue() const { | |
| switch( type() ) { | | switch( type() ) { | |
| case NumberLong: | | case NumberLong: | |
| return *reinterpret_cast< const long long* >( value() ) != 0; | | return *reinterpret_cast< const long long* >( value() ) != 0; | |
| case NumberDouble: | | case NumberDouble: | |
| return *reinterpret_cast< const double* >( value() ) != 0; | | return *reinterpret_cast< const double* >( value() ) != 0; | |
| case NumberInt: | | case NumberInt: | |
| return *reinterpret_cast< const int* >( value() ) != 0; | | return *reinterpret_cast< const int* >( value() ) != 0; | |
| case mongo::Bool: | | case mongo::Bool: | |
| | | | |
| skipping to change at line 472 | | skipping to change at line 504 | |
| case jstNULL: | | case jstNULL: | |
| case Undefined: | | case Undefined: | |
| return false; | | return false; | |
| | | | |
| default: | | default: | |
| ; | | ; | |
| } | | } | |
| return true; | | return true; | |
| } | | } | |
| | | | |
|
| /** True if element is of a numeric type. */ | | /** @return true if element is of a numeric type. */ | |
| inline bool BSONElement::isNumber() const { | | inline bool BSONElement::isNumber() const { | |
| switch( type() ) { | | switch( type() ) { | |
| case NumberLong: | | case NumberLong: | |
| case NumberDouble: | | case NumberDouble: | |
| case NumberInt: | | case NumberInt: | |
| return true; | | return true; | |
| default: | | default: | |
| return false; | | return false; | |
| } | | } | |
| } | | } | |
| | | | |
| inline bool BSONElement::isSimpleType() const { | | inline bool BSONElement::isSimpleType() const { | |
|
| switch( type() ){ | | switch( type() ) { | |
| case NumberLong: | | case NumberLong: | |
| case NumberDouble: | | case NumberDouble: | |
| case NumberInt: | | case NumberInt: | |
| case mongo::String: | | case mongo::String: | |
| case mongo::Bool: | | case mongo::Bool: | |
| case mongo::Date: | | case mongo::Date: | |
| case jstOID: | | case jstOID: | |
| return true; | | return true; | |
| default: | | default: | |
| return false; | | return false; | |
| | | | |
End of changes. 67 change blocks. |
| 313 lines changed or deleted | | 345 lines changed or added | |
|
| bsonobj.h | | bsonobj.h | |
| | | | |
| skipping to change at line 20 | | skipping to change at line 20 | |
| * | | * | |
| * Unless required by applicable law or agreed to in writing, software | | * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | | * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | |
| * See the License for the specific language governing permissions and | | * See the License for the specific language governing permissions and | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
|
| | | #include <boost/intrusive_ptr.hpp> | |
| #include <set> | | #include <set> | |
| #include <list> | | #include <list> | |
| #include <vector> | | #include <vector> | |
|
| | | #include "util/atomic_int.h" | |
| #include "util/builder.h" | | #include "util/builder.h" | |
| #include "stringdata.h" | | #include "stringdata.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| typedef set< BSONElement, BSONElementCmpWithoutField > BSONElementSet; | | typedef set< BSONElement, BSONElementCmpWithoutField > BSONElementSet; | |
|
| | | typedef multiset< BSONElement, BSONElementCmpWithoutField > BSONElement
MSet; | |
| const int BSONObjMaxSize = 32 * 1024 * 1024; | | | |
| | | | |
| /** | | /** | |
|
| C++ representation of a "BSON" object -- that is, an extended JSO
N-style | | C++ representation of a "BSON" object -- that is, an extended JSON-s
tyle | |
| object in a binary representation. | | object in a binary representation. | |
| | | | |
| See bsonspec.org. | | See bsonspec.org. | |
| | | | |
| Note that BSONObj's have a smart pointer capability built in -- so y
ou can | | Note that BSONObj's have a smart pointer capability built in -- so y
ou can | |
| pass them around by value. The reference counts used to implement t
his | | pass them around by value. The reference counts used to implement t
his | |
| do not use locking, so copying and destroying BSONObj's are not thre
ad-safe | | do not use locking, so copying and destroying BSONObj's are not thre
ad-safe | |
| operations. | | operations. | |
| | | | |
| BSON object format: | | BSON object format: | |
| | | | |
| skipping to change at line 74 | | skipping to change at line 75 | |
| BinData: <int len> <byte subtype> <byte[len] data> | | BinData: <int len> <byte subtype> <byte[len] data> | |
| Code: a function (not a closure): same format as String. | | Code: a function (not a closure): same format as String. | |
| Symbol: a language symbol (say a python symbol). same format as St
ring. | | Symbol: a language symbol (say a python symbol). same format as St
ring. | |
| Code With Scope: <total size><String><Object> | | Code With Scope: <total size><String><Object> | |
| \endcode | | \endcode | |
| */ | | */ | |
| class BSONObj { | | class BSONObj { | |
| public: | | public: | |
| | | | |
| /** Construct a BSONObj from data in the proper format. | | /** Construct a BSONObj from data in the proper format. | |
|
| @param ifree true if the BSONObj should free() the msgdata when | | * Use this constructor when something else owns msgdata's buffer | |
| it destructs. | | */ | |
| */ | | explicit BSONObj(const char *msgdata) { | |
| explicit BSONObj(const char *msgdata, bool ifree = false) { | | init(msgdata); | |
| init(msgdata, ifree); | | | |
| } | | } | |
|
| BSONObj(const Record *r); | | | |
| | | /** Construct a BSONObj from data in the proper format. | |
| | | * Use this constructor when you want BSONObj to free(holder) when
it is no longer needed | |
| | | * BSONObj::Holder has an extra 4 bytes for a ref-count before the
start of the object | |
| | | */ | |
| | | class Holder; | |
| | | explicit BSONObj(Holder* holder) { | |
| | | init(holder); | |
| | | } | |
| | | | |
| | | explicit BSONObj(const Record *r); | |
| | | | |
| /** Construct an empty BSONObj -- that is, {}. */ | | /** Construct an empty BSONObj -- that is, {}. */ | |
| BSONObj(); | | BSONObj(); | |
|
| // defensive | | | |
| ~BSONObj() { _objdata = 0; } | | | |
| | | | |
|
| void appendSelfToBufBuilder(BufBuilder& b) const { | | ~BSONObj() { | |
| assert( objsize() ); | | _objdata = 0; // defensive | |
| b.appendBuf(reinterpret_cast<const void *>( objdata() ), objsiz
e()); | | | |
| } | | } | |
| | | | |
|
| | | /** | |
| | | A BSONObj can use a buffer it "owns" or one it does not. | |
| | | | |
| | | OWNED CASE | |
| | | If the BSONObj owns the buffer, the buffer can be shared among s
everal BSONObj's (by assignment). | |
| | | In this case the buffer is basically implemented as a shared_ptr
. | |
| | | Since BSONObj's are typically immutable, this works well. | |
| | | | |
| | | UNOWNED CASE | |
| | | A BSONObj can also point to BSON data in some other data structu
re it does not "own" or free later. | |
| | | For example, in a memory mapped file. In this case, it is impor
tant the original data stays in | |
| | | scope for as long as the BSONObj is in use. If you think the or
iginal data may go out of scope, | |
| | | call BSONObj::getOwned() to promote your BSONObj to having its o
wn copy. | |
| | | | |
| | | On a BSONObj assignment, if the source is unowned, both the sour
ce and dest will have unowned | |
| | | pointers to the original buffer after the assignment. | |
| | | | |
| | | If you are not sure about ownership but need the buffer to last
as long as the BSONObj, call | |
| | | getOwned(). getOwned() is a no-op if the buffer is already owne
d. If not already owned, a malloc | |
| | | and memcpy will result. | |
| | | | |
| | | Most ways to create BSONObj's create 'owned' variants. Unowned
versions can be created with: | |
| | | (1) specifying true for the ifree parameter in the constructor | |
| | | (2) calling BSONObjBuilder::done(). Use BSONObjBuilder::obj() t
o get an owned copy | |
| | | (3) retrieving a subobject retrieves an unowned pointer into the
parent BSON object | |
| | | | |
| | | @return true if this is in owned mode | |
| | | */ | |
| | | bool isOwned() const { return _holder.get() != 0; } | |
| | | | |
| | | /** assure the data buffer is under the control of this BSONObj and
not a remote buffer | |
| | | @see isOwned() | |
| | | */ | |
| | | BSONObj getOwned() const; | |
| | | | |
| | | /** @return a new full (and owned) copy of the object. */ | |
| | | BSONObj copy() const; | |
| | | | |
| /** Readable representation of a BSON object in an extended JSON-st
yle notation. | | /** Readable representation of a BSON object in an extended JSON-st
yle notation. | |
| This is an abbreviated representation which might be used for l
ogging. | | This is an abbreviated representation which might be used for l
ogging. | |
| */ | | */ | |
| string toString( bool isArray = false, bool full=false ) const; | | string toString( bool isArray = false, bool full=false ) const; | |
| void toString(StringBuilder& s, bool isArray = false, bool full=fal
se ) const; | | void toString(StringBuilder& s, bool isArray = false, bool full=fal
se ) const; | |
| | | | |
| /** Properly formatted JSON string. | | /** Properly formatted JSON string. | |
| @param pretty if true we try to add some lf's and indentation | | @param pretty if true we try to add some lf's and indentation | |
| */ | | */ | |
| string jsonString( JsonStringFormat format = Strict, int pretty = 0
) const; | | string jsonString( JsonStringFormat format = Strict, int pretty = 0
) const; | |
| | | | |
| /** note: addFields always adds _id even if not specified */ | | /** note: addFields always adds _id even if not specified */ | |
| int addFields(BSONObj& from, set<string>& fields); /* returns n add
ed */ | | int addFields(BSONObj& from, set<string>& fields); /* returns n add
ed */ | |
| | | | |
|
| | | /** remove specified field and return a new object with the remaini
ng fields. | |
| | | slowish as builds a full new object | |
| | | */ | |
| | | BSONObj removeField(const StringData& name) const; | |
| | | | |
| /** returns # of top level fields in the object | | /** returns # of top level fields in the object | |
| note: iterates to count the fields | | note: iterates to count the fields | |
| */ | | */ | |
| int nFields() const; | | int nFields() const; | |
| | | | |
| /** adds the field names to the fields set. does NOT clear it (app
ends). */ | | /** adds the field names to the fields set. does NOT clear it (app
ends). */ | |
| int getFieldNames(set<string>& fields) const; | | int getFieldNames(set<string>& fields) const; | |
| | | | |
|
| /** return has eoo() true if no match | | /** @return the specified element. element.eoo() will be true if n
ot found. | |
| supports "." notation to reach into embedded objects | | @param name field to find. supports dot (".") notation to reach
into embedded objects. | |
| | | for example "x.y" means "in the nested object in field x, retr
ieve field y" | |
| */ | | */ | |
| BSONElement getFieldDotted(const char *name) const; | | BSONElement getFieldDotted(const char *name) const; | |
|
| /** return has eoo() true if no match | | /** @return the specified element. element.eoo() will be true if n
ot found. | |
| supports "." notation to reach into embedded objects | | @param name field to find. supports dot (".") notation to reach
into embedded objects. | |
| | | for example "x.y" means "in the nested object in field x, retr
ieve field y" | |
| */ | | */ | |
| BSONElement getFieldDotted(const string& name) const { | | BSONElement getFieldDotted(const string& name) const { | |
| return getFieldDotted( name.c_str() ); | | return getFieldDotted( name.c_str() ); | |
| } | | } | |
| | | | |
|
| /** Like getFieldDotted(), but expands multikey arrays and returns
all matching objects | | /** Like getFieldDotted(), but expands arrays and returns all match
ing objects. | |
| | | * Turning off expandLastArray allows you to retrieve nested array
objects instead of | |
| | | * their contents. | |
| */ | | */ | |
|
| void getFieldsDotted(const StringData& name, BSONElementSet &ret )
const; | | void getFieldsDotted(const StringData& name, BSONElementSet &ret, b
ool expandLastArray = true ) const; | |
| | | void getFieldsDotted(const StringData& name, BSONElementMSet &ret,
bool expandLastArray = true ) const; | |
| | | | |
| /** Like getFieldDotted(), but returns first array encountered whil
e traversing the | | /** Like getFieldDotted(), but returns first array encountered whil
e traversing the | |
| dotted fields of name. The name variable is updated to represe
nt field | | dotted fields of name. The name variable is updated to represe
nt field | |
| names with respect to the returned element. */ | | names with respect to the returned element. */ | |
| BSONElement getFieldDottedOrArray(const char *&name) const; | | BSONElement getFieldDottedOrArray(const char *&name) const; | |
| | | | |
| /** Get the field of the specified name. eoo() is true on the retur
ned | | /** Get the field of the specified name. eoo() is true on the retur
ned | |
| element if not found. | | element if not found. | |
| */ | | */ | |
| BSONElement getField(const StringData& name) const; | | BSONElement getField(const StringData& name) const; | |
| | | | |
|
| | | /** Get several fields at once. This is faster than separate getFie
ld() calls as the size of | |
| | | elements iterated can then be calculated only once each. | |
| | | @param n number of fieldNames, and number of elements in the fi
elds array | |
| | | @param fields if a field is found its element is stored in its
corresponding position in this array. | |
| | | if not found the array element is unchanged. | |
| | | */ | |
| | | void getFields(unsigned n, const char **fieldNames, BSONElement *fi
elds) const; | |
| | | | |
| /** Get the field of the specified name. eoo() is true on the retur
ned | | /** Get the field of the specified name. eoo() is true on the retur
ned | |
| element if not found. | | element if not found. | |
| */ | | */ | |
| BSONElement operator[] (const char *field) const { | | BSONElement operator[] (const char *field) const { | |
| return getField(field); | | return getField(field); | |
| } | | } | |
| | | | |
| BSONElement operator[] (const string& field) const { | | BSONElement operator[] (const string& field) const { | |
| return getField(field); | | return getField(field); | |
| } | | } | |
| | | | |
| BSONElement operator[] (int field) const { | | BSONElement operator[] (int field) const { | |
| StringBuilder ss; | | StringBuilder ss; | |
| ss << field; | | ss << field; | |
| string s = ss.str(); | | string s = ss.str(); | |
| return getField(s.c_str()); | | return getField(s.c_str()); | |
| } | | } | |
| | | | |
|
| /** @return true if field exists */ | | /** @return true if field exists */ | |
| bool hasField( const char * name )const { | | bool hasField( const char * name ) const { return !getField(name).e
oo(); } | |
| return ! getField( name ).eoo(); | | /** @return true if field exists */ | |
| } | | bool hasElement(const char *name) const { return hasField(name); } | |
| | | | |
| /** @return "" if DNE or wrong type */ | | /** @return "" if DNE or wrong type */ | |
| const char * getStringField(const char *name) const; | | const char * getStringField(const char *name) const; | |
| | | | |
|
| /** @return subobject of the given name */ | | /** @return subobject of the given name */ | |
| BSONObj getObjectField(const char *name) const; | | BSONObj getObjectField(const char *name) const; | |
| | | | |
| /** @return INT_MIN if not present - does some type conversions */ | | /** @return INT_MIN if not present - does some type conversions */ | |
| int getIntField(const char *name) const; | | int getIntField(const char *name) const; | |
| | | | |
|
| /** @return false if not present */ | | /** @return false if not present | |
| | | @see BSONElement::trueValue() | |
| | | */ | |
| bool getBoolField(const char *name) const; | | bool getBoolField(const char *name) const; | |
| | | | |
| /** | | /** | |
| sets element field names to empty string | | sets element field names to empty string | |
| If a field in pattern is missing, it is omitted from the returne
d | | If a field in pattern is missing, it is omitted from the returne
d | |
| object. | | object. | |
| */ | | */ | |
| BSONObj extractFieldsUnDotted(BSONObj pattern) const; | | BSONObj extractFieldsUnDotted(BSONObj pattern) const; | |
| | | | |
| /** extract items from object which match a pattern object. | | /** extract items from object which match a pattern object. | |
|
| e.g., if pattern is { x : 1, y : 1 }, builds an obje
ct with | | e.g., if pattern is { x : 1, y : 1 }, builds an object with | |
| x and y elements of this object, if they are present
. | | x and y elements of this object, if they are present. | |
| returns elements with original field names | | returns elements with original field names | |
| */ | | */ | |
| BSONObj extractFields(const BSONObj &pattern , bool fillWithNull=fa
lse) const; | | BSONObj extractFields(const BSONObj &pattern , bool fillWithNull=fa
lse) const; | |
| | | | |
| BSONObj filterFieldsUndotted(const BSONObj &filter, bool inFilter)
const; | | BSONObj filterFieldsUndotted(const BSONObj &filter, bool inFilter)
const; | |
| | | | |
| BSONElement getFieldUsingIndexNames(const char *fieldName, const BS
ONObj &indexKey) const; | | BSONElement getFieldUsingIndexNames(const char *fieldName, const BS
ONObj &indexKey) const; | |
| | | | |
| /** @return the raw data of the object */ | | /** @return the raw data of the object */ | |
| const char *objdata() const { | | const char *objdata() const { | |
| return _objdata; | | return _objdata; | |
| } | | } | |
| /** @return total size of the BSON object in bytes */ | | /** @return total size of the BSON object in bytes */ | |
|
| int objsize() const { | | int objsize() const { return *(reinterpret_cast<const int*>(objdata
())); } | |
| return *(reinterpret_cast<const int*>(objdata())); | | | |
| } | | | |
| | | | |
| /** performs a cursory check on the object's size only. */ | | /** performs a cursory check on the object's size only. */ | |
|
| bool isValid(); | | bool isValid() const; | |
| | | | |
| /** @return if the user is a valid user doc | | /** @return if the user is a valid user doc | |
| criter: isValid() no . or $ field names | | criter: isValid() no . or $ field names | |
| */ | | */ | |
| bool okForStorage() const; | | bool okForStorage() const; | |
| | | | |
|
| /** @return true if object is empty -- i.e., {} */ | | /** @return true if object is empty -- i.e., {} */ | |
| bool isEmpty() const { | | bool isEmpty() const { return objsize() <= 5; } | |
| return objsize() <= 5; | | | |
| } | | | |
| | | | |
| void dump() const; | | void dump() const; | |
| | | | |
| /** Alternative output format */ | | /** Alternative output format */ | |
| string hexDump() const; | | string hexDump() const; | |
| | | | |
| /**wo='well ordered'. fields must be in same order in each object. | | /**wo='well ordered'. fields must be in same order in each object. | |
| Ordering is with respect to the signs of the elements | | Ordering is with respect to the signs of the elements | |
| and allows ascending / descending key mixing. | | and allows ascending / descending key mixing. | |
|
| @return <0 if l<r. 0 if l==r. >0 if l>r | | @return <0 if l<r. 0 if l==r. >0 if l>r | |
| */ | | */ | |
| int woCompare(const BSONObj& r, const Ordering &o, | | int woCompare(const BSONObj& r, const Ordering &o, | |
| bool considerFieldName=true) const; | | bool considerFieldName=true) const; | |
| | | | |
| /**wo='well ordered'. fields must be in same order in each object. | | /**wo='well ordered'. fields must be in same order in each object. | |
| Ordering is with respect to the signs of the elements | | Ordering is with respect to the signs of the elements | |
| and allows ascending / descending key mixing. | | and allows ascending / descending key mixing. | |
|
| @return <0 if l<r. 0 if l==r. >0 if l>r | | @return <0 if l<r. 0 if l==r. >0 if l>r | |
| */ | | */ | |
| int woCompare(const BSONObj& r, const BSONObj &ordering = BSONObj()
, | | int woCompare(const BSONObj& r, const BSONObj &ordering = BSONObj()
, | |
| bool considerFieldName=true) const; | | bool considerFieldName=true) const; | |
| | | | |
| bool operator<( const BSONObj& other ) const { return woCompare( ot
her ) < 0; } | | bool operator<( const BSONObj& other ) const { return woCompare( ot
her ) < 0; } | |
| bool operator<=( const BSONObj& other ) const { return woCompare( o
ther ) <= 0; } | | bool operator<=( const BSONObj& other ) const { return woCompare( o
ther ) <= 0; } | |
| bool operator>( const BSONObj& other ) const { return woCompare( ot
her ) > 0; } | | bool operator>( const BSONObj& other ) const { return woCompare( ot
her ) > 0; } | |
| bool operator>=( const BSONObj& other ) const { return woCompare( o
ther ) >= 0; } | | bool operator>=( const BSONObj& other ) const { return woCompare( o
ther ) >= 0; } | |
| | | | |
| /** | | /** | |
| * @param useDotted whether to treat sort key fields as possibly do
tted and expand into them | | * @param useDotted whether to treat sort key fields as possibly do
tted and expand into them | |
| */ | | */ | |
| int woSortOrder( const BSONObj& r , const BSONObj& sortKey , bool u
seDotted=false ) const; | | int woSortOrder( const BSONObj& r , const BSONObj& sortKey , bool u
seDotted=false ) const; | |
| | | | |
|
| | | bool equal(const BSONObj& r) const; | |
| | | | |
| /** This is "shallow equality" -- ints and doubles won't match. fo
r a | | /** This is "shallow equality" -- ints and doubles won't match. fo
r a | |
| deep equality test use woCompare (which is slower). | | deep equality test use woCompare (which is slower). | |
| */ | | */ | |
|
| bool woEqual(const BSONObj& r) const { | | bool shallowEqual(const BSONObj& r) const { | |
| int os = objsize(); | | int os = objsize(); | |
| if ( os == r.objsize() ) { | | if ( os == r.objsize() ) { | |
| return (os == 0 || memcmp(objdata(),r.objdata(),os)==0); | | return (os == 0 || memcmp(objdata(),r.objdata(),os)==0); | |
| } | | } | |
| return false; | | return false; | |
| } | | } | |
| | | | |
|
| /** @return first field of the object */ | | /** @return first field of the object */ | |
| BSONElement firstElement() const { | | BSONElement firstElement() const { return BSONElement(objdata() + 4
); } | |
| return BSONElement(objdata() + 4); | | | |
| } | | | |
| | | | |
|
| /** @return true if field exists in the object */ | | /** faster than firstElement().fieldName() - for the first element
we can easily find the fieldname without | |
| bool hasElement(const char *name) const; | | computing the element size. | |
| | | */ | |
| | | const char * firstElementFieldName() const { | |
| | | const char *p = objdata() + 4; | |
| | | return *p == EOO ? "" : p+1; | |
| | | } | |
| | | | |
|
| /** Get the _id field from the object. For good performance
drivers should | | /** Get the _id field from the object. For good performance driver
s should | |
| assure that _id is the first element of the object; however, co
rrect operation | | assure that _id is the first element of the object; however, co
rrect operation | |
| is assured regardless. | | is assured regardless. | |
| @return true if found | | @return true if found | |
|
| */ | | */ | |
| bool getObjectID(BSONElement& e) const; | | bool getObjectID(BSONElement& e) const; | |
| | | | |
| /** makes a copy of the object. */ | | | |
| BSONObj copy() const; | | | |
| | | | |
| /* make sure the data buffer is under the control of this BSONObj a
nd not a remote buffer */ | | | |
| BSONObj getOwned() const{ | | | |
| if ( !isOwned() ) | | | |
| return copy(); | | | |
| return *this; | | | |
| } | | | |
| bool isOwned() const { return _holder.get() != 0; } | | | |
| | | | |
| /** @return A hash code for the object */ | | /** @return A hash code for the object */ | |
| int hash() const { | | int hash() const { | |
| unsigned x = 0; | | unsigned x = 0; | |
| const char *p = objdata(); | | const char *p = objdata(); | |
| for ( int i = 0; i < objsize(); i++ ) | | for ( int i = 0; i < objsize(); i++ ) | |
| x = x * 131 + p[i]; | | x = x * 131 + p[i]; | |
| return (x & 0x7fffffff) | 0x8000000; // must be > 0 | | return (x & 0x7fffffff) | 0x8000000; // must be > 0 | |
| } | | } | |
| | | | |
| | | | |
| skipping to change at line 305 | | skipping to change at line 362 | |
| /** Return new object with the field names replaced by those in the | | /** Return new object with the field names replaced by those in the | |
| passed object. */ | | passed object. */ | |
| BSONObj replaceFieldNames( const BSONObj &obj ) const; | | BSONObj replaceFieldNames( const BSONObj &obj ) const; | |
| | | | |
| /** true unless corrupt */ | | /** true unless corrupt */ | |
| bool valid() const; | | bool valid() const; | |
| | | | |
| /** @return an md5 value for this object. */ | | /** @return an md5 value for this object. */ | |
| string md5() const; | | string md5() const; | |
| | | | |
|
| bool operator==( const BSONObj& other ) const{ | | bool operator==( const BSONObj& other ) const { return equal( other
); } | |
| return woCompare( other ) == 0; | | | |
| } | | | |
| | | | |
| enum MatchType { | | enum MatchType { | |
| Equality = 0, | | Equality = 0, | |
| LT = 0x1, | | LT = 0x1, | |
| LTE = 0x3, | | LTE = 0x3, | |
| GTE = 0x6, | | GTE = 0x6, | |
| GT = 0x4, | | GT = 0x4, | |
| opIN = 0x8, // { x : { $in : [1,2,3] } } | | opIN = 0x8, // { x : { $in : [1,2,3] } } | |
| NE = 0x9, | | NE = 0x9, | |
| opSIZE = 0x0A, | | opSIZE = 0x0A, | |
| | | | |
| skipping to change at line 336 | | skipping to change at line 391 | |
| opNEAR = 0x13, | | opNEAR = 0x13, | |
| opWITHIN = 0x14, | | opWITHIN = 0x14, | |
| opMAX_DISTANCE=0x15 | | opMAX_DISTANCE=0x15 | |
| }; | | }; | |
| | | | |
| /** add all elements of the object to the specified vector */ | | /** add all elements of the object to the specified vector */ | |
| void elems(vector<BSONElement> &) const; | | void elems(vector<BSONElement> &) const; | |
| /** add all elements of the object to the specified list */ | | /** add all elements of the object to the specified list */ | |
| void elems(list<BSONElement> &) const; | | void elems(list<BSONElement> &) const; | |
| | | | |
|
| /** add all values of the object to the specified vector. If type
mismatches, exception. */ | | /** add all values of the object to the specified vector. If type
mismatches, exception. | |
| | | this is most useful when the BSONObj is an array, but can be us
ed with non-arrays too in theory. | |
| | | | |
| | | example: | |
| | | bo sub = y["subobj"].Obj(); | |
| | | vector<int> myints; | |
| | | sub.Vals(myints); | |
| | | */ | |
| template <class T> | | template <class T> | |
| void Vals(vector<T> &) const; | | void Vals(vector<T> &) const; | |
| /** add all values of the object to the specified list. If type mi
smatches, exception. */ | | /** add all values of the object to the specified list. If type mi
smatches, exception. */ | |
| template <class T> | | template <class T> | |
| void Vals(list<T> &) const; | | void Vals(list<T> &) const; | |
| | | | |
| /** add all values of the object to the specified vector. If type
mismatches, skip. */ | | /** add all values of the object to the specified vector. If type
mismatches, skip. */ | |
| template <class T> | | template <class T> | |
| void vals(vector<T> &) const; | | void vals(vector<T> &) const; | |
| /** add all values of the object to the specified list. If type mi
smatches, skip. */ | | /** add all values of the object to the specified list. If type mi
smatches, skip. */ | |
| template <class T> | | template <class T> | |
| void vals(list<T> &) const; | | void vals(list<T> &) const; | |
| | | | |
| friend class BSONObjIterator; | | friend class BSONObjIterator; | |
| typedef BSONObjIterator iterator; | | typedef BSONObjIterator iterator; | |
|
| BSONObjIterator begin(); | | | |
| | | | |
|
| private: | | /** use something like this: | |
| class Holder { | | for( BSONObj::iterator i = myObj.begin(); i.more(); ) { | |
| public: | | BSONElement e = i.next(); | |
| Holder( const char *objdata ) : | | ... | |
| _objdata( objdata ) { | | | |
| } | | | |
| ~Holder() { | | | |
| free((void *)_objdata); | | | |
| _objdata = 0; | | | |
| } | | } | |
|
| | | */ | |
| | | BSONObjIterator begin() const; | |
| | | | |
| | | void appendSelfToBufBuilder(BufBuilder& b) const { | |
| | | assert( objsize() ); | |
| | | b.appendBuf(reinterpret_cast<const void *>( objdata() ), objsiz
e()); | |
| | | } | |
| | | | |
| | | #pragma pack(1) | |
| | | class Holder : boost::noncopyable { | |
| private: | | private: | |
|
| const char *_objdata; | | Holder(); // this class should never be explicitly created | |
| | | AtomicUInt refCount; | |
| | | public: | |
| | | char data[4]; // start of object | |
| | | | |
| | | void zero() { refCount.zero(); } | |
| | | | |
| | | // these are called automatically by boost::intrusive_ptr | |
| | | friend void intrusive_ptr_add_ref(Holder* h) { h->refCount++; } | |
| | | friend void intrusive_ptr_release(Holder* h) { | |
| | | #if defined(_DEBUG) // cant use dassert or DEV here | |
| | | assert((int)h->refCount > 0); // make sure we haven't alrea
dy freed the buffer | |
| | | #endif | |
| | | if(--(h->refCount) == 0){ | |
| | | #if defined(_DEBUG) | |
| | | unsigned sz = (unsigned&) *h->data; | |
| | | assert(sz < BSONObjMaxInternalSize * 3); | |
| | | memset(h->data, 0xdd, sz); | |
| | | #endif | |
| | | free(h); | |
| | | } | |
| | | } | |
| }; | | }; | |
|
| | | #pragma pack() | |
| | | | |
| | | private: | |
| const char *_objdata; | | const char *_objdata; | |
|
| boost::shared_ptr< Holder > _holder; | | boost::intrusive_ptr< Holder > _holder; | |
| void init(const char *data, bool ifree) { | | | |
| if ( ifree ) | | void _assertInvalid() const; | |
| _holder.reset( new Holder( data ) ); | | | |
| | | void init(Holder *holder) { | |
| | | _holder = holder; // holder is now managed by intrusive_ptr | |
| | | init(holder->data); | |
| | | } | |
| | | void init(const char *data) { | |
| _objdata = data; | | _objdata = data; | |
|
| if ( ! isValid() ){ | | if ( !isValid() ) | |
| StringBuilder ss; | | _assertInvalid(); | |
| int os = objsize(); | | | |
| ss << "Invalid BSONObj spec size: " << os << " (" << toHex(
&os, 4 ) << ")"; | | | |
| try { | | | |
| BSONElement e = firstElement(); | | | |
| ss << " first element:" << e.toString() << " "; | | | |
| } | | | |
| catch ( ... ){} | | | |
| string s = ss.str(); | | | |
| massert( 10334 , s , 0 ); | | | |
| } | | | |
| } | | } | |
| }; | | }; | |
|
| | | | |
| ostream& operator<<( ostream &s, const BSONObj &o ); | | ostream& operator<<( ostream &s, const BSONObj &o ); | |
| ostream& operator<<( ostream &s, const BSONElement &e ); | | ostream& operator<<( ostream &s, const BSONElement &e ); | |
| | | | |
|
| | | StringBuilder& operator<<( StringBuilder &s, const BSONObj &o ); | |
| | | StringBuilder& operator<<( StringBuilder &s, const BSONElement &e ); | |
| | | | |
| struct BSONArray : BSONObj { | | struct BSONArray : BSONObj { | |
| // Don't add anything other than forwarding constructors!!! | | // Don't add anything other than forwarding constructors!!! | |
| BSONArray(): BSONObj() {} | | BSONArray(): BSONObj() {} | |
| explicit BSONArray(const BSONObj& obj): BSONObj(obj) {} | | explicit BSONArray(const BSONObj& obj): BSONObj(obj) {} | |
| }; | | }; | |
| | | | |
| } | | } | |
| | | | |
End of changes. 41 change blocks. |
| 90 lines changed or deleted | | 179 lines changed or added | |
|
| bsonobjbuilder.h | | bsonobjbuilder.h | |
| | | | |
| skipping to change at line 27 | | skipping to change at line 27 | |
| * distributed under the License is distributed on an "AS IS" BASIS, | | * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | |
| * See the License for the specific language governing permissions and | | * See the License for the specific language governing permissions and | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include <limits> | | #include <limits> | |
| #include <cmath> | | #include <cmath> | |
|
| using namespace std; | | #include "bsonelement.h" | |
| | | #include "bsonobj.h" | |
| | | #include "bsonmisc.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| | | using namespace std; | |
| | | | |
| #if defined(_WIN32) | | #if defined(_WIN32) | |
| // warning: 'this' : used in base member initializer list | | // warning: 'this' : used in base member initializer list | |
| #pragma warning( disable : 4355 ) | | #pragma warning( disable : 4355 ) | |
| #endif | | #endif | |
| | | | |
| template<typename T> | | template<typename T> | |
| class BSONFieldValue { | | class BSONFieldValue { | |
| public: | | public: | |
|
| BSONFieldValue( const string& name , const T& t ){ | | BSONFieldValue( const string& name , const T& t ) { | |
| _name = name; | | _name = name; | |
| _t = t; | | _t = t; | |
| } | | } | |
| | | | |
| const T& value() const { return _t; } | | const T& value() const { return _t; } | |
| const string& name() const { return _name; } | | const string& name() const { return _name; } | |
| | | | |
| private: | | private: | |
| string _name; | | string _name; | |
| T _t; | | T _t; | |
| }; | | }; | |
| | | | |
| template<typename T> | | template<typename T> | |
| class BSONField { | | class BSONField { | |
| public: | | public: | |
| BSONField( const string& name , const string& longName="" ) | | BSONField( const string& name , const string& longName="" ) | |
|
| : _name(name), _longName(longName){} | | : _name(name), _longName(longName) {} | |
| const string& name() const { return _name; } | | const string& name() const { return _name; } | |
| operator string() const { return _name; } | | operator string() const { return _name; } | |
| | | | |
| BSONFieldValue<T> make( const T& t ) const { | | BSONFieldValue<T> make( const T& t ) const { | |
| return BSONFieldValue<T>( _name , t ); | | return BSONFieldValue<T>( _name , t ); | |
| } | | } | |
| | | | |
| BSONFieldValue<BSONObj> gt( const T& t ) const { return query( "$gt
" , t ); } | | BSONFieldValue<BSONObj> gt( const T& t ) const { return query( "$gt
" , t ); } | |
| BSONFieldValue<BSONObj> lt( const T& t ) const { return query( "$lt
" , t ); } | | BSONFieldValue<BSONObj> lt( const T& t ) const { return query( "$lt
" , t ); } | |
| | | | |
| | | | |
| skipping to change at line 84 | | skipping to change at line 88 | |
| string _name; | | string _name; | |
| string _longName; | | string _longName; | |
| }; | | }; | |
| | | | |
| /** Utility for creating a BSONObj. | | /** Utility for creating a BSONObj. | |
| See also the BSON() and BSON_ARRAY() macros. | | See also the BSON() and BSON_ARRAY() macros. | |
| */ | | */ | |
| class BSONObjBuilder : boost::noncopyable { | | class BSONObjBuilder : boost::noncopyable { | |
| public: | | public: | |
| /** @param initsize this is just a hint as to the final size of the
object */ | | /** @param initsize this is just a hint as to the final size of the
object */ | |
|
| BSONObjBuilder(int initsize=512) : _b(_buf), _buf(initsize), _offse
t( 0 ), _s( this ) , _tracker(0) , _doneCalled(false) { | | BSONObjBuilder(int initsize=512) : _b(_buf), _buf(initsize + sizeof
(unsigned)), _offset( sizeof(unsigned) ), _s( this ) , _tracker(0) , _doneC
alled(false) { | |
| _b.skip(4); /*leave room for size field*/ | | _b.appendNum((unsigned)0); // ref-count | |
| | | _b.skip(4); /*leave room for size field and ref-count*/ | |
| } | | } | |
| | | | |
|
| /** @param baseBuilder construct a BSONObjBuilder using an existing
BufBuilder */ | | /** @param baseBuilder construct a BSONObjBuilder using an existing
BufBuilder | |
| | | * This is for more efficient adding of subobjects/arrays. See doc
s for subobjStart for example. | |
| | | */ | |
| BSONObjBuilder( BufBuilder &baseBuilder ) : _b( baseBuilder ), _buf
( 0 ), _offset( baseBuilder.len() ), _s( this ) , _tracker(0) , _doneCalled
(false) { | | BSONObjBuilder( BufBuilder &baseBuilder ) : _b( baseBuilder ), _buf
( 0 ), _offset( baseBuilder.len() ), _s( this ) , _tracker(0) , _doneCalled
(false) { | |
| _b.skip( 4 ); | | _b.skip( 4 ); | |
| } | | } | |
| | | | |
|
| BSONObjBuilder( const BSONSizeTracker & tracker ) : _b(_buf) , _buf
(tracker.getSize() ), _offset(0), _s( this ) , _tracker( (BSONSizeTracker*)
(&tracker) ) , _doneCalled(false) { | | BSONObjBuilder( const BSONSizeTracker & tracker ) : _b(_buf) , _buf
(tracker.getSize() + sizeof(unsigned) ), _offset( sizeof(unsigned) ), _s( t
his ) , _tracker( (BSONSizeTracker*)(&tracker) ) , _doneCalled(false) { | |
| _b.skip( 4 ); | | _b.appendNum((unsigned)0); // ref-count | |
| | | _b.skip(4); | |
| } | | } | |
| | | | |
|
| ~BSONObjBuilder(){ | | ~BSONObjBuilder() { | |
| if ( !_doneCalled && _b.buf() && _buf.getSize() == 0 ){ | | if ( !_doneCalled && _b.buf() && _buf.getSize() == 0 ) { | |
| _done(); | | _done(); | |
| } | | } | |
| } | | } | |
| | | | |
| /** add all the fields from the object specified to this object */ | | /** add all the fields from the object specified to this object */ | |
| BSONObjBuilder& appendElements(BSONObj x); | | BSONObjBuilder& appendElements(BSONObj x); | |
| | | | |
|
| | | /** add all the fields from the object specified to this object if
they don't exist already */ | |
| | | BSONObjBuilder& appendElementsUnique( BSONObj x ); | |
| | | | |
| /** append element to the object we are building */ | | /** append element to the object we are building */ | |
| BSONObjBuilder& append( const BSONElement& e) { | | BSONObjBuilder& append( const BSONElement& e) { | |
| assert( !e.eoo() ); // do not append eoo, that would corrupt us
. the builder auto appends when done() is called. | | assert( !e.eoo() ); // do not append eoo, that would corrupt us
. the builder auto appends when done() is called. | |
| _b.appendBuf((void*) e.rawdata(), e.size()); | | _b.appendBuf((void*) e.rawdata(), e.size()); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| /** append an element but with a new name */ | | /** append an element but with a new name */ | |
|
| BSONObjBuilder& appendAs(const BSONElement& e, const StringData& f
ieldName) { | | BSONObjBuilder& appendAs(const BSONElement& e, const StringData& fi
eldName) { | |
| assert( !e.eoo() ); // do not append eoo, that would corrupt us
. the builder auto appends when done() is called. | | assert( !e.eoo() ); // do not append eoo, that would corrupt us
. the builder auto appends when done() is called. | |
| _b.appendNum((char) e.type()); | | _b.appendNum((char) e.type()); | |
| _b.appendStr(fieldName); | | _b.appendStr(fieldName); | |
| _b.appendBuf((void *) e.value(), e.valuesize()); | | _b.appendBuf((void *) e.value(), e.valuesize()); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| /** add a subobject as a member */ | | /** add a subobject as a member */ | |
| BSONObjBuilder& append(const StringData& fieldName, BSONObj subObj)
{ | | BSONObjBuilder& append(const StringData& fieldName, BSONObj subObj)
{ | |
| _b.appendNum((char) Object); | | _b.appendNum((char) Object); | |
| _b.appendStr(fieldName); | | _b.appendStr(fieldName); | |
| _b.appendBuf((void *) subObj.objdata(), subObj.objsize()); | | _b.appendBuf((void *) subObj.objdata(), subObj.objsize()); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| /** add a subobject as a member */ | | /** add a subobject as a member */ | |
|
| BSONObjBuilder& appendObject(const StringData& fieldName, const cha
r * objdata , int size = 0 ){ | | BSONObjBuilder& appendObject(const StringData& fieldName, const cha
r * objdata , int size = 0 ) { | |
| assert( objdata ); | | assert( objdata ); | |
|
| if ( size == 0 ){ | | if ( size == 0 ) { | |
| size = *((int*)objdata); | | size = *((int*)objdata); | |
| } | | } | |
| | | | |
| assert( size > 4 && size < 100000000 ); | | assert( size > 4 && size < 100000000 ); | |
| | | | |
| _b.appendNum((char) Object); | | _b.appendNum((char) Object); | |
| _b.appendStr(fieldName); | | _b.appendStr(fieldName); | |
| _b.appendBuf((void*)objdata, size ); | | _b.appendBuf((void*)objdata, size ); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| /** add header for a new subobject and return bufbuilder for writin
g to | | /** add header for a new subobject and return bufbuilder for writin
g to | |
|
| the subobject's body */ | | * the subobject's body | |
| | | * | |
| | | * example: | |
| | | * | |
| | | * BSONObjBuilder b; | |
| | | * BSONObjBuilder sub (b.subobjStart("fieldName")); | |
| | | * // use sub | |
| | | * sub.done() | |
| | | * // use b and convert to object | |
| | | */ | |
| BufBuilder &subobjStart(const StringData& fieldName) { | | BufBuilder &subobjStart(const StringData& fieldName) { | |
| _b.appendNum((char) Object); | | _b.appendNum((char) Object); | |
| _b.appendStr(fieldName); | | _b.appendStr(fieldName); | |
| return _b; | | return _b; | |
| } | | } | |
| | | | |
| /** add a subobject as a member with type Array. Thus arr object s
hould have "0", "1", ... | | /** add a subobject as a member with type Array. Thus arr object s
hould have "0", "1", ... | |
| style fields in it. | | style fields in it. | |
| */ | | */ | |
| BSONObjBuilder& appendArray(const StringData& fieldName, const BSON
Obj &subObj) { | | BSONObjBuilder& appendArray(const StringData& fieldName, const BSON
Obj &subObj) { | |
| | | | |
| skipping to change at line 212 | | skipping to change at line 232 | |
| | | | |
| /** Append a NumberLong */ | | /** Append a NumberLong */ | |
| BSONObjBuilder& append(const StringData& fieldName, long long n) { | | BSONObjBuilder& append(const StringData& fieldName, long long n) { | |
| _b.appendNum((char) NumberLong); | | _b.appendNum((char) NumberLong); | |
| _b.appendStr(fieldName); | | _b.appendStr(fieldName); | |
| _b.appendNum(n); | | _b.appendNum(n); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| /** appends a number. if n < max(int)/2 then uses int, otherwise l
ong long */ | | /** appends a number. if n < max(int)/2 then uses int, otherwise l
ong long */ | |
|
| BSONObjBuilder& appendIntOrLL( const StringData& fieldName , long l
ong n ){ | | BSONObjBuilder& appendIntOrLL( const StringData& fieldName , long l
ong n ) { | |
| long long x = n; | | long long x = n; | |
| if ( x < 0 ) | | if ( x < 0 ) | |
| x = x * -1; | | x = x * -1; | |
|
| if ( x < ( numeric_limits<int>::max() / 2 ) ) | | if ( x < ( (numeric_limits<int>::max)() / 2 ) ) // extra () to
avoid max macro on windows | |
| append( fieldName , (int)n ); | | append( fieldName , (int)n ); | |
| else | | else | |
| append( fieldName , n ); | | append( fieldName , n ); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| /** | | /** | |
| * appendNumber is a series of method for appending the smallest se
nsible type | | * appendNumber is a series of method for appending the smallest se
nsible type | |
| * mostly for JS | | * mostly for JS | |
| */ | | */ | |
|
| BSONObjBuilder& appendNumber( const StringData& fieldName , int n )
{ | | BSONObjBuilder& appendNumber( const StringData& fieldName , int n )
{ | |
| return append( fieldName , n ); | | return append( fieldName , n ); | |
| } | | } | |
| | | | |
|
| BSONObjBuilder& appendNumber( const StringData& fieldName , double
d ){ | | BSONObjBuilder& appendNumber( const StringData& fieldName , double
d ) { | |
| return append( fieldName , d ); | | return append( fieldName , d ); | |
| } | | } | |
| | | | |
|
| BSONObjBuilder& appendNumber( const StringData& fieldName , size_t
n ){ | | BSONObjBuilder& appendNumber( const StringData& fieldName , size_t
n ) { | |
| static size_t maxInt = (size_t)pow( 2.0 , 30.0 ); | | static size_t maxInt = (size_t)pow( 2.0 , 30.0 ); | |
| | | | |
| if ( n < maxInt ) | | if ( n < maxInt ) | |
| append( fieldName , (int)n ); | | append( fieldName , (int)n ); | |
| else | | else | |
| append( fieldName , (long long)n ); | | append( fieldName , (long long)n ); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
|
| BSONObjBuilder& appendNumber( const StringData& fieldName , long lo
ng l ){ | | BSONObjBuilder& appendNumber( const StringData& fieldName , long lo
ng l ) { | |
| static long long maxInt = (int)pow( 2.0 , 30.0 ); | | static long long maxInt = (int)pow( 2.0 , 30.0 ); | |
| static long long maxDouble = (long long)pow( 2.0 , 40.0 ); | | static long long maxDouble = (long long)pow( 2.0 , 40.0 ); | |
|
| | | long long x = l >= 0 ? l : -l; | |
| if ( l < maxInt ) | | if ( x < maxInt ) | |
| append( fieldName , (int)l ); | | append( fieldName , (int)l ); | |
|
| else if ( l < maxDouble ) | | else if ( x < maxDouble ) | |
| append( fieldName , (double)l ); | | append( fieldName , (double)l ); | |
| else | | else | |
| append( fieldName , l ); | | append( fieldName , l ); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| /** Append a double element */ | | /** Append a double element */ | |
| BSONObjBuilder& append(const StringData& fieldName, double n) { | | BSONObjBuilder& append(const StringData& fieldName, double n) { | |
| _b.appendNum((char) NumberDouble); | | _b.appendNum((char) NumberDouble); | |
| _b.appendStr(fieldName); | | _b.appendStr(fieldName); | |
| | | | |
| skipping to change at line 363 | | skipping to change at line 383 | |
| } | | } | |
| | | | |
| BSONObjBuilder& appendCode(const StringData& fieldName, const Strin
gData& code) { | | BSONObjBuilder& appendCode(const StringData& fieldName, const Strin
gData& code) { | |
| _b.appendNum((char) Code); | | _b.appendNum((char) Code); | |
| _b.appendStr(fieldName); | | _b.appendStr(fieldName); | |
| _b.appendNum((int) code.size()+1); | | _b.appendNum((int) code.size()+1); | |
| _b.appendStr(code); | | _b.appendStr(code); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
|
| /** Append a string element. len DOES include terminating nul */ | | /** Append a string element. | |
| BSONObjBuilder& append(const StringData& fieldName, const char *str
, int len) { | | @param sz size includes terminating null character */ | |
| | | BSONObjBuilder& append(const StringData& fieldName, const char *str
, int sz) { | |
| _b.appendNum((char) String); | | _b.appendNum((char) String); | |
| _b.appendStr(fieldName); | | _b.appendStr(fieldName); | |
|
| _b.appendNum((int)len); | | _b.appendNum((int)sz); | |
| _b.appendBuf(str, len); | | _b.appendBuf(str, sz); | |
| return *this; | | return *this; | |
| } | | } | |
| /** Append a string element */ | | /** Append a string element */ | |
| BSONObjBuilder& append(const StringData& fieldName, const char *str
) { | | BSONObjBuilder& append(const StringData& fieldName, const char *str
) { | |
|
| return append(fieldName, str, (int) strlen(str)+1); | | return append(fieldName, str, (int) strlen(str)+1); | |
| } | | } | |
| /** Append a string element */ | | /** Append a string element */ | |
|
| BSONObjBuilder& append(const StringData& fieldName, string str) { | | BSONObjBuilder& append(const StringData& fieldName, const string& s
tr) { | |
| return append(fieldName, str.c_str(), (int) str.size()+1); | | return append(fieldName, str.c_str(), (int) str.size()+1); | |
| } | | } | |
| | | | |
| BSONObjBuilder& appendSymbol(const StringData& fieldName, const Str
ingData& symbol) { | | BSONObjBuilder& appendSymbol(const StringData& fieldName, const Str
ingData& symbol) { | |
| _b.appendNum((char) Symbol); | | _b.appendNum((char) Symbol); | |
| _b.appendStr(fieldName); | | _b.appendStr(fieldName); | |
| _b.appendNum((int) symbol.size()+1); | | _b.appendNum((int) symbol.size()+1); | |
| _b.appendStr(symbol); | | _b.appendStr(symbol); | |
|
| return *this; } | | return *this; | |
| | | } | |
| | | | |
| /** Append a Null element to the object */ | | /** Append a Null element to the object */ | |
| BSONObjBuilder& appendNull( const StringData& fieldName ) { | | BSONObjBuilder& appendNull( const StringData& fieldName ) { | |
| _b.appendNum( (char) jstNULL ); | | _b.appendNum( (char) jstNULL ); | |
| _b.appendStr( fieldName ); | | _b.appendStr( fieldName ); | |
|
| return *this; } | | return *this; | |
| | | } | |
| | | | |
| // Append an element that is less than all other keys. | | // Append an element that is less than all other keys. | |
| BSONObjBuilder& appendMinKey( const StringData& fieldName ) { | | BSONObjBuilder& appendMinKey( const StringData& fieldName ) { | |
| _b.appendNum( (char) MinKey ); | | _b.appendNum( (char) MinKey ); | |
| _b.appendStr( fieldName ); | | _b.appendStr( fieldName ); | |
| return *this; | | return *this; | |
| } | | } | |
| // Append an element that is greater than all other keys. | | // Append an element that is greater than all other keys. | |
| BSONObjBuilder& appendMaxKey( const StringData& fieldName ) { | | BSONObjBuilder& appendMaxKey( const StringData& fieldName ) { | |
| _b.appendNum( (char) MaxKey ); | | _b.appendNum( (char) MaxKey ); | |
| | | | |
| skipping to change at line 466 | | skipping to change at line 489 | |
| BSONObjBuilder& appendBinData( const StringData& fieldName, int len
, BinDataType type, const unsigned char *data ) { | | BSONObjBuilder& appendBinData( const StringData& fieldName, int len
, BinDataType type, const unsigned char *data ) { | |
| return appendBinData(fieldName, len, type, (const char *) data)
; | | return appendBinData(fieldName, len, type, (const char *) data)
; | |
| } | | } | |
| | | | |
| /** | | /** | |
| Subtype 2 is deprecated. | | Subtype 2 is deprecated. | |
| Append a BSON bindata bytearray element. | | Append a BSON bindata bytearray element. | |
| @param data a byte array | | @param data a byte array | |
| @param len the length of data | | @param len the length of data | |
| */ | | */ | |
|
| BSONObjBuilder& appendBinDataArrayDeprecated( const char * fieldNam
e , const char * data , int len ){ | | BSONObjBuilder& appendBinDataArrayDeprecated( const char * fieldNam
e , const char * data , int len ) { | |
| _b.appendNum( (char) BinData ); | | _b.appendNum( (char) BinData ); | |
| _b.appendStr( fieldName ); | | _b.appendStr( fieldName ); | |
| _b.appendNum( len + 4 ); | | _b.appendNum( len + 4 ); | |
| _b.appendNum( (char)0x2 ); | | _b.appendNum( (char)0x2 ); | |
| _b.appendNum( len ); | | _b.appendNum( len ); | |
| _b.appendBuf( (void *) data, len ); | | _b.appendBuf( (void *) data, len ); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| /** Append to the BSON object a field of type CodeWScope. This is
a javascript code | | /** Append to the BSON object a field of type CodeWScope. This is
a javascript code | |
| | | | |
| skipping to change at line 495 | | skipping to change at line 518 | |
| _b.appendBuf( ( void * )scope.objdata(), scope.objsize() ); | | _b.appendBuf( ( void * )scope.objdata(), scope.objsize() ); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| void appendUndefined( const StringData& fieldName ) { | | void appendUndefined( const StringData& fieldName ) { | |
| _b.appendNum( (char) Undefined ); | | _b.appendNum( (char) Undefined ); | |
| _b.appendStr( fieldName ); | | _b.appendStr( fieldName ); | |
| } | | } | |
| | | | |
| /* helper function -- see Query::where() for primary way to do this
. */ | | /* helper function -- see Query::where() for primary way to do this
. */ | |
|
| void appendWhere( const StringData& code, const BSONObj &scope ){ | | void appendWhere( const StringData& code, const BSONObj &scope ) { | |
| appendCodeWScope( "$where" , code , scope ); | | appendCodeWScope( "$where" , code , scope ); | |
| } | | } | |
| | | | |
| /** | | /** | |
| these are the min/max when comparing, not strict min/max element
s for a given type | | these are the min/max when comparing, not strict min/max element
s for a given type | |
| */ | | */ | |
| void appendMinForType( const StringData& fieldName , int type ); | | void appendMinForType( const StringData& fieldName , int type ); | |
| void appendMaxForType( const StringData& fieldName , int type ); | | void appendMaxForType( const StringData& fieldName , int type ); | |
| | | | |
| /** Append an array of values. */ | | /** Append an array of values. */ | |
| template < class T > | | template < class T > | |
| BSONObjBuilder& append( const StringData& fieldName, const vector<
T >& vals ); | | BSONObjBuilder& append( const StringData& fieldName, const vector<
T >& vals ); | |
| | | | |
| template < class T > | | template < class T > | |
| BSONObjBuilder& append( const StringData& fieldName, const list< T
>& vals ); | | BSONObjBuilder& append( const StringData& fieldName, const list< T
>& vals ); | |
| | | | |
|
| /** The returned BSONObj will free the buffer when it is finished.
*/ | | /** Append a set of values. */ | |
| | | template < class T > | |
| | | BSONObjBuilder& append( const StringData& fieldName, const set< T >
& vals ); | |
| | | | |
| | | /** | |
| | | * destructive | |
| | | * The returned BSONObj will free the buffer when it is finished. | |
| | | * @return owned BSONObj | |
| | | */ | |
| BSONObj obj() { | | BSONObj obj() { | |
| bool own = owned(); | | bool own = owned(); | |
| massert( 10335 , "builder does not own memory", own ); | | massert( 10335 , "builder does not own memory", own ); | |
|
| int l; | | doneFast(); | |
| return BSONObj(decouple(l), true); | | BSONObj::Holder* h = (BSONObj::Holder*)_b.buf(); | |
| | | decouple(); // sets _b.buf() to NULL | |
| | | return BSONObj(h); | |
| } | | } | |
| | | | |
| /** Fetch the object we have built. | | /** Fetch the object we have built. | |
|
| BSONObjBuilder still frees the object when the build
er goes out of | | BSONObjBuilder still frees the object when the builder goes out
of | |
| scope -- very important to keep in mind. Use obj()
if you | | scope -- very important to keep in mind. Use obj() if you | |
| would like the BSONObj to last longer than the build
er. | | would like the BSONObj to last longer than the builder. | |
| */ | | */ | |
| BSONObj done() { | | BSONObj done() { | |
| return BSONObj(_done()); | | return BSONObj(_done()); | |
| } | | } | |
| | | | |
| // Like 'done' above, but does not construct a BSONObj to return to
the caller. | | // Like 'done' above, but does not construct a BSONObj to return to
the caller. | |
| void doneFast() { | | void doneFast() { | |
| (void)_done(); | | (void)_done(); | |
| } | | } | |
| | | | |
| | | | |
| skipping to change at line 560 | | skipping to change at line 593 | |
| _b.decouple(); | | _b.decouple(); | |
| return x; | | return x; | |
| } | | } | |
| void decouple() { | | void decouple() { | |
| _b.decouple(); // post done() call version. be sure jsobj f
rees... | | _b.decouple(); // post done() call version. be sure jsobj f
rees... | |
| } | | } | |
| | | | |
| void appendKeys( const BSONObj& keyPattern , const BSONObj& values
); | | void appendKeys( const BSONObj& keyPattern , const BSONObj& values
); | |
| | | | |
| static string numStr( int i ) { | | static string numStr( int i ) { | |
|
| if (i>=0 && i<100) | | if (i>=0 && i<100 && numStrsReady) | |
| return numStrs[i]; | | return numStrs[i]; | |
| StringBuilder o; | | StringBuilder o; | |
| o << i; | | o << i; | |
| return o.str(); | | return o.str(); | |
| } | | } | |
| | | | |
| /** Stream oriented way to add field names and values. */ | | /** Stream oriented way to add field names and values. */ | |
| BSONObjBuilderValueStream &operator<<(const char * name ) { | | BSONObjBuilderValueStream &operator<<(const char * name ) { | |
| _s.endField( name ); | | _s.endField( name ); | |
| return _s; | | return _s; | |
| | | | |
| skipping to change at line 609 | | skipping to change at line 642 | |
| BSONObjBuilder& operator<<( const BSONFieldValue<T>& v ) { | | BSONObjBuilder& operator<<( const BSONFieldValue<T>& v ) { | |
| append( v.name().c_str() , v.value() ); | | append( v.name().c_str() , v.value() ); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| /** @return true if we are using our own bufbuilder, and not an alt
ernate that was given to us in our constructor */ | | /** @return true if we are using our own bufbuilder, and not an alt
ernate that was given to us in our constructor */ | |
| bool owned() const { return &_b == &_buf; } | | bool owned() const { return &_b == &_buf; } | |
| | | | |
| BSONObjIterator iterator() const ; | | BSONObjIterator iterator() const ; | |
| | | | |
|
| | | bool hasField( const StringData& name ) const ; | |
| | | | |
| | | int len() const { return _b.len(); } | |
| | | | |
| | | BufBuilder& bb() { return _b; } | |
| | | | |
| private: | | private: | |
| char* _done() { | | char* _done() { | |
| if ( _doneCalled ) | | if ( _doneCalled ) | |
| return _b.buf() + _offset; | | return _b.buf() + _offset; | |
| | | | |
| _doneCalled = true; | | _doneCalled = true; | |
| _s.endField(); | | _s.endField(); | |
| _b.appendNum((char) EOO); | | _b.appendNum((char) EOO); | |
| char *data = _b.buf() + _offset; | | char *data = _b.buf() + _offset; | |
| int size = _b.len() - _offset; | | int size = _b.len() - _offset; | |
| | | | |
| skipping to change at line 633 | | skipping to change at line 672 | |
| } | | } | |
| | | | |
| BufBuilder &_b; | | BufBuilder &_b; | |
| BufBuilder _buf; | | BufBuilder _buf; | |
| int _offset; | | int _offset; | |
| BSONObjBuilderValueStream _s; | | BSONObjBuilderValueStream _s; | |
| BSONSizeTracker * _tracker; | | BSONSizeTracker * _tracker; | |
| bool _doneCalled; | | bool _doneCalled; | |
| | | | |
| static const string numStrs[100]; // cache of 0 to 99 inclusive | | static const string numStrs[100]; // cache of 0 to 99 inclusive | |
|
| | | static bool numStrsReady; // for static init safety. see comments i
n db/jsobj.cpp | |
| }; | | }; | |
| | | | |
| class BSONArrayBuilder : boost::noncopyable { | | class BSONArrayBuilder : boost::noncopyable { | |
| public: | | public: | |
| BSONArrayBuilder() : _i(0), _b() {} | | BSONArrayBuilder() : _i(0), _b() {} | |
| BSONArrayBuilder( BufBuilder &_b ) : _i(0), _b(_b) {} | | BSONArrayBuilder( BufBuilder &_b ) : _i(0), _b(_b) {} | |
|
| | | BSONArrayBuilder( int initialSize ) : _i(0), _b(initialSize) {} | |
| | | | |
| template <typename T> | | template <typename T> | |
|
| BSONArrayBuilder& append(const T& x){ | | BSONArrayBuilder& append(const T& x) { | |
| _b.append(num().c_str(), x); | | _b.append(num(), x); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
|
| BSONArrayBuilder& append(const BSONElement& e){ | | BSONArrayBuilder& append(const BSONElement& e) { | |
| _b.appendAs(e, num()); | | _b.appendAs(e, num()); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| template <typename T> | | template <typename T> | |
|
| BSONArrayBuilder& operator<<(const T& x){ | | BSONArrayBuilder& operator<<(const T& x) { | |
| return append(x); | | return append(x); | |
| } | | } | |
| | | | |
| void appendNull() { | | void appendNull() { | |
|
| _b.appendNull(num().c_str()); | | _b.appendNull(num()); | |
| } | | } | |
| | | | |
|
| BSONArray arr(){ return BSONArray(_b.obj()); } | | /** | |
| | | * destructive - ownership moves to returned BSONArray | |
| | | * @return owned BSONArray | |
| | | */ | |
| | | BSONArray arr() { return BSONArray(_b.obj()); } | |
| | | | |
| BSONObj done() { return _b.done(); } | | BSONObj done() { return _b.done(); } | |
| | | | |
| void doneFast() { _b.doneFast(); } | | void doneFast() { _b.doneFast(); } | |
| | | | |
| template <typename T> | | template <typename T> | |
|
| BSONArrayBuilder& append(const StringData& name, const T& x){ | | BSONArrayBuilder& append(const StringData& name, const T& x) { | |
| fill( name ); | | fill( name ); | |
| append( x ); | | append( x ); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
|
| BufBuilder &subobjStart( const StringData& name = "0" ) { | | // These two just use next position | |
| | | BufBuilder &subobjStart() { return _b.subobjStart( num() ); } | |
| | | BufBuilder &subarrayStart() { return _b.subarrayStart( num() ); } | |
| | | | |
| | | // These fill missing entries up to pos. if pos is < next pos is ig
nored | |
| | | BufBuilder &subobjStart(int pos) { | |
| | | fill(pos); | |
| | | return _b.subobjStart( num() ); | |
| | | } | |
| | | BufBuilder &subarrayStart(int pos) { | |
| | | fill(pos); | |
| | | return _b.subarrayStart( num() ); | |
| | | } | |
| | | | |
| | | // These should only be used where you really need interface compat
ability with BSONObjBuilder | |
| | | // Currently they are only used by update.cpp and it should probabl
y stay that way | |
| | | BufBuilder &subobjStart( const StringData& name ) { | |
| fill( name ); | | fill( name ); | |
| return _b.subobjStart( num() ); | | return _b.subobjStart( num() ); | |
| } | | } | |
| | | | |
| BufBuilder &subarrayStart( const char *name ) { | | BufBuilder &subarrayStart( const char *name ) { | |
| fill( name ); | | fill( name ); | |
| return _b.subarrayStart( num() ); | | return _b.subarrayStart( num() ); | |
| } | | } | |
| | | | |
| void appendArray( const StringData& name, BSONObj subObj ) { | | void appendArray( const StringData& name, BSONObj subObj ) { | |
| fill( name ); | | fill( name ); | |
| _b.appendArray( num(), subObj ); | | _b.appendArray( num(), subObj ); | |
| } | | } | |
| | | | |
|
| void appendAs( const BSONElement &e, const char *name ) { | | void appendAs( const BSONElement &e, const char *name) { | |
| fill( name ); | | fill( name ); | |
| append( e ); | | append( e ); | |
| } | | } | |
| | | | |
|
| | | int len() const { return _b.len(); } | |
| | | | |
| private: | | private: | |
| void fill( const StringData& name ) { | | void fill( const StringData& name ) { | |
| char *r; | | char *r; | |
|
| int n = strtol( name.data(), &r, 10 ); | | long int n = strtol( name.data(), &r, 10 ); | |
| if ( *r ) | | if ( *r ) | |
| uasserted( 13048, (string)"can't append to array using stri
ng field name [" + name.data() + "]" ); | | uasserted( 13048, (string)"can't append to array using stri
ng field name [" + name.data() + "]" ); | |
|
| while( _i < n ) | | fill(n); | |
| | | } | |
| | | | |
| | | void fill (int upTo){ | |
| | | while( _i < upTo ) | |
| append( nullElt() ); | | append( nullElt() ); | |
| } | | } | |
| | | | |
| static BSONElement nullElt() { | | static BSONElement nullElt() { | |
| static BSONObj n = nullObj(); | | static BSONObj n = nullObj(); | |
| return n.firstElement(); | | return n.firstElement(); | |
| } | | } | |
| | | | |
| static BSONObj nullObj() { | | static BSONObj nullObj() { | |
| BSONObjBuilder _b; | | BSONObjBuilder _b; | |
| _b.appendNull( "" ); | | _b.appendNull( "" ); | |
| return _b.obj(); | | return _b.obj(); | |
| } | | } | |
| | | | |
|
| string num(){ return _b.numStr(_i++); } | | string num() { return _b.numStr(_i++); } | |
| int _i; | | int _i; | |
| BSONObjBuilder _b; | | BSONObjBuilder _b; | |
| }; | | }; | |
| | | | |
| template < class T > | | template < class T > | |
| inline BSONObjBuilder& BSONObjBuilder::append( const StringData& fieldN
ame, const vector< T >& vals ) { | | inline BSONObjBuilder& BSONObjBuilder::append( const StringData& fieldN
ame, const vector< T >& vals ) { | |
| BSONObjBuilder arrBuilder; | | BSONObjBuilder arrBuilder; | |
| for ( unsigned int i = 0; i < vals.size(); ++i ) | | for ( unsigned int i = 0; i < vals.size(); ++i ) | |
| arrBuilder.append( numStr( i ), vals[ i ] ); | | arrBuilder.append( numStr( i ), vals[ i ] ); | |
| appendArray( fieldName, arrBuilder.done() ); | | appendArray( fieldName, arrBuilder.done() ); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
|
| template < class T > | | template < class L > | |
| inline BSONObjBuilder& BSONObjBuilder::append( const StringData& fieldN
ame, const list< T >& vals ) { | | inline BSONObjBuilder& _appendIt( BSONObjBuilder& _this, const StringDa
ta& fieldName, const L& vals ) { | |
| BSONObjBuilder arrBuilder; | | BSONObjBuilder arrBuilder; | |
| int n = 0; | | int n = 0; | |
|
| for( typename list< T >::const_iterator i = vals.begin(); i != vals
.end(); i++ ) | | for( typename L::const_iterator i = vals.begin(); i != vals.end();
i++ ) | |
| arrBuilder.append( numStr(n++), *i ); | | arrBuilder.append( BSONObjBuilder::numStr(n++), *i ); | |
| appendArray( fieldName, arrBuilder.done() ); | | _this.appendArray( fieldName, arrBuilder.done() ); | |
| return *this; | | return _this; | |
| | | } | |
| | | | |
| | | template < class T > | |
| | | inline BSONObjBuilder& BSONObjBuilder::append( const StringData& fieldN
ame, const list< T >& vals ) { | |
| | | return _appendIt< list< T > >( *this, fieldName, vals ); | |
| | | } | |
| | | | |
| | | template < class T > | |
| | | inline BSONObjBuilder& BSONObjBuilder::append( const StringData& fieldN
ame, const set< T >& vals ) { | |
| | | return _appendIt< set< T > >( *this, fieldName, vals ); | |
| } | | } | |
| | | | |
| // $or helper: OR(BSON("x" << GT << 7), BSON("y" << LT 6)); | | // $or helper: OR(BSON("x" << GT << 7), BSON("y" << LT 6)); | |
| inline BSONObj OR(const BSONObj& a, const BSONObj& b) | | inline BSONObj OR(const BSONObj& a, const BSONObj& b) | |
|
| { return BSON( "$or" << BSON_ARRAY(a << b) ); } | | { return BSON( "$or" << BSON_ARRAY(a << b) ); } | |
| inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c) | | inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c) | |
|
| { return BSON( "$or" << BSON_ARRAY(a << b << c) ); } | | { return BSON( "$or" << BSON_ARRAY(a << b << c) ); } | |
| inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c,
const BSONObj& d) | | inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c,
const BSONObj& d) | |
|
| { return BSON( "$or" << BSON_ARRAY(a << b << c << d) ); } | | { return BSON( "$or" << BSON_ARRAY(a << b << c << d) ); } | |
| inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c,
const BSONObj& d, const BSONObj& e) | | inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c,
const BSONObj& d, const BSONObj& e) | |
|
| { return BSON( "$or" << BSON_ARRAY(a << b << c << d << e) ); } | | { return BSON( "$or" << BSON_ARRAY(a << b << c << d << e) ); } | |
| inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c,
const BSONObj& d, const BSONObj& e, const BSONObj& f) | | inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c,
const BSONObj& d, const BSONObj& e, const BSONObj& f) | |
|
| { return BSON( "$or" << BSON_ARRAY(a << b << c << d << e << f) ); } | | { return BSON( "$or" << BSON_ARRAY(a << b << c << d << e << f) ); } | |
| | | | |
| } | | } | |
| | | | |
End of changes. 55 change blocks. |
| 63 lines changed or deleted | | 140 lines changed or added | |
|
| builder.h | | builder.h | |
| | | | |
| skipping to change at line 23 | | skipping to change at line 23 | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | |
| * See the License for the specific language governing permissions and | | * See the License for the specific language governing permissions and | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include <string> | | #include <string> | |
| #include <string.h> | | #include <string.h> | |
| #include <stdio.h> | | #include <stdio.h> | |
|
| #include <boost/shared_ptr.hpp> | | | |
| | | | |
| #include "../inline_decls.h" | | #include "../inline_decls.h" | |
| #include "../stringdata.h" | | #include "../stringdata.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| | | /* Note the limit here is rather arbitrary and is simply a standard. ge
nerally the code works | |
| | | with any object that fits in ram. | |
| | | | |
| | | Also note that the server has some basic checks to enforce this limi
t but those checks are not exhaustive | |
| | | for example need to check for size too big after | |
| | | update $push (append) operation | |
| | | various db.eval() type operations | |
| | | */ | |
| | | const int BSONObjMaxUserSize = 16 * 1024 * 1024; | |
| | | | |
| | | /* | |
| | | Sometimeswe we need objects slightly larger - an object in the repli
cation local.oplog | |
| | | is slightly larger than a user object for example. | |
| | | */ | |
| | | const int BSONObjMaxInternalSize = BSONObjMaxUserSize + ( 16 * 1024 ); | |
| | | | |
| | | const int BufferMaxSize = 64 * 1024 * 1024; | |
| | | | |
| class StringBuilder; | | class StringBuilder; | |
| | | | |
| void msgasserted(int msgid, const char *msg); | | void msgasserted(int msgid, const char *msg); | |
| | | | |
|
| class BufBuilder { | | class TrivialAllocator { | |
| public: | | public: | |
|
| BufBuilder(int initsize = 512) : size(initsize) { | | void* Malloc(size_t sz) { return malloc(sz); } | |
| | | void* Realloc(void *p, size_t sz) { return realloc(p, sz); } | |
| | | void Free(void *p) { free(p); } | |
| | | }; | |
| | | | |
| | | class StackAllocator { | |
| | | public: | |
| | | enum { SZ = 512 }; | |
| | | void* Malloc(size_t sz) { | |
| | | if( sz <= SZ ) return buf; | |
| | | return malloc(sz); | |
| | | } | |
| | | void* Realloc(void *p, size_t sz) { | |
| | | if( p == buf ) { | |
| | | if( sz <= SZ ) return buf; | |
| | | void *d = malloc(sz); | |
| | | memcpy(d, p, SZ); | |
| | | return d; | |
| | | } | |
| | | return realloc(p, sz); | |
| | | } | |
| | | void Free(void *p) { | |
| | | if( p != buf ) | |
| | | free(p); | |
| | | } | |
| | | private: | |
| | | char buf[SZ]; | |
| | | }; | |
| | | | |
| | | template< class Allocator > | |
| | | class _BufBuilder { | |
| | | // non-copyable, non-assignable | |
| | | _BufBuilder( const _BufBuilder& ); | |
| | | _BufBuilder& operator=( const _BufBuilder& ); | |
| | | Allocator al; | |
| | | public: | |
| | | _BufBuilder(int initsize = 512) : size(initsize) { | |
| if ( size > 0 ) { | | if ( size > 0 ) { | |
|
| data = (char *) malloc(size); | | data = (char *) al.Malloc(size); | |
| if( data == 0 ) | | if( data == 0 ) | |
| msgasserted(10000, "out of memory BufBuilder"); | | msgasserted(10000, "out of memory BufBuilder"); | |
|
| } else { | | } | |
| | | else { | |
| data = 0; | | data = 0; | |
| } | | } | |
| l = 0; | | l = 0; | |
| } | | } | |
|
| ~BufBuilder() { | | ~_BufBuilder() { kill(); } | |
| kill(); | | | |
| } | | | |
| | | | |
| void kill() { | | void kill() { | |
| if ( data ) { | | if ( data ) { | |
|
| free(data); | | al.Free(data); | |
| data = 0; | | data = 0; | |
| } | | } | |
| } | | } | |
| | | | |
|
| void reset( int maxSize = 0 ){ | | void reset() { | |
| l = 0; | | l = 0; | |
|
| if ( maxSize && size > maxSize ){ | | } | |
| free(data); | | void reset( int maxSize ) { | |
| data = (char*)malloc(maxSize); | | l = 0; | |
| | | if ( maxSize && size > maxSize ) { | |
| | | al.Free(data); | |
| | | data = (char*)al.Malloc(maxSize); | |
| size = maxSize; | | size = maxSize; | |
| } | | } | |
| } | | } | |
| | | | |
|
| /* leave room for some stuff later */ | | /** leave room for some stuff later | |
| | | @return point to region that was skipped. pointer may change l
ater (on realloc), so for immediate use only | |
| | | */ | |
| char* skip(int n) { return grow(n); } | | char* skip(int n) { return grow(n); } | |
| | | | |
| /* note this may be deallocated (realloced) if you keep writing. */ | | /* note this may be deallocated (realloced) if you keep writing. */ | |
| char* buf() { return data; } | | char* buf() { return data; } | |
| const char* buf() const { return data; } | | const char* buf() const { return data; } | |
| | | | |
| /* assume ownership of the buffer - you must then free() it */ | | /* assume ownership of the buffer - you must then free() it */ | |
| void decouple() { data = 0; } | | void decouple() { data = 0; } | |
| | | | |
|
| void appendChar(char j){ | | void appendUChar(unsigned char j) { | |
| | | *((unsigned char*)grow(sizeof(unsigned char))) = j; | |
| | | } | |
| | | void appendChar(char j) { | |
| *((char*)grow(sizeof(char))) = j; | | *((char*)grow(sizeof(char))) = j; | |
| } | | } | |
|
| void appendNum(char j){ | | void appendNum(char j) { | |
| *((char*)grow(sizeof(char))) = j; | | *((char*)grow(sizeof(char))) = j; | |
| } | | } | |
| void appendNum(short j) { | | void appendNum(short j) { | |
| *((short*)grow(sizeof(short))) = j; | | *((short*)grow(sizeof(short))) = j; | |
| } | | } | |
| void appendNum(int j) { | | void appendNum(int j) { | |
| *((int*)grow(sizeof(int))) = j; | | *((int*)grow(sizeof(int))) = j; | |
| } | | } | |
| void appendNum(unsigned j) { | | void appendNum(unsigned j) { | |
| *((unsigned*)grow(sizeof(unsigned))) = j; | | *((unsigned*)grow(sizeof(unsigned))) = j; | |
| | | | |
| skipping to change at line 108 | | skipping to change at line 167 | |
| *((long long*)grow(sizeof(long long))) = j; | | *((long long*)grow(sizeof(long long))) = j; | |
| } | | } | |
| void appendNum(unsigned long long j) { | | void appendNum(unsigned long long j) { | |
| *((unsigned long long*)grow(sizeof(unsigned long long))) = j; | | *((unsigned long long*)grow(sizeof(unsigned long long))) = j; | |
| } | | } | |
| | | | |
| void appendBuf(const void *src, size_t len) { | | void appendBuf(const void *src, size_t len) { | |
| memcpy(grow((int) len), src, len); | | memcpy(grow((int) len), src, len); | |
| } | | } | |
| | | | |
|
| void appendStr(const StringData &str , bool includeEOO = true ) { | | template<class T> | |
| const int len = str.size() + ( includeEOO ? 1 : 0 ); | | void appendStruct(const T& s) { | |
| memcpy(grow(len), str.data(), len); | | appendBuf(&s, sizeof(T)); | |
| } | | } | |
| | | | |
|
| int len() const { | | void appendStr(const StringData &str , bool includeEndingNull = tru
e ) { | |
| return l; | | const int len = str.size() + ( includeEndingNull ? 1 : 0 ); | |
| | | memcpy(grow(len), str.data(), len); | |
| } | | } | |
| | | | |
|
| void setlen( int newLen ){ | | /** @return length of current string */ | |
| l = newLen; | | int len() const { return l; } | |
| } | | void setlen( int newLen ) { l = newLen; } | |
| | | /** @return size of the buffer */ | |
| | | int getSize() const { return size; } | |
| | | | |
| /* returns the pre-grow write position */ | | /* returns the pre-grow write position */ | |
| inline char* grow(int by) { | | inline char* grow(int by) { | |
| int oldlen = l; | | int oldlen = l; | |
| l += by; | | l += by; | |
| if ( l > size ) { | | if ( l > size ) { | |
| grow_reallocate(); | | grow_reallocate(); | |
| } | | } | |
| return data + oldlen; | | return data + oldlen; | |
| } | | } | |
| | | | |
|
| int getSize() const { return size; } | | | |
| | | | |
| private: | | private: | |
| /* "slow" portion of 'grow()' */ | | /* "slow" portion of 'grow()' */ | |
|
| void NOINLINE_DECL grow_reallocate(){ | | void NOINLINE_DECL grow_reallocate() { | |
| int a = size * 2; | | int a = size * 2; | |
| if ( a == 0 ) | | if ( a == 0 ) | |
| a = 512; | | a = 512; | |
| if ( l > a ) | | if ( l > a ) | |
| a = l + 16 * 1024; | | a = l + 16 * 1024; | |
|
| if( a > 64 * 1024 * 1024 ) | | if ( a > BufferMaxSize ) | |
| msgasserted(10000, "BufBuilder grow() > 64MB"); | | msgasserted(13548, "BufBuilder grow() > 64MB"); | |
| data = (char *) realloc(data, a); | | data = (char *) al.Realloc(data, a); | |
| size= a; | | size= a; | |
| } | | } | |
| | | | |
| char *data; | | char *data; | |
| int l; | | int l; | |
| int size; | | int size; | |
| | | | |
| friend class StringBuilder; | | friend class StringBuilder; | |
| }; | | }; | |
| | | | |
|
| | | typedef _BufBuilder<TrivialAllocator> BufBuilder; | |
| | | | |
| | | /** The StackBufBuilder builds smaller datasets on the stack instead of
using malloc. | |
| | | this can be significantly faster for small bufs. However, you ca
n not decouple() the | |
| | | buffer with StackBufBuilder. | |
| | | While designed to be a variable on the stack, if you were to dynami
cally allocate one, | |
| | | nothing bad would happen. In fact in some circumstances this mig
ht make sense, say, | |
| | | embedded in some other object. | |
| | | */ | |
| | | class StackBufBuilder : public _BufBuilder<StackAllocator> { | |
| | | public: | |
| | | StackBufBuilder() : _BufBuilder<StackAllocator>(StackAllocator::SZ)
{ } | |
| | | void decouple(); // not allowed. not implemented. | |
| | | }; | |
| | | | |
| #if defined(_WIN32) | | #if defined(_WIN32) | |
|
| | | #pragma warning( push ) | |
| | | // warning C4996: 'sprintf': This function or variable may be unsafe. Consi
der using sprintf_s instead. To disable deprecation, use _CRT_SECURE_NO_WAR
NINGS. | |
| #pragma warning( disable : 4996 ) | | #pragma warning( disable : 4996 ) | |
| #endif | | #endif | |
| | | | |
|
| | | /** stringstream deals with locale so this is a lot faster than std::st
ringstream for UTF8 */ | |
| class StringBuilder { | | class StringBuilder { | |
| public: | | public: | |
| StringBuilder( int initsize=256 ) | | StringBuilder( int initsize=256 ) | |
|
| : _buf( initsize ){ | | : _buf( initsize ) { | |
| } | | } | |
| | | | |
|
| #define SBNUM(val,maxSize,macro) \ | | StringBuilder& operator<<( double x ) { | |
| int prev = _buf.l; \ | | return SBNUM( x , 25 , "%g" ); | |
| int z = sprintf( _buf.grow(maxSize) , macro , (val) ); \ | | | |
| assert( z >= 0 ); \ | | | |
| _buf.l = prev + z; \ | | | |
| return *this; | | | |
| | | | |
| StringBuilder& operator<<( double x ){ | | | |
| SBNUM( x , 25 , "%g" ); | | | |
| } | | } | |
|
| StringBuilder& operator<<( int x ){ | | StringBuilder& operator<<( int x ) { | |
| SBNUM( x , 11 , "%d" ); | | return SBNUM( x , 11 , "%d" ); | |
| } | | } | |
|
| StringBuilder& operator<<( unsigned x ){ | | StringBuilder& operator<<( unsigned x ) { | |
| SBNUM( x , 11 , "%u" ); | | return SBNUM( x , 11 , "%u" ); | |
| } | | } | |
|
| StringBuilder& operator<<( long x ){ | | StringBuilder& operator<<( long x ) { | |
| SBNUM( x , 22 , "%ld" ); | | return SBNUM( x , 22 , "%ld" ); | |
| } | | } | |
|
| StringBuilder& operator<<( unsigned long x ){ | | StringBuilder& operator<<( unsigned long x ) { | |
| SBNUM( x , 22 , "%lu" ); | | return SBNUM( x , 22 , "%lu" ); | |
| } | | } | |
|
| StringBuilder& operator<<( long long x ){ | | StringBuilder& operator<<( long long x ) { | |
| SBNUM( x , 22 , "%lld" ); | | return SBNUM( x , 22 , "%lld" ); | |
| } | | } | |
|
| StringBuilder& operator<<( unsigned long long x ){ | | StringBuilder& operator<<( unsigned long long x ) { | |
| SBNUM( x , 22 , "%llu" ); | | return SBNUM( x , 22 , "%llu" ); | |
| } | | } | |
|
| StringBuilder& operator<<( short x ){ | | StringBuilder& operator<<( short x ) { | |
| SBNUM( x , 8 , "%hd" ); | | return SBNUM( x , 8 , "%hd" ); | |
| } | | } | |
|
| StringBuilder& operator<<( char c ){ | | StringBuilder& operator<<( char c ) { | |
| _buf.grow( 1 )[0] = c; | | _buf.grow( 1 )[0] = c; | |
| return *this; | | return *this; | |
| } | | } | |
|
| #undef SBNUM | | | |
| | | | |
|
| void appendDoubleNice( double x ){ | | void appendDoubleNice( double x ) { | |
| int prev = _buf.l; | | int prev = _buf.l; | |
| char * start = _buf.grow( 32 ); | | char * start = _buf.grow( 32 ); | |
| int z = sprintf( start , "%.16g" , x ); | | int z = sprintf( start , "%.16g" , x ); | |
| assert( z >= 0 ); | | assert( z >= 0 ); | |
| _buf.l = prev + z; | | _buf.l = prev + z; | |
|
| if( strchr(start, '.') == 0 && strchr(start, 'E') == 0 && strch
r(start, 'N') == 0 ){ | | if( strchr(start, '.') == 0 && strchr(start, 'E') == 0 && strch
r(start, 'N') == 0 ) { | |
| write( ".0" , 2 ); | | write( ".0" , 2 ); | |
| } | | } | |
| } | | } | |
| | | | |
|
| void write( const char* buf, int len){ | | void write( const char* buf, int len) { memcpy( _buf.grow( len ) ,
buf , len ); } | |
| memcpy( _buf.grow( len ) , buf , len ); | | | |
| } | | | |
| | | | |
|
| void append( const StringData& str ){ | | void append( const StringData& str ) { memcpy( _buf.grow( str.size(
) ) , str.data() , str.size() ); } | |
| memcpy( _buf.grow( str.size() ) , str.data() , str.size() ); | | | |
| } | | StringBuilder& operator<<( const StringData& str ) { | |
| StringBuilder& operator<<( const StringData& str ){ | | | |
| append( str ); | | append( str ); | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
|
| // access | | void reset( int maxSize = 0 ) { _buf.reset( maxSize ); } | |
| | | | |
|
| void reset( int maxSize = 0 ){ | | std::string str() const { return std::string(_buf.data, _buf.l); } | |
| _buf.reset( maxSize ); | | | |
| } | | | |
| | | | |
|
| std::string str(){ | | int len() const { return _buf.l; } | |
| return std::string(_buf.data, _buf.l); | | | |
| } | | | |
| | | | |
| private: | | private: | |
| BufBuilder _buf; | | BufBuilder _buf; | |
|
| | | | |
| | | // non-copyable, non-assignable | |
| | | StringBuilder( const StringBuilder& ); | |
| | | StringBuilder& operator=( const StringBuilder& ); | |
| | | | |
| | | template <typename T> | |
| | | StringBuilder& SBNUM(T val,int maxSize,const char *macro) { | |
| | | int prev = _buf.l; | |
| | | int z = sprintf( _buf.grow(maxSize) , macro , (val) ); | |
| | | assert( z >= 0 ); | |
| | | _buf.l = prev + z; | |
| | | return *this; | |
| | | } | |
| }; | | }; | |
| | | | |
|
| | | #if defined(_WIN32) | |
| | | #pragma warning( pop ) | |
| | | #endif | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 42 change blocks. |
| 73 lines changed or deleted | | 153 lines changed or added | |
|
| chunk.h | | chunk.h | |
|
| // shard.h | | // @file chunk.h | |
| | | | |
| /* | | | |
| A "shard" is a database (replica pair typically) which represents | | | |
| one partition of the overall database. | | | |
| */ | | | |
| | | | |
| /** | | /** | |
| * Copyright (C) 2008 10gen Inc. | | * Copyright (C) 2008 10gen Inc. | |
| * | | * | |
| * This program is free software: you can redistribute it and/or modify | | * 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
, | | * it under the terms of the GNU Affero General Public License, version 3
, | |
| * as published by the Free Software Foundation. | | * as published by the Free Software Foundation. | |
| * | | * | |
| * This program is distributed in the hope that it will be useful, | | * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Affero General Public License for more details. | | * GNU Affero General Public License for more details. | |
| * | | * | |
| * You should have received a copy of the GNU Affero General Public Licen
se | | * You should have received a copy of the GNU Affero General Public Licen
se | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../pch.h" | | #include "../pch.h" | |
|
| #include "../client/dbclient.h" | | | |
| #include "../client/model.h" | | | |
| #include "../bson/util/atomic_int.h" | | #include "../bson/util/atomic_int.h" | |
|
| | | #include "../client/dbclient.h" | |
| | | #include "../client/distlock.h" | |
| | | | |
| #include "shardkey.h" | | #include "shardkey.h" | |
| #include "shard.h" | | #include "shard.h" | |
|
| #include "config.h" | | | |
| #include "util.h" | | #include "util.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| class DBConfig; | | class DBConfig; | |
| class Chunk; | | class Chunk; | |
| class ChunkRange; | | class ChunkRange; | |
| class ChunkManager; | | class ChunkManager; | |
| class ChunkRangeMangager; | | class ChunkRangeMangager; | |
| class ChunkObjUnitTest; | | class ChunkObjUnitTest; | |
| | | | |
|
| typedef shared_ptr<Chunk> ChunkPtr; | | typedef shared_ptr<const Chunk> ChunkPtr; | |
| | | | |
| // key is max for each Chunk or ChunkRange | | // key is max for each Chunk or ChunkRange | |
| typedef map<BSONObj,ChunkPtr,BSONObjCmp> ChunkMap; | | typedef map<BSONObj,ChunkPtr,BSONObjCmp> ChunkMap; | |
| typedef map<BSONObj,shared_ptr<ChunkRange>,BSONObjCmp> ChunkRangeMap; | | typedef map<BSONObj,shared_ptr<ChunkRange>,BSONObjCmp> ChunkRangeMap; | |
| | | | |
|
| | | typedef shared_ptr<const ChunkManager> ChunkManagerPtr; | |
| | | | |
| /** | | /** | |
| config.chunks | | config.chunks | |
| { ns : "alleyinsider.fs.chunks" , min : {} , max : {} , server : "lo
calhost:30001" } | | { ns : "alleyinsider.fs.chunks" , min : {} , max : {} , server : "lo
calhost:30001" } | |
| | | | |
| x is in a shard iff | | x is in a shard iff | |
| min <= x < max | | min <= x < max | |
| */ | | */ | |
|
| class Chunk : boost::noncopyable, public boost::enable_shared_from_this
<Chunk> { | | class Chunk : boost::noncopyable { | |
| public: | | public: | |
|
| | | Chunk( const ChunkManager * info , BSONObj from); | |
| | | Chunk( const ChunkManager * info , const BSONObj& min, const BSONOb
j& max, const Shard& shard); | |
| | | | |
|
| Chunk( ChunkManager * info ); | | // | |
| Chunk( ChunkManager * info , const BSONObj& min, const BSONObj& max
, const Shard& shard); | | // serialization support | |
| | | // | |
| | | | |
|
| const BSONObj& getMin() const { return _min; } | | void serialize(BSONObjBuilder& to, ShardChunkVersion myLastMod=0); | |
| const BSONObj& getMax() const { return _max; } | | | |
| | | | |
|
| void setMin(const BSONObj& o){ | | // | |
| _min = o; | | // chunk boundary support | |
| } | | // | |
| void setMax(const BSONObj& o){ | | | |
| _max = o; | | | |
| } | | | |
| | | | |
|
| string getns() const; | | const BSONObj& getMin() const { return _min; } | |
| Shard getShard() const { return _shard; } | | const BSONObj& getMax() const { return _max; } | |
| | | | |
|
| void setShard( const Shard& shard ); | | // if min/max key is pos/neg infinity | |
| | | bool minIsInf() const; | |
| | | bool maxIsInf() const; | |
| | | | |
| bool contains( const BSONObj& obj ) const; | | bool contains( const BSONObj& obj ) const; | |
| | | | |
|
| string toString() const; | | string genID() const; | |
| | | static string genID( const string& ns , const BSONObj& min ); | |
| friend ostream& operator << (ostream& out, const Chunk& c){ return
(out << c.toString()); } | | | |
| | | | |
|
| bool operator==(const Chunk& s) const; | | // | |
| | | // chunk version support | |
| | | // | |
| | | | |
|
| bool operator!=(const Chunk& s) const{ | | void appendShortVersion( const char * name , BSONObjBuilder& b ) co
nst; | |
| return ! ( *this == s ); | | | |
| } | | | |
| | | | |
|
| // if min/max key is pos/neg infinity | | ShardChunkVersion getLastmod() const { return _lastmod; } | |
| bool minIsInf() const; | | void setLastmod( ShardChunkVersion v ) { _lastmod = v; } | |
| bool maxIsInf() const; | | | |
| | | | |
|
| BSONObj pickSplitPoint() const; | | // | |
| ChunkPtr split(); | | // split support | |
| | | // | |
| | | | |
|
| void pickSplitVector( vector<BSONObj>* splitPoints , int chunkSize
) const; | | /** | |
| ChunkPtr multiSplit( const vector<BSONObj>& splitPoints ); | | * if the amount of data written nears the max size of a shard | |
| | | * then we check the real size, and if its too big, we split | |
| | | * @return if something was split | |
| | | */ | |
| | | bool splitIfShould( long dataWritten ) const; | |
| | | | |
| /** | | /** | |
|
| * @return size of shard in bytes | | * Splits this chunk at a non-specificed split key to be chosen by
the mongod holding this chunk. | |
| * talks to mongod to do this | | * | |
| | | * @param force if set to true, will split the chunk regardless if
the split is really necessary size wise | |
| | | * if set to false, will only split if the chunk has r
eached the currently desired maximum size | |
| | | * @param res the object containing details about the split executi
on | |
| | | * @return splitPoint if found a key and split successfully, else e
mpty BSONObj | |
| */ | | */ | |
|
| long getPhysicalSize() const; | | BSONObj singleSplit( bool force , BSONObj& res ) const; | |
| | | | |
|
| int countObjects(int maxcount=0) const; | | /** | |
| | | * Splits this chunk at the given key (or keys) | |
| | | * | |
| | | * @param splitPoints the vector of keys that should be used to div
ide this chunk | |
| | | * @param res the object containing details about the split executi
on | |
| | | * @return if the split was successful | |
| | | */ | |
| | | bool multiSplit( const vector<BSONObj>& splitPoints , BSONObj& res
) const; | |
| | | | |
| /** | | /** | |
|
| * if the amount of data written nears the max size of a shard | | * Asks the mongod holding this chunk to find a key that approximat
ely divides this chunk in two | |
| * then we check the real size, and if its too big, we split | | * | |
| | | * @param medianKey the key that divides this chunk, if there is on
e, or empty | |
| */ | | */ | |
|
| bool splitIfShould( long dataWritten ); | | void pickMedianKey( BSONObj& medianKey ) const; | |
| | | | |
|
| /* | | /** | |
| * moves either this shard or newShard if it makes sense too | | * @param splitPoints vector to be filled in | |
| * @return whether or not a shard was moved | | * @param chunkSize chunk size to target in bytes | |
| | | * @param maxPoints limits the number of split points that are need
ed, zero is max (optional) | |
| | | * @param maxObjs limits the number of objects in each chunk, zero
is as max (optional) | |
| */ | | */ | |
|
| bool moveIfShould( ChunkPtr newShard = ChunkPtr() ); | | void pickSplitVector( vector<BSONObj>& splitPoints , int chunkSize
, int maxPoints = 0, int maxObjs = 0) const; | |
| | | | |
|
| bool moveAndCommit( const Shard& to , string& errmsg ); | | // | |
| | | // migration support | |
| | | // | |
| | | | |
|
| const char * getNS(){ return "config.chunks"; } | | /** | |
| void serialize(BSONObjBuilder& to, ShardChunkVersion myLastMod=0); | | * Issues a migrate request for this chunk | |
| void unserialize(const BSONObj& from); | | * | |
| string modelServer() const; | | * @param to shard to move this chunk to | |
| | | * @param chunSize maximum number of bytes beyond which the migrate
should no go trhough | |
| | | * @param res the object containing details about the migrate execu
tion | |
| | | * @return true if move was successful | |
| | | */ | |
| | | bool moveAndCommit( const Shard& to , long long chunkSize , BSONObj
& res ) const; | |
| | | | |
|
| void appendShortVersion( const char * name , BSONObjBuilder& b ); | | /** | |
| | | * @return size of shard in bytes | |
| | | * talks to mongod to do this | |
| | | */ | |
| | | long getPhysicalSize() const; | |
| | | | |
|
| void _markModified(); | | // | |
| | | // public constants | |
| | | // | |
| | | | |
|
| | | static string chunkMetadataNS; | |
| static int MaxChunkSize; | | static int MaxChunkSize; | |
|
| | | static int MaxObjectPerChunk; | |
| | | // | |
| | | // accessors and helpers | |
| | | // | |
| | | | |
|
| string genID() const; | | string toString() const; | |
| static string genID( const string& ns , const BSONObj& min ); | | | |
| | | | |
|
| const ChunkManager* getManager() const { return _manager; } | | friend ostream& operator << (ostream& out, const Chunk& c) { return
(out << c.toString()); } | |
| | | bool operator==(const Chunk& s) const; | |
| | | bool operator!=(const Chunk& s) const { return ! ( *this == s ); } | |
| | | | |
|
| bool modified(); | | string getns() const; | |
| | | const char * getNS() { return "config.chunks"; } | |
| | | Shard getShard() const { return _shard; } | |
| | | const ChunkManager* getManager() const { return _manager; } | |
| | | | |
|
| ShardChunkVersion getVersionOnConfigServer() const; | | | |
| private: | | private: | |
| | | | |
|
| bool _splitIfShould( long dataWritten ); | | | |
| | | | |
| // main shard info | | // main shard info | |
| | | | |
|
| ChunkManager * _manager; | | const ChunkManager * _manager; | |
| ShardKeyPattern skey() const; | | | |
| | | | |
| BSONObj _min; | | BSONObj _min; | |
| BSONObj _max; | | BSONObj _max; | |
| Shard _shard; | | Shard _shard; | |
| ShardChunkVersion _lastmod; | | ShardChunkVersion _lastmod; | |
| | | | |
|
| bool _modified; | | | |
| | | | |
| // transient stuff | | // transient stuff | |
| | | | |
|
| long _dataWritten; | | mutable long _dataWritten; | |
| | | | |
| // methods, etc.. | | // methods, etc.. | |
| | | | |
|
| void _split( BSONObj& middle ); | | /** | |
| | | * if sort 1, return lowest key | |
| | | * if sort -1, return highest key | |
| | | * will return empty object if have none | |
| | | */ | |
| | | BSONObj _getExtremeKey( int sort ) const; | |
| | | | |
|
| friend class ChunkManager; | | /** initializes _dataWritten with a random value so that a mongos r
estart wouldn't cause delay in splitting */ | |
| friend class ShardObjUnitTest; | | static long mkDataWritten(); | |
| | | | |
| | | ShardKeyPattern skey() const; | |
| }; | | }; | |
| | | | |
|
| class ChunkRange{ | | class ChunkRange { | |
| public: | | public: | |
|
| const ChunkManager* getManager() const{ return _manager; } | | const ChunkManager* getManager() const { return _manager; } | |
| Shard getShard() const{ return _shard; } | | Shard getShard() const { return _shard; } | |
| | | | |
| const BSONObj& getMin() const { return _min; } | | const BSONObj& getMin() const { return _min; } | |
| const BSONObj& getMax() const { return _max; } | | const BSONObj& getMax() const { return _max; } | |
| | | | |
| // clones of Chunk methods | | // clones of Chunk methods | |
| bool contains(const BSONObj& obj) const; | | bool contains(const BSONObj& obj) const; | |
| | | | |
| ChunkRange(ChunkMap::const_iterator begin, const ChunkMap::const_it
erator end) | | ChunkRange(ChunkMap::const_iterator begin, const ChunkMap::const_it
erator end) | |
| : _manager(begin->second->getManager()) | | : _manager(begin->second->getManager()) | |
| , _shard(begin->second->getShard()) | | , _shard(begin->second->getShard()) | |
| , _min(begin->second->getMin()) | | , _min(begin->second->getMin()) | |
|
| , _max(prior(end)->second->getMax()) | | , _max(prior(end)->second->getMax()) { | |
| { | | | |
| assert( begin != end ); | | assert( begin != end ); | |
| | | | |
|
| DEV while (begin != end){ | | DEV while (begin != end) { | |
| assert(begin->second->getManager() == _manager); | | assert(begin->second->getManager() == _manager); | |
| assert(begin->second->getShard() == _shard); | | assert(begin->second->getShard() == _shard); | |
| ++begin; | | ++begin; | |
| } | | } | |
| } | | } | |
| | | | |
| // Merge min and max (must be adjacent ranges) | | // Merge min and max (must be adjacent ranges) | |
| ChunkRange(const ChunkRange& min, const ChunkRange& max) | | ChunkRange(const ChunkRange& min, const ChunkRange& max) | |
| : _manager(min.getManager()) | | : _manager(min.getManager()) | |
| , _shard(min.getShard()) | | , _shard(min.getShard()) | |
| , _min(min.getMin()) | | , _min(min.getMin()) | |
|
| , _max(max.getMax()) | | , _max(max.getMax()) { | |
| { | | | |
| assert(min.getShard() == max.getShard()); | | assert(min.getShard() == max.getShard()); | |
| assert(min.getManager() == max.getManager()); | | assert(min.getManager() == max.getManager()); | |
| assert(min.getMax() == max.getMin()); | | assert(min.getMax() == max.getMin()); | |
| } | | } | |
| | | | |
|
| friend ostream& operator<<(ostream& out, const ChunkRange& cr){ | | friend ostream& operator<<(ostream& out, const ChunkRange& cr) { | |
| return (out << "ChunkRange(min=" << cr._min << ", max=" << cr._
max << ", shard=" << cr._shard <<")"); | | return (out << "ChunkRange(min=" << cr._min << ", max=" << cr._
max << ", shard=" << cr._shard <<")"); | |
| } | | } | |
| | | | |
| private: | | private: | |
| const ChunkManager* _manager; | | const ChunkManager* _manager; | |
| const Shard _shard; | | const Shard _shard; | |
| const BSONObj _min; | | const BSONObj _min; | |
| const BSONObj _max; | | const BSONObj _max; | |
| }; | | }; | |
| | | | |
| class ChunkRangeManager { | | class ChunkRangeManager { | |
| public: | | public: | |
| const ChunkRangeMap& ranges() const { return _ranges; } | | const ChunkRangeMap& ranges() const { return _ranges; } | |
| | | | |
| void clear() { _ranges.clear(); } | | void clear() { _ranges.clear(); } | |
| | | | |
| void reloadAll(const ChunkMap& chunks); | | void reloadAll(const ChunkMap& chunks); | |
|
| void reloadRange(const ChunkMap& chunks, const BSONObj& min, const
BSONObj& max); | | | |
| | | | |
| // Slow operation -- wrap with DEV | | // Slow operation -- wrap with DEV | |
| void assertValid() const; | | void assertValid() const; | |
| | | | |
| ChunkRangeMap::const_iterator upper_bound(const BSONObj& o) const {
return _ranges.upper_bound(o); } | | ChunkRangeMap::const_iterator upper_bound(const BSONObj& o) const {
return _ranges.upper_bound(o); } | |
| ChunkRangeMap::const_iterator lower_bound(const BSONObj& o) const {
return _ranges.lower_bound(o); } | | ChunkRangeMap::const_iterator lower_bound(const BSONObj& o) const {
return _ranges.lower_bound(o); } | |
| | | | |
| private: | | private: | |
| // assumes nothing in this range exists in _ranges | | // assumes nothing in this range exists in _ranges | |
| void _insertRange(ChunkMap::const_iterator begin, const ChunkMap::c
onst_iterator end); | | void _insertRange(ChunkMap::const_iterator begin, const ChunkMap::c
onst_iterator end); | |
| | | | |
| skipping to change at line 248 | | skipping to change at line 284 | |
| }; | | }; | |
| | | | |
| /* config.sharding | | /* config.sharding | |
| { ns: 'alleyinsider.fs.chunks' , | | { ns: 'alleyinsider.fs.chunks' , | |
| key: { ts : 1 } , | | key: { ts : 1 } , | |
| shards: [ { min: 1, max: 100, server: a } , { min: 101, max: 200
, server : b } ] | | shards: [ { min: 1, max: 100, server: a } , { min: 101, max: 200
, server : b } ] | |
| } | | } | |
| */ | | */ | |
| class ChunkManager { | | class ChunkManager { | |
| public: | | public: | |
|
| | | typedef map<Shard,ShardChunkVersion> ShardVersionMap; | |
| | | | |
|
| ChunkManager( DBConfig * config , string ns , ShardKeyPattern patte
rn , bool unique ); | | ChunkManager( string ns , ShardKeyPattern pattern , bool unique ); | |
| virtual ~ChunkManager(); | | | |
| | | | |
| string getns() const { return _ns; } | | string getns() const { return _ns; } | |
| | | | |
|
| int numChunks() const { rwlock lk( _lock , false ); return _chunkMa
p.size(); } | | int numChunks() const { return _chunkMap.size(); } | |
| bool hasShardKey( const BSONObj& obj ); | | bool hasShardKey( const BSONObj& obj ) const; | |
| | | | |
|
| ChunkPtr findChunk( const BSONObj& obj , bool retry = false ); | | void createFirstChunk( const Shard& shard ) const; // only call fro
m DBConfig::shardCollection | |
| | | ChunkPtr findChunk( const BSONObj& obj ) const; | |
| ChunkPtr findChunkOnServer( const Shard& shard ) const; | | ChunkPtr findChunkOnServer( const Shard& shard ) const; | |
| | | | |
|
| ShardKeyPattern& getShardKey(){ return _key; } | | | |
| const ShardKeyPattern& getShardKey() const { return _key; } | | const ShardKeyPattern& getShardKey() const { return _key; } | |
|
| bool isUnique(){ return _unique; } | | bool isUnique() const { return _unique; } | |
| | | | |
| void maybeChunkCollection(); | | | |
| | | | |
|
| void getShardsForQuery( set<Shard>& shards , const BSONObj& query )
; | | void maybeChunkCollection() const; | |
| void getAllShards( set<Shard>& all ); | | | |
| void getShardsForRange(set<Shard>& shards, const BSONObj& min, cons
t BSONObj& max); // [min, max) | | | |
| | | | |
|
| void save( bool major ); | | void getShardsForQuery( set<Shard>& shards , const BSONObj& query )
const; | |
| | | void getAllShards( set<Shard>& all ) const; | |
| | | void getShardsForRange(set<Shard>& shards, const BSONObj& min, cons
t BSONObj& max) const; // [min, max) | |
| | | | |
| string toString() const; | | string toString() const; | |
| | | | |
| ShardChunkVersion getVersion( const Shard& shard ) const; | | ShardChunkVersion getVersion( const Shard& shard ) const; | |
| ShardChunkVersion getVersion() const; | | ShardChunkVersion getVersion() const; | |
| | | | |
| /** | | /** | |
|
| * actually does a query on the server | | | |
| * doesn't look at any local data | | | |
| */ | | | |
| ShardChunkVersion getVersionOnConfigServer() const; | | | |
| | | | |
| /** | | | |
| * this is just an increasing number of how many ChunkManagers we h
ave so we know if something has been updated | | * this is just an increasing number of how many ChunkManagers we h
ave so we know if something has been updated | |
| */ | | */ | |
|
| unsigned long long getSequenceNumber(){ | | unsigned long long getSequenceNumber() const { return _sequenceNumb
er; } | |
| return _sequenceNumber; | | | |
| } | | | |
| | | | |
|
| void getInfo( BSONObjBuilder& b ){ | | void getInfo( BSONObjBuilder& b ) const { | |
| b.append( "key" , _key.key() ); | | b.append( "key" , _key.key() ); | |
| b.appendBool( "unique" , _unique ); | | b.appendBool( "unique" , _unique ); | |
| } | | } | |
| | | | |
| /** | | /** | |
| * @param me - so i don't get deleted before i'm done | | * @param me - so i don't get deleted before i'm done | |
| */ | | */ | |
|
| void drop( ChunkManagerPtr me ); | | void drop( ChunkManagerPtr me ) const; | |
| | | | |
| void _printChunks() const; | | void _printChunks() const; | |
| | | | |
|
| private: | | int getCurrentDesiredChunkSize() const; | |
| | | | |
|
| void _reload(); | | private: | |
| void _reload_inlock(); | | ChunkManagerPtr reload(bool force=true) const; // doesn't modify se
lf! | |
| void _load(); | | | |
| | | | |
|
| void save_inlock( bool major ); | | // helpers for constructor | |
| ShardChunkVersion getVersion_inlock() const; | | void _load(ChunkMap& chunks, set<Shard>& shards, ShardVersionMap& s
hardVersions); | |
| void ensureIndex_inlock(); | | static bool _isValid(const ChunkMap& chunks); | |
| | | | |
|
| DBConfig * _config; | | // All members should be const for thread-safety | |
| string _ns; | | const string _ns; | |
| ShardKeyPattern _key; | | const ShardKeyPattern _key; | |
| bool _unique; | | const bool _unique; | |
| | | | |
|
| map<string,unsigned long long> _maxMarkers; | | const ChunkMap _chunkMap; | |
| | | const ChunkRangeManager _chunkRanges; | |
| | | | |
|
| ChunkMap _chunkMap; | | const set<Shard> _shards; | |
| ChunkRangeManager _chunkRanges; | | | |
| | | | |
|
| set<Shard> _shards; | | const ShardVersionMap _shardVersions; // max version per shard | |
| | | | |
|
| unsigned long long _sequenceNumber; | | ShardChunkVersion _version; // max version of any chunk | |
| | | | |
|
| mutable RWLock _lock; | | mutable mutex _mutex; // only used with _nsLock | |
| | | mutable DistributedLock _nsLock; | |
| | | | |
|
| // This should only be called from Chunk after it has been migrated | | const unsigned long long _sequenceNumber; | |
| void _migrationNotification(Chunk* c); | | | |
| | | | |
| friend class Chunk; | | friend class Chunk; | |
| friend class ChunkRangeManager; // only needed for CRM::assertValid
() | | friend class ChunkRangeManager; // only needed for CRM::assertValid
() | |
| static AtomicUInt NextSequenceNumber; | | static AtomicUInt NextSequenceNumber; | |
|
| | | | |
| bool _isValid() const; | | | |
| }; | | }; | |
| | | | |
| // like BSONObjCmp. for use as an STL comparison functor | | // like BSONObjCmp. for use as an STL comparison functor | |
| // key-order in "order" argument must match key-order in shardkey | | // key-order in "order" argument must match key-order in shardkey | |
| class ChunkCmp { | | class ChunkCmp { | |
| public: | | public: | |
| ChunkCmp( const BSONObj &order = BSONObj() ) : _cmp( order ) {} | | ChunkCmp( const BSONObj &order = BSONObj() ) : _cmp( order ) {} | |
| bool operator()( const Chunk &l, const Chunk &r ) const { | | bool operator()( const Chunk &l, const Chunk &r ) const { | |
| return _cmp(l.getMin(), r.getMin()); | | return _cmp(l.getMin(), r.getMin()); | |
| } | | } | |
| | | | |
| skipping to change at line 372 | | skipping to change at line 395 | |
| struct chunk_lock { | | struct chunk_lock { | |
| chunk_lock( const Chunk* c ){ | | chunk_lock( const Chunk* c ){ | |
| | | | |
| } | | } | |
| | | | |
| Chunk _c; | | Chunk _c; | |
| }; | | }; | |
| */ | | */ | |
| inline string Chunk::genID() const { return genID(_manager->getns(), _m
in); } | | inline string Chunk::genID() const { return genID(_manager->getns(), _m
in); } | |
| | | | |
|
| | | bool setShardVersion( DBClientBase & conn , const string& ns , ShardChu
nkVersion version , bool authoritative , BSONObj& result ); | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 73 change blocks. |
| 126 lines changed or deleted | | 151 lines changed or added | |
|
| client.h | | client.h | |
| | | | |
| skipping to change at line 29 | | skipping to change at line 29 | |
| /* Client represents a connection to the database (the server-side) and cor
responds | | /* Client represents a connection to the database (the server-side) and cor
responds | |
| to an open socket (or logical connection if pooling on sockets) from a c
lient. | | to an open socket (or logical connection if pooling on sockets) from a c
lient. | |
| | | | |
| todo: switch to asio...this will fit nicely with that. | | todo: switch to asio...this will fit nicely with that. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../pch.h" | | #include "../pch.h" | |
| #include "security.h" | | #include "security.h" | |
|
| #include "namespace.h" | | #include "namespace-inl.h" | |
| #include "lasterror.h" | | #include "lasterror.h" | |
| #include "stats/top.h" | | #include "stats/top.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| extern class ReplSet *theReplSet; | | extern class ReplSet *theReplSet; | |
| class AuthenticationInfo; | | class AuthenticationInfo; | |
| class Database; | | class Database; | |
| class CurOp; | | class CurOp; | |
| class Command; | | class Command; | |
| class Client; | | class Client; | |
|
| class MessagingPort; | | class AbstractMessagingPort; | |
| | | | |
| extern boost::thread_specific_ptr<Client> currentClient; | | extern boost::thread_specific_ptr<Client> currentClient; | |
| | | | |
|
| | | typedef long long ConnectionId; | |
| | | | |
| | | /** the database's concept of an outside "client" */ | |
| class Client : boost::noncopyable { | | class Client : boost::noncopyable { | |
| public: | | public: | |
|
| | | class Context; | |
| | | | |
| | | static mongo::mutex clientsMutex; | |
| | | static set<Client*> clients; // always be in clientsMutex when mani
pulating this | |
| | | static int recommendedYieldMicros( int * writers = 0 , int * reader
s = 0 ); | |
| | | static int getActiveClientCount( int& writers , int& readers ); | |
| static Client *syncThread; | | static Client *syncThread; | |
|
| | | | |
| | | /* each thread which does db operations has a Client object in TLS. | |
| | | call this when your thread starts. | |
| | | */ | |
| | | static Client& initThread(const char *desc, AbstractMessagingPort *
mp = 0); | |
| | | | |
| | | ~Client(); | |
| | | | |
| | | /* | |
| | | this has to be called as the client goes away, but before thread
termination | |
| | | @return true if anything was done | |
| | | */ | |
| | | bool shutdown(); | |
| | | | |
| | | /** set so isSyncThread() works */ | |
| void iAmSyncThread() { | | void iAmSyncThread() { | |
| wassert( syncThread == 0 ); | | wassert( syncThread == 0 ); | |
| syncThread = this; | | syncThread = this; | |
| } | | } | |
|
| bool isSyncThread() const { return this == syncThread; } // true if
this client is the replication secondary pull thread | | /** @return true if this client is the replication secondary pull t
hread. not used much, is used in create index sync code. */ | |
| | | bool isSyncThread() const { return this == syncThread; } | |
| | | | |
|
| static mongo::mutex clientsMutex; | | string clientAddress(bool includePort=false) const; | |
| static set<Client*> clients; // always be in clientsMutex when mani
pulating this | | const AuthenticationInfo * getAuthenticationInfo() const { return &
_ai; } | |
| static int recommendedYieldMicros( int * writers = 0 , int * reader
s = 0 ); | | AuthenticationInfo * getAuthenticationInfo() { return &_ai; } | |
| | | bool isAdmin() { return _ai.isAuthorized( "admin" ); } | |
| | | CurOp* curop() const { return _curOp; } | |
| | | Context* getContext() const { return _context; } | |
| | | Database* database() const { return _context ? _context->db() : 0;
} | |
| | | const char *ns() const { return _context->ns(); } | |
| | | const char *desc() const { return _desc; } | |
| | | void setLastOp( ReplTime op ) { _lastOp = op; } | |
| | | ReplTime getLastOp() const { return _lastOp; } | |
| | | | |
| | | /* report what the last operation was. used by getlasterror */ | |
| | | void appendLastOp( BSONObjBuilder& b ) const; | |
| | | | |
| | | bool isGod() const { return _god; } /* this is for map/reduce write
s */ | |
| | | string toString() const; | |
| | | void gotHandshake( const BSONObj& o ); | |
| | | BSONObj getRemoteID() const { return _remoteId; } | |
| | | BSONObj getHandshake() const { return _handshake; } | |
| | | AbstractMessagingPort * port() const { return _mp; } | |
| | | ConnectionId getConnectionId() const { return _connectionId; } | |
| | | | |
| | | private: | |
| | | ConnectionId _connectionId; // > 0 for things "conn", 0 otherwise | |
| | | string _threadId; // "" on non support systems | |
| | | CurOp * _curOp; | |
| | | Context * _context; | |
| | | bool _shutdown; | |
| | | const char *_desc; | |
| | | bool _god; | |
| | | AuthenticationInfo _ai; | |
| | | ReplTime _lastOp; | |
| | | BSONObj _handshake; | |
| | | BSONObj _remoteId; | |
| | | AbstractMessagingPort * const _mp; | |
| | | | |
| | | Client(const char *desc, AbstractMessagingPort *p = 0); | |
| | | | |
| | | friend class CurOp; | |
| | | | |
| | | public: | |
| | | | |
| /* set _god=true temporarily, safely */ | | /* set _god=true temporarily, safely */ | |
| class GodScope { | | class GodScope { | |
| bool _prev; | | bool _prev; | |
| public: | | public: | |
| GodScope(); | | GodScope(); | |
| ~GodScope(); | | ~GodScope(); | |
| }; | | }; | |
| | | | |
| /* Set database we want to use, then, restores when we finish (are
out of scope) | | /* Set database we want to use, then, restores when we finish (are
out of scope) | |
| Note this is also helpful if an exception happens as the state i
f fixed up. | | Note this is also helpful if an exception happens as the state i
f fixed up. | |
| */ | | */ | |
|
| class Context : boost::noncopyable{ | | class Context : boost::noncopyable { | |
| Client * _client; | | public: | |
| Context * _oldContext; | | | |
| | | | |
| string _path; | | | |
| mongolock * _lock; | | | |
| bool _justCreated; | | | |
| | | | |
| string _ns; | | | |
| Database * _db; | | | |
| | | | |
| /** | | /** | |
|
| * at this point _client, _oldContext and _ns have to be set | | * this is the main constructor | |
| * _db should not have been touched | | * use this unless there is a good reason not to | |
| * this will set _db and create if needed | | | |
| * will also set _client->_context to this | | | |
| */ | | */ | |
|
| void _finishInit( bool doauth=true); | | Context(const string& ns, string path=dbpath, mongolock * lock
= 0 , bool doauth=true ); | |
| | | | |
| void _auth( int lockState = dbMutex.getState() ); | | | |
| public: | | | |
| Context(const string& ns, string path=dbpath, mongolock * lock
= 0 , bool doauth=true ) | | | |
| : _client( currentClient.get() ) , _oldContext( _client->_c
ontext ) , | | | |
| _path( path ) , _lock( lock ) , | | | |
| _ns( ns ), _db(0){ | | | |
| _finishInit( doauth ); | | | |
| } | | | |
| | | | |
| /* this version saves the context but doesn't yet set the new o
ne: */ | | /* this version saves the context but doesn't yet set the new o
ne: */ | |
|
| | | Context(); | |
| Context() | | | |
| : _client( currentClient.get() ) , _oldContext( _client->_c
ontext ), | | | |
| _path( dbpath ) , _lock(0) , _justCreated(false), _db(0){ | | | |
| _client->_context = this; | | | |
| clear(); | | | |
| } | | | |
| | | | |
| /** | | /** | |
| * if you are doing this after allowing a write there could be
a race condition | | * if you are doing this after allowing a write there could be
a race condition | |
| * if someone closes that db. this checks that the DB is still
valid | | * if someone closes that db. this checks that the DB is still
valid | |
| */ | | */ | |
| Context( string ns , Database * db, bool doauth=true ); | | Context( string ns , Database * db, bool doauth=true ); | |
| | | | |
| ~Context(); | | ~Context(); | |
| | | | |
| Client* getClient() const { return _client; } | | Client* getClient() const { return _client; } | |
| Database* db() const { return _db; } | | Database* db() const { return _db; } | |
| const char * ns() const { return _ns.c_str(); } | | const char * ns() const { return _ns.c_str(); } | |
|
| bool justCreated() const { return _justCreated; } | | | |
| | | | |
| bool equals( const string& ns , const string& path=dbpath ) con
st { | | | |
| return _ns == ns && _path == path; | | | |
| } | | | |
| | | | |
|
| bool inDB( const string& db , const string& path=dbpath ) const
{ | | /** @return if the db was created by this Context */ | |
| if ( _path != path ) | | bool justCreated() const { return _justCreated; } | |
| return false; | | | |
| | | | |
| if ( db == _ns ) | | | |
| return true; | | | |
| | | | |
|
| string::size_type idx = _ns.find( db ); | | bool equals( const string& ns , const string& path=dbpath ) con
st { return _ns == ns && _path == path; } | |
| if ( idx != 0 ) | | | |
| return false; | | | |
| | | | |
|
| return _ns[db.size()] == '.'; | | /** | |
| } | | * @return true iff the current Context is using db/path | |
| | | */ | |
| | | bool inDB( const string& db , const string& path=dbpath ) const
; | |
| | | | |
|
| void clear(){ | | void clear() { _ns = ""; _db = 0; } | |
| _ns = ""; | | | |
| _db = 0; | | | |
| } | | | |
| | | | |
| /** | | /** | |
| * call before unlocking, so clear any non-thread safe state | | * call before unlocking, so clear any non-thread safe state | |
| */ | | */ | |
|
| void unlocked(){ | | void unlocked() { _db = 0; } | |
| _db = 0; | | | |
| } | | | |
| | | | |
| /** | | /** | |
| * call after going back into the lock, will re-establish non-t
hread safe stuff | | * call after going back into the lock, will re-establish non-t
hread safe stuff | |
| */ | | */ | |
|
| void relocked(){ | | void relocked() { _finishInit(); } | |
| _finishInit(); | | | |
| } | | | |
| | | | |
| friend class CurOp; | | friend class CurOp; | |
|
| }; // class Client::Context | | | |
| | | | |
| private: | | | |
| void _dropns( const string& ns ); | | | |
| | | | |
|
| CurOp * _curOp; | | private: | |
| Context * _context; | | /** | |
| bool _shutdown; | | * at this point _client, _oldContext and _ns have to be set | |
| set<string> _tempCollections; | | * _db should not have been touched | |
| const char *_desc; | | * this will set _db and create if needed | |
| bool _god; | | * will also set _client->_context to this | |
| AuthenticationInfo _ai; | | */ | |
| ReplTime _lastOp; | | void _finishInit( bool doauth=true); | |
| BSONObj _handshake; | | | |
| BSONObj _remoteId; | | | |
| | | | |
| public: | | | |
| MessagingPort * const _mp; | | | |
| | | | |
| string clientAddress() const; | | | |
| AuthenticationInfo * getAuthenticationInfo(){ return &_ai; } | | | |
| bool isAdmin() { return _ai.isAuthorized( "admin" ); } | | | |
| CurOp* curop() { return _curOp; } | | | |
| Context* getContext(){ return _context; } | | | |
| Database* database() { return _context ? _context->db() : 0; } | | | |
| const char *ns() const { return _context->ns(); } | | | |
| const char *desc() const { return _desc; } | | | |
| | | | |
| Client(const char *desc, MessagingPort *p = 0); | | | |
| ~Client(); | | | |
| | | | |
| void addTempCollection( const string& ns ); | | | |
| | | | |
| void _invalidateDB(const string& db); | | | |
| static void invalidateDB(const string& db); | | | |
| static void invalidateNS( const string& ns ); | | | |
| | | | |
| void setLastOp( ReplTime op ) { _lastOp = op; } | | | |
| ReplTime getLastOp() const { return _lastOp; } | | | |
| | | | |
|
| /* report what the last operation was. used by getlasterror */ | | void _auth( int lockState = dbMutex.getState() ); | |
| void appendLastOp( BSONObjBuilder& b ) { | | | |
| if( theReplSet ) { | | | |
| b.append("lastOp" , (long long) _lastOp); | | | |
| } | | | |
| else { | | | |
| OpTime lo(_lastOp); | | | |
| if ( ! lo.isNull() ) | | | |
| b.appendTimestamp( "lastOp" , lo.asDate() ); | | | |
| } | | | |
| } | | | |
| | | | |
|
| /* each thread which does db operations has a Client object in TLS. | | Client * _client; | |
| call this when your thread starts. | | Context * _oldContext; | |
| */ | | | |
| static Client& initThread(const char *desc, MessagingPort *mp = 0); | | | |
| | | | |
|
| /* | | string _path; | |
| this has to be called as the client goes away, but before thread
termination | | mongolock * _lock; | |
| @return true if anything was done | | bool _justCreated; | |
| */ | | | |
| bool shutdown(); | | | |
| | | | |
|
| /* this is for map/reduce writes */ | | string _ns; | |
| bool isGod() const { return _god; } | | Database * _db; | |
| | | | |
|
| friend class CurOp; | | }; // class Client::Context | |
| | | | |
|
| string toString() const; | | | |
| void gotHandshake( const BSONObj& o ); | | | |
| BSONObj getRemoteID() const { return _remoteId; } | | | |
| BSONObj getHandshake() const { return _handshake; } | | | |
| }; | | }; | |
| | | | |
| /** get the Client object for this thread. */ | | /** get the Client object for this thread. */ | |
| inline Client& cc() { | | inline Client& cc() { | |
| Client * c = currentClient.get(); | | Client * c = currentClient.get(); | |
| assert( c ); | | assert( c ); | |
| return *c; | | return *c; | |
| } | | } | |
| | | | |
|
| /* each thread which does db operations has a Client object in TLS. | | inline Client::GodScope::GodScope() { | |
| call this when your thread starts. | | | |
| */ | | | |
| inline Client& Client::initThread(const char *desc, MessagingPort *mp)
{ | | | |
| setThreadName(desc); | | | |
| assert( currentClient.get() == 0 ); | | | |
| Client *c = new Client(desc, mp); | | | |
| currentClient.reset(c); | | | |
| mongo::lastError.initThread(); | | | |
| return *c; | | | |
| } | | | |
| | | | |
| inline Client::GodScope::GodScope(){ | | | |
| _prev = cc()._god; | | _prev = cc()._god; | |
| cc()._god = true; | | cc()._god = true; | |
| } | | } | |
| | | | |
|
| inline Client::GodScope::~GodScope(){ | | inline Client::GodScope::~GodScope() { cc()._god = _prev; } | |
| cc()._god = _prev; | | | |
| } | | | |
| | | | |
|
| /* this unlocks, does NOT upgrade. that works for our current usage
*/ | | /* this unlocks, does NOT upgrade. that works for our current usage */ | |
| inline void mongolock::releaseAndWriteLock() { | | inline void mongolock::releaseAndWriteLock() { | |
| if( !_writelock ) { | | if( !_writelock ) { | |
| | | | |
| #if BOOST_VERSION >= 103500 | | #if BOOST_VERSION >= 103500 | |
| int s = dbMutex.getState(); | | int s = dbMutex.getState(); | |
| if( s != -1 ) { | | if( s != -1 ) { | |
| log() << "error: releaseAndWriteLock() s == " << s << endl; | | log() << "error: releaseAndWriteLock() s == " << s << endl; | |
| msgasserted( 12600, "releaseAndWriteLock: unlock_shared fai
led, probably recursive" ); | | msgasserted( 12600, "releaseAndWriteLock: unlock_shared fai
led, probably recursive" ); | |
| } | | } | |
| #endif | | #endif | |
| | | | |
End of changes. 29 change blocks. |
| 146 lines changed or deleted | | 106 lines changed or added | |
|
| clientcursor.h | | clientcursor.h | |
| | | | |
| skipping to change at line 30 | | skipping to change at line 30 | |
| | | | |
| ClientCursor is a wrapper that represents a cursorid from our database | | ClientCursor is a wrapper that represents a cursorid from our database | |
| application's perspective. | | application's perspective. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../pch.h" | | #include "../pch.h" | |
| #include "cursor.h" | | #include "cursor.h" | |
| #include "jsobj.h" | | #include "jsobj.h" | |
|
| #include "../util/message.h" | | #include "../util/net/message.h" | |
| | | #include "../util/net/listen.h" | |
| #include "../util/background.h" | | #include "../util/background.h" | |
| #include "diskloc.h" | | #include "diskloc.h" | |
| #include "dbhelpers.h" | | #include "dbhelpers.h" | |
| #include "matcher.h" | | #include "matcher.h" | |
| #include "../client/dbclient.h" | | #include "../client/dbclient.h" | |
|
| | | #include "projection.h" | |
| | | #include "s/d_chunk_manager.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| typedef long long CursorId; /* passed to the client so it can send back
on getMore */ | | typedef long long CursorId; /* passed to the client so it can send back
on getMore */ | |
| class Cursor; /* internal server cursor base class */ | | class Cursor; /* internal server cursor base class */ | |
| class ClientCursor; | | class ClientCursor; | |
| class ParsedQuery; | | class ParsedQuery; | |
| | | | |
|
| | | struct ByLocKey { | |
| | | | |
| | | ByLocKey( const DiskLoc & l , const CursorId& i ) : loc(l), id(i) {
} | |
| | | | |
| | | static ByLocKey min( const DiskLoc& l ) { return ByLocKey( l , nume
ric_limits<long long>::min() ); } | |
| | | static ByLocKey max( const DiskLoc& l ) { return ByLocKey( l , nume
ric_limits<long long>::max() ); } | |
| | | | |
| | | bool operator<( const ByLocKey &other ) const { | |
| | | int x = loc.compare( other.loc ); | |
| | | if ( x ) | |
| | | return x < 0; | |
| | | return id < other.id; | |
| | | } | |
| | | | |
| | | DiskLoc loc; | |
| | | CursorId id; | |
| | | | |
| | | }; | |
| | | | |
| /* todo: make this map be per connection. this will prevent cursor hij
acking security attacks perhaps. | | /* todo: make this map be per connection. this will prevent cursor hij
acking security attacks perhaps. | |
|
| | | * ERH: 9/2010 this may not work since some drivers send getMore
over a different connection | |
| */ | | */ | |
| typedef map<CursorId, ClientCursor*> CCById; | | typedef map<CursorId, ClientCursor*> CCById; | |
|
| | | typedef map<ByLocKey, ClientCursor*> CCByLoc; | |
| | | | |
| extern BSONObj id_obj; | | extern BSONObj id_obj; | |
| | | | |
| class ClientCursor { | | class ClientCursor { | |
| friend class CmdCursorInfo; | | friend class CmdCursorInfo; | |
|
| DiskLoc _lastLoc; // use getter and setter n
ot this (important) | | | |
| unsigned _idleAgeMillis; // how long has the cursor
been around, relative to server idle time | | | |
| | | | |
| /* 0 = normal | | | |
| 1 = no timeout allowed | | | |
| 100 = in use (pinned) -- see Pointer class | | | |
| */ | | | |
| unsigned _pinValue; | | | |
| | | | |
| bool _doingDeletes; | | | |
| ElapsedTracker _yieldSometimesTracker; | | | |
| | | | |
| static CCById clientCursorsById; | | | |
| static long long numberTimedOut; | | | |
| static boost::recursive_mutex ccmutex; // must use this for all s
tatics above! | | | |
| static CursorId allocCursorId_inlock(); | | | |
| | | | |
| public: | | public: | |
| static void assertNoCursors(); | | static void assertNoCursors(); | |
| | | | |
| /* use this to assure we don't in the background time out cursor wh
ile it is under use. | | /* use this to assure we don't in the background time out cursor wh
ile it is under use. | |
| if you are using noTimeout() already, there is no risk anyway. | | if you are using noTimeout() already, there is no risk anyway. | |
| Further, this mechanism guards against two getMore requests on t
he same cursor executing | | Further, this mechanism guards against two getMore requests on t
he same cursor executing | |
| at the same time - which might be bad. That should never happen
, but if a client driver | | at the same time - which might be bad. That should never happen
, but if a client driver | |
| had a bug, it could (or perhaps some sort of attack situation). | | had a bug, it could (or perhaps some sort of attack situation). | |
| */ | | */ | |
| class Pointer : boost::noncopyable { | | class Pointer : boost::noncopyable { | |
| ClientCursor *_c; | | ClientCursor *_c; | |
| public: | | public: | |
| ClientCursor * c() { return _c; } | | ClientCursor * c() { return _c; } | |
| void release() { | | void release() { | |
| if( _c ) { | | if( _c ) { | |
| assert( _c->_pinValue >= 100 ); | | assert( _c->_pinValue >= 100 ); | |
| _c->_pinValue -= 100; | | _c->_pinValue -= 100; | |
| _c = 0; | | _c = 0; | |
| } | | } | |
| } | | } | |
|
| | | /** | |
| | | * call this if during a yield, the cursor got deleted | |
| | | * if so, we don't want to use the point address | |
| | | */ | |
| | | void deleted() { | |
| | | _c = 0; | |
| | | } | |
| ~Pointer() { release(); } | | ~Pointer() { release(); } | |
| Pointer(long long cursorid) { | | Pointer(long long cursorid) { | |
| recursive_scoped_lock lock(ccmutex); | | recursive_scoped_lock lock(ccmutex); | |
| _c = ClientCursor::find_inlock(cursorid, true); | | _c = ClientCursor::find_inlock(cursorid, true); | |
| if( _c ) { | | if( _c ) { | |
| if( _c->_pinValue >= 100 ) { | | if( _c->_pinValue >= 100 ) { | |
| _c = 0; | | _c = 0; | |
| uasserted(12051, "clientcursor already in use? driv
er problem?"); | | uasserted(12051, "clientcursor already in use? driv
er problem?"); | |
| } | | } | |
| _c->_pinValue += 100; | | _c->_pinValue += 100; | |
| | | | |
| skipping to change at line 118 | | skipping to change at line 132 | |
| CleanupPointer() : _c( 0 ), _id( -1 ) {} | | CleanupPointer() : _c( 0 ), _id( -1 ) {} | |
| void reset( ClientCursor *c = 0 ) { | | void reset( ClientCursor *c = 0 ) { | |
| if ( c == _c ) | | if ( c == _c ) | |
| return; | | return; | |
| if ( _c ) { | | if ( _c ) { | |
| // be careful in case cursor was deleted by someone els
e | | // be careful in case cursor was deleted by someone els
e | |
| ClientCursor::erase( _id ); | | ClientCursor::erase( _id ); | |
| } | | } | |
| if ( c ) { | | if ( c ) { | |
| _c = c; | | _c = c; | |
|
| _id = c->cursorid; | | _id = c->_cursorid; | |
| } else { | | } | |
| | | else { | |
| _c = 0; | | _c = 0; | |
| _id = -1; | | _id = -1; | |
| } | | } | |
| } | | } | |
| ~CleanupPointer() { | | ~CleanupPointer() { | |
| DESTRUCTOR_GUARD ( reset(); ); | | DESTRUCTOR_GUARD ( reset(); ); | |
| } | | } | |
| operator bool() { return _c; } | | operator bool() { return _c; } | |
| ClientCursor * operator-> () { return _c; } | | ClientCursor * operator-> () { return _c; } | |
| private: | | private: | |
| ClientCursor *_c; | | ClientCursor *_c; | |
| CursorId _id; | | CursorId _id; | |
| }; | | }; | |
| | | | |
|
| /*const*/ CursorId cursorid; | | ClientCursor(int queryOptions, const shared_ptr<Cursor>& c, const s
tring& ns, BSONObj query = BSONObj() ); | |
| const string ns; | | | |
| const shared_ptr<Cursor> c; | | | |
| int pos; // # objects into the cursor so far | | | |
| const BSONObj query; // used for logging diags only; opt
ional in constructor | | | |
| const int _queryOptions; // see enum QueryOptions dbclient.h | | | |
| OpTime _slaveReadTill; | | | |
| Database * const _db; | | | |
| | | | |
| ClientCursor(int queryOptions, shared_ptr<Cursor>& _c, const string
& _ns, BSONObj _query = BSONObj()) : | | | |
| _idleAgeMillis(0), _pinValue(0), | | | |
| _doingDeletes(false), _yieldSometimesTracker(128,10), | | | |
| ns(_ns), c(_c), | | | |
| pos(0), query(_query), | | | |
| _queryOptions(queryOptions), | | | |
| _db( cc().database() ) | | | |
| { | | | |
| assert( _db ); | | | |
| assert( str::startsWith(_ns, _db->name) ); | | | |
| if( queryOptions & QueryOption_NoCursorTimeout ) | | | |
| noTimeout(); | | | |
| recursive_scoped_lock lock(ccmutex); | | | |
| cursorid = allocCursorId_inlock(); | | | |
| clientCursorsById.insert( make_pair(cursorid, this) ); | | | |
| } | | | |
| | | | |
| ~ClientCursor(); | | ~ClientCursor(); | |
| | | | |
|
| DiskLoc lastLoc() const { return _lastLoc; } | | // *************** basic accessors ******************* | |
| | | | |
|
| shared_ptr< ParsedQuery > pq; | | CursorId cursorid() const { return _cursorid; } | |
| shared_ptr< FieldMatcher > fields; // which fields query wants retu
rned | | string ns() const { return _ns; } | |
| Message originalMessage; // this is effectively an auto ptr for dat
a the matcher points to | | Database * db() const { return _db; } | |
| | | const BSONObj& query() const { return _query; } | |
| | | int queryOptions() const { return _queryOptions; } | |
| | | | |
|
| /* Get rid of cursors for namespaces that begin with nsprefix. | | DiskLoc lastLoc() const { return _lastLoc; } | |
| | | | |
| | | /* Get rid of cursors for namespaces 'ns'. When dropping a db, ns i
s "dbname." | |
| Used by drop, dropIndexes, dropDatabase. | | Used by drop, dropIndexes, dropDatabase. | |
| */ | | */ | |
|
| static void invalidate(const char *nsPrefix); | | static void invalidate(const char *ns); | |
| | | | |
| /** | | /** | |
| * @param microsToSleep -1 : ask client | | * @param microsToSleep -1 : ask client | |
| * >=0 : sleep for that amount | | * >=0 : sleep for that amount | |
|
| | | * @param recordToLoad after yielding lock, load this record with o
nly mmutex | |
| * do a dbtemprelease | | * do a dbtemprelease | |
| * note: caller should check matcher.docMatcher().atomic() first an
d not yield if atomic - | | * note: caller should check matcher.docMatcher().atomic() first an
d not yield if atomic - | |
| * we don't do herein as this->matcher (above) is only initia
lized for true queries/getmore. | | * we don't do herein as this->matcher (above) is only initia
lized for true queries/getmore. | |
| * (ie not set for remote/update) | | * (ie not set for remote/update) | |
| * @return if the cursor is still valid. | | * @return if the cursor is still valid. | |
| * if false is returned, then this ClientCursor should be c
onsidered deleted - | | * if false is returned, then this ClientCursor should be c
onsidered deleted - | |
| * in fact, the whole database could be gone. | | * in fact, the whole database could be gone. | |
| */ | | */ | |
|
| bool yield( int microsToSleep = -1 ); | | bool yield( int microsToSleep = -1 , Record * recordToLoad = 0 ); | |
| | | | |
| | | enum RecordNeeds { | |
| | | DontNeed = -1 , MaybeCovered = 0 , WillNeed = 100 | |
| | | }; | |
| | | | |
| /** | | /** | |
|
| | | * @param needRecord whether or not the next record has to be read
from disk for sure | |
| | | * if this is true, will yield of next record isn
't in memory | |
| | | * @param yielded true if a yield occurred, and potentially if a yi
eld did not occur | |
| * @return same as yield() | | * @return same as yield() | |
| */ | | */ | |
|
| bool yieldSometimes(); | | bool yieldSometimes( RecordNeeds need, bool *yielded = 0 ); | |
| | | | |
| static int yieldSuggest(); | | static int yieldSuggest(); | |
|
| static void staticYield( int micros ); | | static void staticYield( int micros , const StringData& ns , Record
* rec ); | |
| | | | |
| struct YieldData { CursorId _id; bool _doingDeletes; }; | | struct YieldData { CursorId _id; bool _doingDeletes; }; | |
| bool prepareToYield( YieldData &data ); | | bool prepareToYield( YieldData &data ); | |
| static bool recoverFromYield( const YieldData &data ); | | static bool recoverFromYield( const YieldData &data ); | |
| | | | |
| struct YieldLock : boost::noncopyable { | | struct YieldLock : boost::noncopyable { | |
| explicit YieldLock( ptr<ClientCursor> cc ) | | explicit YieldLock( ptr<ClientCursor> cc ) | |
|
| : _canYield(cc->c->supportYields()) { | | : _canYield(cc->_c->supportYields()) { | |
| if ( _canYield ){ | | if ( _canYield ) { | |
| cc->prepareToYield( _data ); | | cc->prepareToYield( _data ); | |
| _unlock.reset(new dbtempreleasecond()); | | _unlock.reset(new dbtempreleasecond()); | |
| } | | } | |
| } | | } | |
|
| ~YieldLock(){ | | ~YieldLock() { | |
| if ( _unlock ){ | | if ( _unlock ) { | |
| log( LL_WARNING ) << "ClientCursor::YieldLock not close
d properly" << endl; | | log( LL_WARNING ) << "ClientCursor::YieldLock not close
d properly" << endl; | |
| relock(); | | relock(); | |
| } | | } | |
| } | | } | |
|
| bool stillOk(){ | | bool stillOk() { | |
| if ( ! _canYield ) | | if ( ! _canYield ) | |
| return true; | | return true; | |
| relock(); | | relock(); | |
| return ClientCursor::recoverFromYield( _data ); | | return ClientCursor::recoverFromYield( _data ); | |
| } | | } | |
|
| void relock(){ | | void relock() { | |
| _unlock.reset(); | | _unlock.reset(); | |
| } | | } | |
| private: | | private: | |
| const bool _canYield; | | const bool _canYield; | |
| YieldData _data; | | YieldData _data; | |
| scoped_ptr<dbtempreleasecond> _unlock; | | scoped_ptr<dbtempreleasecond> _unlock; | |
| }; | | }; | |
| | | | |
| // --- some pass through helpers for Cursor --- | | // --- some pass through helpers for Cursor --- | |
| | | | |
|
| BSONObj indexKeyPattern() { return c->indexKeyPattern(); } | | Cursor* c() const { return _c.get(); } | |
| bool ok() { return c->ok(); } | | int pos() const { return _pos; } | |
| bool advance(){ return c->advance(); } | | | |
| BSONObj current() { return c->current(); } | | | |
| | | | |
|
| bool currentMatches(){ | | void incPos( int n ) { _pos += n; } // TODO: this is bad | |
| if ( ! c->matcher() ) | | void setPos( int n ) { _pos = n; } // TODO : this is bad too | |
| | | | |
| | | BSONObj indexKeyPattern() { return _c->indexKeyPattern(); } | |
| | | bool modifiedKeys() const { return _c->modifiedKeys(); } | |
| | | bool isMultiKey() const { return _c->isMultiKey(); } | |
| | | | |
| | | bool ok() { return _c->ok(); } | |
| | | bool advance() { return _c->advance(); } | |
| | | BSONObj current() { return _c->current(); } | |
| | | DiskLoc currLoc() { return _c->currLoc(); } | |
| | | BSONObj currKey() const { return _c->currKey(); } | |
| | | | |
| | | /** | |
| | | * same as BSONObj::getFieldsDotted | |
| | | * if it can be retrieved from key, it is | |
| | | * @param holder keeps the currKey in scope by keeping a reference
to it here. generally you'll want | |
| | | * holder and ret to destruct about the same time. | |
| | | * @return if this was retrieved from key | |
| | | */ | |
| | | bool getFieldsDotted( const string& name, BSONElementSet &ret, BSON
Obj& holder ); | |
| | | | |
| | | /** | |
| | | * same as BSONObj::getFieldDotted | |
| | | * if it can be retrieved from key, it is | |
| | | * @return if this was retrieved from key | |
| | | */ | |
| | | BSONElement getFieldDotted( const string& name , BSONObj& holder ,
bool * fromKey = 0 ) ; | |
| | | | |
| | | /** extract items from object which match a pattern object. | |
| | | * e.g., if pattern is { x : 1, y : 1 }, builds an object with | |
| | | * x and y elements of this object, if they are present. | |
| | | * returns elements with original field names | |
| | | * NOTE: copied from BSONObj::extractFields | |
| | | */ | |
| | | BSONObj extractFields(const BSONObj &pattern , bool fillWithNull =
false) ; | |
| | | | |
| | | bool currentIsDup() { return _c->getsetdup( _c->currLoc() ); } | |
| | | | |
| | | bool currentMatches() { | |
| | | if ( ! _c->matcher() ) | |
| return true; | | return true; | |
|
| return c->matcher()->matchesCurrent( c.get() ); | | return _c->matcher()->matchesCurrent( _c.get() ); | |
| } | | } | |
| | | | |
|
| | | void setChunkManager( ShardChunkManagerPtr manager ){ _chunkManager
= manager; } | |
| | | ShardChunkManagerPtr getChunkManager(){ return _chunkManager; } | |
| | | | |
| private: | | private: | |
| void setLastLoc_inlock(DiskLoc); | | void setLastLoc_inlock(DiskLoc); | |
| | | | |
| static ClientCursor* find_inlock(CursorId id, bool warn = true) { | | static ClientCursor* find_inlock(CursorId id, bool warn = true) { | |
| CCById::iterator it = clientCursorsById.find(id); | | CCById::iterator it = clientCursorsById.find(id); | |
| if ( it == clientCursorsById.end() ) { | | if ( it == clientCursorsById.end() ) { | |
| if ( warn ) | | if ( warn ) | |
| OCCASIONALLY out() << "ClientCursor::find(): cursor not
found in map " << id << " (ok after a drop)\n"; | | OCCASIONALLY out() << "ClientCursor::find(): cursor not
found in map " << id << " (ok after a drop)\n"; | |
| return 0; | | return 0; | |
| } | | } | |
| return it->second; | | return it->second; | |
| } | | } | |
| public: | | public: | |
| static ClientCursor* find(CursorId id, bool warn = true) { | | static ClientCursor* find(CursorId id, bool warn = true) { | |
| recursive_scoped_lock lock(ccmutex); | | recursive_scoped_lock lock(ccmutex); | |
| ClientCursor *c = find_inlock(id, warn); | | ClientCursor *c = find_inlock(id, warn); | |
|
| // if this asserts, your code was not thread safe -
you either need to set no timeout | | // if this asserts, your code was not thread safe - you either
need to set no timeout | |
| // for the cursor or keep a ClientCursor::Pointer in
scope for it. | | // for the cursor or keep a ClientCursor::Pointer in scope for
it. | |
| massert( 12521, "internal error: use of an unlocked ClientCurso
r", c == 0 || c->_pinValue ); | | massert( 12521, "internal error: use of an unlocked ClientCurso
r", c == 0 || c->_pinValue ); | |
| return c; | | return c; | |
| } | | } | |
| | | | |
| static bool erase(CursorId id) { | | static bool erase(CursorId id) { | |
| recursive_scoped_lock lock(ccmutex); | | recursive_scoped_lock lock(ccmutex); | |
| ClientCursor *cc = find_inlock(id); | | ClientCursor *cc = find_inlock(id); | |
| if ( cc ) { | | if ( cc ) { | |
| assert( cc->_pinValue < 100 ); // you can't still have an a
ctive ClientCursor::Pointer | | assert( cc->_pinValue < 100 ); // you can't still have an a
ctive ClientCursor::Pointer | |
| delete cc; | | delete cc; | |
| return true; | | return true; | |
| } | | } | |
| return false; | | return false; | |
| } | | } | |
| | | | |
|
| | | /** | |
| | | * @return number of cursors found | |
| | | */ | |
| | | static int erase( int n , long long * ids ); | |
| | | | |
| /* call when cursor's location changes so that we can update the | | /* call when cursor's location changes so that we can update the | |
| cursorsbylocation map. if you are locked and internally iterati
ng, only | | cursorsbylocation map. if you are locked and internally iterati
ng, only | |
| need to call when you are ready to "unlock". | | need to call when you are ready to "unlock". | |
| */ | | */ | |
| void updateLocation(); | | void updateLocation(); | |
| | | | |
| void mayUpgradeStorage() { | | void mayUpgradeStorage() { | |
| /* if ( !ids_.get() ) | | /* if ( !ids_.get() ) | |
| return; | | return; | |
| stringstream ss; | | stringstream ss; | |
| | | | |
| skipping to change at line 295 | | skipping to change at line 343 | |
| } | | } | |
| | | | |
| /** | | /** | |
| * @param millis amount of idle passed time since last call | | * @param millis amount of idle passed time since last call | |
| */ | | */ | |
| bool shouldTimeout( unsigned millis ); | | bool shouldTimeout( unsigned millis ); | |
| | | | |
| void storeOpForSlave( DiskLoc last ); | | void storeOpForSlave( DiskLoc last ); | |
| void updateSlaveLocation( CurOp& curop ); | | void updateSlaveLocation( CurOp& curop ); | |
| | | | |
|
| unsigned idleTime(){ | | unsigned idleTime() const { return _idleAgeMillis; } | |
| return _idleAgeMillis; | | | |
| } | | void setDoingDeletes( bool doingDeletes ) {_doingDeletes = doingDel
etes; } | |
| | | | |
| | | void slaveReadTill( const OpTime& t ) { _slaveReadTill = t; } | |
| | | | |
| | | public: // static methods | |
| | | | |
| static void idleTimeReport(unsigned millis); | | static void idleTimeReport(unsigned millis); | |
|
| private: | | | |
| | | static void appendStats( BSONObjBuilder& result ); | |
| | | static unsigned numCursors() { return clientCursorsById.size(); } | |
| | | static void informAboutToDeleteBucket(const DiskLoc& b); | |
| | | static void aboutToDelete(const DiskLoc& dl); | |
| | | static void find( const string& ns , set<CursorId>& all ); | |
| | | | |
| | | private: // methods | |
| | | | |
| // cursors normally timeout after an inactivy period to prevent exc
ess memory use | | // cursors normally timeout after an inactivy period to prevent exc
ess memory use | |
| // setting this prevents timeout of the cursor in question. | | // setting this prevents timeout of the cursor in question. | |
|
| void noTimeout() { | | void noTimeout() { _pinValue++; } | |
| _pinValue++; | | | |
| } | | | |
| | | | |
|
| multimap<DiskLoc, ClientCursor*>& byLoc() { | | CCByLoc& byLoc() { return _db->ccByLoc; } | |
| return _db->ccByLoc; | | | |
| } | | | |
| public: | | | |
| void setDoingDeletes( bool doingDeletes ){ | | | |
| _doingDeletes = doingDeletes; | | | |
| } | | | |
| | | | |
|
| static void appendStats( BSONObjBuilder& result ); | | Record* _recordForYield( RecordNeeds need ); | |
| | | | |
|
| static unsigned numCursors() { return clientCursorsById.size(); } | | private: | |
| | | | |
|
| static void informAboutToDeleteBucket(const DiskLoc& b); | | CursorId _cursorid; | |
| static void aboutToDelete(const DiskLoc& dl); | | | |
| | | const string _ns; | |
| | | Database * _db; | |
| | | | |
| | | const shared_ptr<Cursor> _c; | |
| | | map<string,int> _indexedFields; // map from indexed field to offse
t in key object | |
| | | int _pos; // # objects into the cursor so fa
r | |
| | | | |
| | | const BSONObj _query; // used for logging diags only; op
tional in constructor | |
| | | int _queryOptions; // see enum QueryOptions dbclient.h | |
| | | | |
| | | OpTime _slaveReadTill; | |
| | | | |
| | | DiskLoc _lastLoc; // use getter and setter n
ot this (important) | |
| | | unsigned _idleAgeMillis; // how long has the cursor
been around, relative to server idle time | |
| | | | |
| | | /* 0 = normal | |
| | | 1 = no timeout allowed | |
| | | 100 = in use (pinned) -- see Pointer class | |
| | | */ | |
| | | unsigned _pinValue; | |
| | | | |
| | | bool _doingDeletes; | |
| | | ElapsedTracker _yieldSometimesTracker; | |
| | | | |
| | | ShardChunkManagerPtr _chunkManager; | |
| | | | |
| | | public: | |
| | | shared_ptr<ParsedQuery> pq; | |
| | | shared_ptr<Projection> fields; // which fields query wants returned | |
| | | Message originalMessage; // this is effectively an auto ptr for dat
a the matcher points to | |
| | | | |
| | | private: // static members | |
| | | | |
| | | static CCById clientCursorsById; | |
| | | static long long numberTimedOut; | |
| | | static boost::recursive_mutex& ccmutex; // must use this for all
statics above! | |
| | | static CursorId allocCursorId_inlock(); | |
| | | | |
|
| static void find( const string& ns , set<CursorId>& all ); | | | |
| }; | | }; | |
| | | | |
| class ClientCursorMonitor : public BackgroundJob { | | class ClientCursorMonitor : public BackgroundJob { | |
| public: | | public: | |
|
| | | string name() const { return "ClientCursorMonitor"; } | |
| void run(); | | void run(); | |
|
| string name() { return "ClientCursorMonitor"; } | | | |
| }; | | }; | |
| | | | |
| extern ClientCursorMonitor clientCursorMonitor; | | extern ClientCursorMonitor clientCursorMonitor; | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
|
| | | | |
| | | // ClientCursor should only be used with auto_ptr because it needs to be | |
| | | // release()ed after a yield if stillOk() returns false and these pointer t
ypes | |
| | | // do not support releasing. This will prevent them from being used acciden
tally | |
| | | namespace boost{ | |
| | | template<> class scoped_ptr<mongo::ClientCursor> {}; | |
| | | template<> class shared_ptr<mongo::ClientCursor> {}; | |
| | | } | |
| | | | |
End of changes. 39 change blocks. |
| 89 lines changed or deleted | | 177 lines changed or added | |
|
| core.h | | core.h | |
| | | | |
| skipping to change at line 34 | | skipping to change at line 34 | |
| #include <cmath> | | #include <cmath> | |
| | | | |
| #ifndef M_PI | | #ifndef M_PI | |
| # define M_PI 3.14159265358979323846 | | # define M_PI 3.14159265358979323846 | |
| #endif | | #endif | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| class GeoBitSets { | | class GeoBitSets { | |
| public: | | public: | |
|
| GeoBitSets(){ | | GeoBitSets() { | |
| for ( int i=0; i<32; i++ ){ | | for ( int i=0; i<32; i++ ) { | |
| masks32[i] = ( 1 << ( 31 - i ) ); | | masks32[i] = ( 1 << ( 31 - i ) ); | |
| } | | } | |
|
| for ( int i=0; i<64; i++ ){ | | for ( int i=0; i<64; i++ ) { | |
| masks64[i] = ( 1LL << ( 63 - i ) ); | | masks64[i] = ( 1LL << ( 63 - i ) ); | |
| } | | } | |
| | | | |
|
| for ( unsigned i=0; i<16; i++ ){ | | for ( unsigned i=0; i<16; i++ ) { | |
| unsigned fixed = 0; | | unsigned fixed = 0; | |
|
| for ( int j=0; j<4; j++ ){ | | for ( int j=0; j<4; j++ ) { | |
| if ( i & ( 1 << j ) ) | | if ( i & ( 1 << j ) ) | |
| fixed |= ( 1 << ( j * 2 ) ); | | fixed |= ( 1 << ( j * 2 ) ); | |
| } | | } | |
| hashedToNormal[fixed] = i; | | hashedToNormal[fixed] = i; | |
| } | | } | |
| | | | |
| } | | } | |
| int masks32[32]; | | int masks32[32]; | |
| long long masks64[64]; | | long long masks64[64]; | |
| | | | |
| | | | |
| skipping to change at line 62 | | skipping to change at line 62 | |
| int masks32[32]; | | int masks32[32]; | |
| long long masks64[64]; | | long long masks64[64]; | |
| | | | |
| unsigned hashedToNormal[256]; | | unsigned hashedToNormal[256]; | |
| }; | | }; | |
| | | | |
| extern GeoBitSets geoBitSets; | | extern GeoBitSets geoBitSets; | |
| | | | |
| class GeoHash { | | class GeoHash { | |
| public: | | public: | |
|
| | | | |
| GeoHash() | | GeoHash() | |
|
| : _hash(0),_bits(0){ | | : _hash(0),_bits(0) { | |
| } | | } | |
| | | | |
|
| explicit GeoHash( const char * hash ){ | | explicit GeoHash( const char * hash ) { | |
| init( hash ); | | init( hash ); | |
| } | | } | |
| | | | |
|
| explicit GeoHash( const string& hash ){ | | explicit GeoHash( const string& hash ) { | |
| init( hash ); | | init( hash ); | |
| } | | } | |
| | | | |
|
| explicit GeoHash( const BSONElement& e , unsigned bits=32 ){ | | static GeoHash makeFromBinData(const char *bindata, unsigned bits)
{ | |
| | | GeoHash h; | |
| | | h._bits = bits; | |
| | | h._copy( (char*)&h._hash , bindata ); | |
| | | h._fix(); | |
| | | return h; | |
| | | } | |
| | | | |
| | | explicit GeoHash( const BSONElement& e , unsigned bits=32 ) { | |
| _bits = bits; | | _bits = bits; | |
|
| if ( e.type() == BinData ){ | | if ( e.type() == BinData ) { | |
| int len = 0; | | int len = 0; | |
| _copy( (char*)&_hash , e.binData( len ) ); | | _copy( (char*)&_hash , e.binData( len ) ); | |
| assert( len == 8 ); | | assert( len == 8 ); | |
| _bits = bits; | | _bits = bits; | |
| } | | } | |
| else { | | else { | |
|
| cout << "GeoHash cons e : " << e << endl; | | cout << "GeoHash bad element: " << e << endl; | |
| uassert(13047,"wrong type for geo index. if you're using a
pre-release version, need to rebuild index",0); | | uassert(13047,"wrong type for geo index. if you're using a
pre-release version, need to rebuild index",0); | |
| } | | } | |
| _fix(); | | _fix(); | |
| } | | } | |
| | | | |
|
| GeoHash( unsigned x , unsigned y , unsigned bits=32){ | | GeoHash( unsigned x , unsigned y , unsigned bits=32) { | |
| init( x , y , bits ); | | init( x , y , bits ); | |
| } | | } | |
| | | | |
|
| GeoHash( const GeoHash& old ){ | | GeoHash( const GeoHash& old ) { | |
| _hash = old._hash; | | _hash = old._hash; | |
| _bits = old._bits; | | _bits = old._bits; | |
| } | | } | |
| | | | |
| GeoHash( long long hash , unsigned bits ) | | GeoHash( long long hash , unsigned bits ) | |
|
| : _hash( hash ) , _bits( bits ){ | | : _hash( hash ) , _bits( bits ) { | |
| _fix(); | | _fix(); | |
| } | | } | |
| | | | |
|
| void init( unsigned x , unsigned y , unsigned bits ){ | | void init( unsigned x , unsigned y , unsigned bits ) { | |
| assert( bits <= 32 ); | | assert( bits <= 32 ); | |
| _hash = 0; | | _hash = 0; | |
| _bits = bits; | | _bits = bits; | |
|
| for ( unsigned i=0; i<bits; i++ ){ | | for ( unsigned i=0; i<bits; i++ ) { | |
| if ( isBitSet( x , i ) ) _hash |= geoBitSets.masks64[i*2]; | | if ( isBitSet( x , i ) ) _hash |= geoBitSets.masks64[i*2]; | |
| if ( isBitSet( y , i ) ) _hash |= geoBitSets.masks64[(i*2)+
1]; | | if ( isBitSet( y , i ) ) _hash |= geoBitSets.masks64[(i*2)+
1]; | |
| } | | } | |
| } | | } | |
| | | | |
| void unhash_fast( unsigned& x , unsigned& y ) const { | | void unhash_fast( unsigned& x , unsigned& y ) const { | |
| x = 0; | | x = 0; | |
| y = 0; | | y = 0; | |
| char * c = (char*)(&_hash); | | char * c = (char*)(&_hash); | |
|
| for ( int i=0; i<8; i++ ){ | | for ( int i=0; i<8; i++ ) { | |
| unsigned t = (unsigned)(c[i]) & 0x55; | | unsigned t = (unsigned)(c[i]) & 0x55; | |
| y |= ( geoBitSets.hashedToNormal[t] << (4*(i)) ); | | y |= ( geoBitSets.hashedToNormal[t] << (4*(i)) ); | |
| | | | |
| t = ( (unsigned)(c[i]) >> 1 ) & 0x55; | | t = ( (unsigned)(c[i]) >> 1 ) & 0x55; | |
| x |= ( geoBitSets.hashedToNormal[t] << (4*(i)) ); | | x |= ( geoBitSets.hashedToNormal[t] << (4*(i)) ); | |
| } | | } | |
| } | | } | |
| | | | |
| void unhash_slow( unsigned& x , unsigned& y ) const { | | void unhash_slow( unsigned& x , unsigned& y ) const { | |
| x = 0; | | x = 0; | |
| y = 0; | | y = 0; | |
|
| for ( unsigned i=0; i<_bits; i++ ){ | | for ( unsigned i=0; i<_bits; i++ ) { | |
| if ( getBitX(i) ) | | if ( getBitX(i) ) | |
| x |= geoBitSets.masks32[i]; | | x |= geoBitSets.masks32[i]; | |
| if ( getBitY(i) ) | | if ( getBitY(i) ) | |
| y |= geoBitSets.masks32[i]; | | y |= geoBitSets.masks32[i]; | |
| } | | } | |
| } | | } | |
| | | | |
| void unhash( unsigned& x , unsigned& y ) const { | | void unhash( unsigned& x , unsigned& y ) const { | |
| unhash_fast( x , y ); | | unhash_fast( x , y ); | |
| } | | } | |
| | | | |
| /** | | /** | |
| * @param 0 = high | | * @param 0 = high | |
| */ | | */ | |
|
| static bool isBitSet( unsigned val , unsigned bit ){ | | static bool isBitSet( unsigned val , unsigned bit ) { | |
| return geoBitSets.masks32[bit] & val; | | return geoBitSets.masks32[bit] & val; | |
| } | | } | |
| | | | |
| GeoHash up() const { | | GeoHash up() const { | |
| return GeoHash( _hash , _bits - 1 ); | | return GeoHash( _hash , _bits - 1 ); | |
| } | | } | |
| | | | |
| bool hasPrefix( const GeoHash& other ) const { | | bool hasPrefix( const GeoHash& other ) const { | |
| assert( other._bits <= _bits ); | | assert( other._bits <= _bits ); | |
| if ( other._bits == 0 ) | | if ( other._bits == 0 ) | |
| | | | |
| skipping to change at line 174 | | skipping to change at line 183 | |
| buf.append( _hash & geoBitSets.masks64[x] ? "1" : "0" ); | | buf.append( _hash & geoBitSets.masks64[x] ? "1" : "0" ); | |
| return buf.str(); | | return buf.str(); | |
| } | | } | |
| | | | |
| string toStringHex1() const { | | string toStringHex1() const { | |
| stringstream ss; | | stringstream ss; | |
| ss << hex << _hash; | | ss << hex << _hash; | |
| return ss.str(); | | return ss.str(); | |
| } | | } | |
| | | | |
|
| void init( const string& s ){ | | void init( const string& s ) { | |
| _hash = 0; | | _hash = 0; | |
| _bits = s.size() / 2; | | _bits = s.size() / 2; | |
| for ( unsigned pos=0; pos<s.size(); pos++ ) | | for ( unsigned pos=0; pos<s.size(); pos++ ) | |
| if ( s[pos] == '1' ) | | if ( s[pos] == '1' ) | |
| setBit( pos , 1 ); | | setBit( pos , 1 ); | |
| } | | } | |
| | | | |
|
| void setBit( unsigned pos , bool one ){ | | void setBit( unsigned pos , bool one ) { | |
| assert( pos < _bits * 2 ); | | assert( pos < _bits * 2 ); | |
| if ( one ) | | if ( one ) | |
| _hash |= geoBitSets.masks64[pos]; | | _hash |= geoBitSets.masks64[pos]; | |
| else if ( _hash & geoBitSets.masks64[pos] ) | | else if ( _hash & geoBitSets.masks64[pos] ) | |
| _hash &= ~geoBitSets.masks64[pos]; | | _hash &= ~geoBitSets.masks64[pos]; | |
| } | | } | |
| | | | |
| bool getBit( unsigned pos ) const { | | bool getBit( unsigned pos ) const { | |
| return _hash & geoBitSets.masks64[pos]; | | return _hash & geoBitSets.masks64[pos]; | |
| } | | } | |
| | | | |
| skipping to change at line 216 | | skipping to change at line 225 | |
| append( b , "" ); | | append( b , "" ); | |
| BSONObj o = b.obj(); | | BSONObj o = b.obj(); | |
| assert( o.objsize() == 20 ); | | assert( o.objsize() == 20 ); | |
| return o; | | return o; | |
| } | | } | |
| | | | |
| bool constrains() const { | | bool constrains() const { | |
| return _bits > 0; | | return _bits > 0; | |
| } | | } | |
| | | | |
|
| void move( int x , int y ){ | | bool canRefine() const { | |
| | | return _bits < 32; | |
| | | } | |
| | | | |
| | | void move( int x , int y ) { | |
| assert( _bits ); | | assert( _bits ); | |
| _move( 0 , x ); | | _move( 0 , x ); | |
| _move( 1 , y ); | | _move( 1 , y ); | |
| } | | } | |
| | | | |
|
| void _move( unsigned offset , int d ){ | | void _move( unsigned offset , int d ) { | |
| if ( d == 0 ) | | if ( d == 0 ) | |
| return; | | return; | |
| assert( d <= 1 && d>= -1 ); // TEMP | | assert( d <= 1 && d>= -1 ); // TEMP | |
| | | | |
| bool from, to; | | bool from, to; | |
|
| if ( d > 0 ){ | | if ( d > 0 ) { | |
| from = 0; | | from = 0; | |
| to = 1; | | to = 1; | |
| } | | } | |
| else { | | else { | |
| from = 1; | | from = 1; | |
| to = 0; | | to = 0; | |
| } | | } | |
| | | | |
| unsigned pos = ( _bits * 2 ) - 1; | | unsigned pos = ( _bits * 2 ) - 1; | |
| if ( offset == 0 ) | | if ( offset == 0 ) | |
| pos--; | | pos--; | |
|
| while ( true ){ | | while ( true ) { | |
| if ( getBit(pos) == from ){ | | if ( getBit(pos) == from ) { | |
| setBit( pos , to ); | | setBit( pos , to ); | |
| return; | | return; | |
| } | | } | |
| | | | |
|
| if ( pos < 2 ){ | | if ( pos < 2 ) { | |
| // overflow | | // overflow | |
|
| for ( ; pos < ( _bits * 2 ) ; pos += 2 ){ | | for ( ; pos < ( _bits * 2 ) ; pos += 2 ) { | |
| setBit( pos , from ); | | setBit( pos , from ); | |
| } | | } | |
| return; | | return; | |
| } | | } | |
| | | | |
| setBit( pos , from ); | | setBit( pos , from ); | |
| pos -= 2; | | pos -= 2; | |
| } | | } | |
| | | | |
| assert(0); | | assert(0); | |
| } | | } | |
| | | | |
| GeoHash& operator=(const GeoHash& h) { | | GeoHash& operator=(const GeoHash& h) { | |
| _hash = h._hash; | | _hash = h._hash; | |
| _bits = h._bits; | | _bits = h._bits; | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
|
| bool operator==(const GeoHash& h ){ | | bool operator==(const GeoHash& h ) const { | |
| return _hash == h._hash && _bits == h._bits; | | return _hash == h._hash && _bits == h._bits; | |
| } | | } | |
| | | | |
|
| | | bool operator!=(const GeoHash& h ) const { | |
| | | return !( *this == h ); | |
| | | } | |
| | | | |
| | | bool operator<(const GeoHash& h ) const { | |
| | | if( _hash != h._hash ) return _hash < h._hash; | |
| | | return _bits < h._bits; | |
| | | } | |
| | | | |
| GeoHash& operator+=( const char * s ) { | | GeoHash& operator+=( const char * s ) { | |
| unsigned pos = _bits * 2; | | unsigned pos = _bits * 2; | |
| _bits += strlen(s) / 2; | | _bits += strlen(s) / 2; | |
| assert( _bits <= 32 ); | | assert( _bits <= 32 ); | |
|
| while ( s[0] ){ | | while ( s[0] ) { | |
| if ( s[0] == '1' ) | | if ( s[0] == '1' ) | |
| setBit( pos , 1 ); | | setBit( pos , 1 ); | |
| pos++; | | pos++; | |
| s++; | | s++; | |
| } | | } | |
| | | | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| GeoHash operator+( const char * s ) const { | | GeoHash operator+( const char * s ) const { | |
| GeoHash n = *this; | | GeoHash n = *this; | |
| n+=s; | | n+=s; | |
| return n; | | return n; | |
| } | | } | |
| | | | |
|
| void _fix(){ | | GeoHash operator+( string s ) const { | |
| | | return operator+( s.c_str() ); | |
| | | } | |
| | | | |
| | | void _fix() { | |
| static long long FULL = 0xFFFFFFFFFFFFFFFFLL; | | static long long FULL = 0xFFFFFFFFFFFFFFFFLL; | |
| long long mask = FULL << ( 64 - ( _bits * 2 ) ); | | long long mask = FULL << ( 64 - ( _bits * 2 ) ); | |
| _hash &= mask; | | _hash &= mask; | |
| } | | } | |
| | | | |
| void append( BSONObjBuilder& b , const char * name ) const { | | void append( BSONObjBuilder& b , const char * name ) const { | |
| char buf[8]; | | char buf[8]; | |
| _copy( buf , (char*)&_hash ); | | _copy( buf , (char*)&_hash ); | |
| b.appendBinData( name , 8 , bdtCustom , buf ); | | b.appendBinData( name , 8 , bdtCustom , buf ); | |
| } | | } | |
| | | | |
| skipping to change at line 313 | | skipping to change at line 339 | |
| long long getHash() const { | | long long getHash() const { | |
| return _hash; | | return _hash; | |
| } | | } | |
| | | | |
| unsigned getBits() const { | | unsigned getBits() const { | |
| return _bits; | | return _bits; | |
| } | | } | |
| | | | |
| GeoHash commonPrefix( const GeoHash& other ) const { | | GeoHash commonPrefix( const GeoHash& other ) const { | |
| unsigned i=0; | | unsigned i=0; | |
|
| for ( ; i<_bits && i<other._bits; i++ ){ | | for ( ; i<_bits && i<other._bits; i++ ) { | |
| if ( getBitX( i ) == other.getBitX( i ) && | | if ( getBitX( i ) == other.getBitX( i ) && | |
|
| getBitY( i ) == other.getBitY( i ) ) | | getBitY( i ) == other.getBitY( i ) ) | |
| continue; | | continue; | |
| break; | | break; | |
| } | | } | |
| return GeoHash(_hash,i); | | return GeoHash(_hash,i); | |
| } | | } | |
| | | | |
| private: | | private: | |
| | | | |
|
| void _copy( char * dst , const char * src ) const { | | static void _copy( char * dst , const char * src ) { | |
| for ( unsigned a=0; a<8; a++ ){ | | for ( unsigned a=0; a<8; a++ ) { | |
| dst[a] = src[7-a]; | | dst[a] = src[7-a]; | |
| } | | } | |
| } | | } | |
| | | | |
| long long _hash; | | long long _hash; | |
| unsigned _bits; // bits per field, so 1 to 32 | | unsigned _bits; // bits per field, so 1 to 32 | |
| }; | | }; | |
| | | | |
|
| inline ostream& operator<<( ostream &s, const GeoHash &h ){ | | inline ostream& operator<<( ostream &s, const GeoHash &h ) { | |
| s << h.toString(); | | s << h.toString(); | |
| return s; | | return s; | |
| } | | } | |
| | | | |
| class GeoConvert { | | class GeoConvert { | |
| public: | | public: | |
|
| virtual ~GeoConvert(){} | | virtual ~GeoConvert() {} | |
| | | | |
| virtual void unhash( const GeoHash& h , double& x , double& y ) con
st = 0; | | virtual void unhash( const GeoHash& h , double& x , double& y ) con
st = 0; | |
| virtual GeoHash hash( double x , double y ) const = 0; | | virtual GeoHash hash( double x , double y ) const = 0; | |
| }; | | }; | |
| | | | |
| class Point { | | class Point { | |
| public: | | public: | |
| | | | |
|
| Point( const GeoConvert * g , const GeoHash& hash ){ | | Point( const GeoConvert * g , const GeoHash& hash ) { | |
| g->unhash( hash , _x , _y ); | | g->unhash( hash , _x , _y ); | |
| } | | } | |
| | | | |
|
| explicit Point( const BSONElement& e ){ | | explicit Point( const BSONElement& e ) { | |
| BSONObjIterator i(e.Obj()); | | BSONObjIterator i(e.Obj()); | |
| _x = i.next().number(); | | _x = i.next().number(); | |
| _y = i.next().number(); | | _y = i.next().number(); | |
| } | | } | |
| | | | |
|
| explicit Point( const BSONObj& o ){ | | explicit Point( const BSONObj& o ) { | |
| BSONObjIterator i(o); | | BSONObjIterator i(o); | |
| _x = i.next().number(); | | _x = i.next().number(); | |
| _y = i.next().number(); | | _y = i.next().number(); | |
| } | | } | |
| | | | |
| Point( double x , double y ) | | Point( double x , double y ) | |
|
| : _x( x ) , _y( y ){ | | : _x( x ) , _y( y ) { | |
| } | | } | |
| | | | |
|
| Point() : _x(0),_y(0){ | | Point() : _x(0),_y(0) { | |
| } | | } | |
| | | | |
|
| GeoHash hash( const GeoConvert * g ){ | | GeoHash hash( const GeoConvert * g ) { | |
| return g->hash( _x , _y ); | | return g->hash( _x , _y ); | |
| } | | } | |
| | | | |
| double distance( const Point& p ) const { | | double distance( const Point& p ) const { | |
| double a = _x - p._x; | | double a = _x - p._x; | |
| double b = _y - p._y; | | double b = _y - p._y; | |
|
| | | | |
| | | // Avoid numerical error if possible... | |
| | | if( a == 0 ) return abs( _y - p._y ); | |
| | | if( b == 0 ) return abs( _x - p._x ); | |
| | | | |
| return sqrt( ( a * a ) + ( b * b ) ); | | return sqrt( ( a * a ) + ( b * b ) ); | |
| } | | } | |
| | | | |
|
| | | /** | |
| | | * Distance method that compares x or y coords when other direction
is zero, | |
| | | * avoids numerical error when distances are very close to radius b
ut axis-aligned. | |
| | | * | |
| | | * An example of the problem is: | |
| | | * (52.0 - 51.9999) - 0.0001 = 3.31965e-15 and 52.0 - 51.9999 > 0.0
001 in double arithmetic | |
| | | * but: | |
| | | * 51.9999 + 0.0001 <= 52.0 | |
| | | * | |
| | | * This avoids some (but not all!) suprising results in $center que
ries where points are | |
| | | * ( radius + center.x, center.y ) or vice-versa. | |
| | | */ | |
| | | bool distanceWithin( const Point& p, double radius ) const { | |
| | | double a = _x - p._x; | |
| | | double b = _y - p._y; | |
| | | | |
| | | if( a == 0 ) { | |
| | | // | |
| | | // Note: For some, unknown reason, when a 32-bit g++ optim
izes this call, the sum is | |
| | | // calculated imprecisely. We need to force the compiler t
o always evaluate it correctly, | |
| | | // hence the weirdness. | |
| | | // | |
| | | // On some 32-bit linux machines, removing the volatile key
word or calculating the sum inline | |
| | | // will make certain geo tests fail. Of course this check
will force volatile for all 32-bit systems, | |
| | | // not just affected systems. | |
| | | if( sizeof(void*) <= 4 ){ | |
| | | volatile double sum = _y > p._y ? p._y + radius : _y +
radius; | |
| | | return _y > p._y ? sum >= _y : sum >= p._y; | |
| | | } | |
| | | else { | |
| | | // Original math, correct for most systems | |
| | | return _y > p._y ? p._y + radius >= _y : _y + radius >=
p._y; | |
| | | } | |
| | | } | |
| | | if( b == 0 ) { | |
| | | if( sizeof(void*) <= 4 ){ | |
| | | volatile double sum = _x > p._x ? p._x + radius : _x +
radius; | |
| | | return _x > p._x ? sum >= _x : sum >= p._x; | |
| | | } | |
| | | else { | |
| | | return _x > p._x ? p._x + radius >= _x : _x + radius >=
p._x; | |
| | | } | |
| | | } | |
| | | | |
| | | return sqrt( ( a * a ) + ( b * b ) ) <= radius; | |
| | | } | |
| | | | |
| string toString() const { | | string toString() const { | |
| StringBuilder buf(32); | | StringBuilder buf(32); | |
| buf << "(" << _x << "," << _y << ")"; | | buf << "(" << _x << "," << _y << ")"; | |
| return buf.str(); | | return buf.str(); | |
| | | | |
| } | | } | |
| | | | |
| double _x; | | double _x; | |
| double _y; | | double _y; | |
| }; | | }; | |
| | | | |
| extern const double EARTH_RADIUS_KM; | | extern const double EARTH_RADIUS_KM; | |
| extern const double EARTH_RADIUS_MILES; | | extern const double EARTH_RADIUS_MILES; | |
| | | | |
|
| | | // Technically lat/long bounds, not really tied to earth radius. | |
| | | inline void checkEarthBounds( Point p ) { | |
| | | uassert( 14808, str::stream() << "point " << p.toString() << " must
be in earth-like bounds of long : [-180, 180), lat : [-90, 90] ", | |
| | | p._x >= -180 && p._x < 180 && p._y >= -90 && p._y <= 90 ); | |
| | | } | |
| | | | |
| inline double deg2rad(double deg) { return deg * (M_PI/180); } | | inline double deg2rad(double deg) { return deg * (M_PI/180); } | |
| inline double rad2deg(double rad) { return rad * (180/M_PI); } | | inline double rad2deg(double rad) { return rad * (180/M_PI); } | |
| | | | |
| // WARNING: _x and _y MUST be longitude and latitude in that order | | // WARNING: _x and _y MUST be longitude and latitude in that order | |
| // note: multiply by earth radius for distance | | // note: multiply by earth radius for distance | |
| inline double spheredist_rad( const Point& p1, const Point& p2 ) { | | inline double spheredist_rad( const Point& p1, const Point& p2 ) { | |
| // this uses the n-vector formula: http://en.wikipedia.org/wiki/N-v
ector | | // this uses the n-vector formula: http://en.wikipedia.org/wiki/N-v
ector | |
| // If you try to match the code to the formula, note that I inline
the cross-product. | | // If you try to match the code to the formula, note that I inline
the cross-product. | |
| // TODO: optimize with SSE | | // TODO: optimize with SSE | |
| | | | |
| double sin_x1(sin(p1._x)), cos_x1(cos(p1._x)); | | double sin_x1(sin(p1._x)), cos_x1(cos(p1._x)); | |
| double sin_y1(sin(p1._y)), cos_y1(cos(p1._y)); | | double sin_y1(sin(p1._y)), cos_y1(cos(p1._y)); | |
| double sin_x2(sin(p2._x)), cos_x2(cos(p2._x)); | | double sin_x2(sin(p2._x)), cos_x2(cos(p2._x)); | |
| double sin_y2(sin(p2._y)), cos_y2(cos(p2._y)); | | double sin_y2(sin(p2._y)), cos_y2(cos(p2._y)); | |
| | | | |
| double cross_prod = | | double cross_prod = | |
| (cos_y1*cos_x1 * cos_y2*cos_x2) + | | (cos_y1*cos_x1 * cos_y2*cos_x2) + | |
| (cos_y1*sin_x1 * cos_y2*sin_x2) + | | (cos_y1*sin_x1 * cos_y2*sin_x2) + | |
| (sin_y1 * sin_y2); | | (sin_y1 * sin_y2); | |
| | | | |
|
| if (cross_prod >= 1 || cross_prod <= -1){ | | if (cross_prod >= 1 || cross_prod <= -1) { | |
| // fun with floats | | // fun with floats | |
| assert( fabs(cross_prod)-1 < 1e-6 ); | | assert( fabs(cross_prod)-1 < 1e-6 ); | |
| return cross_prod > 0 ? 0 : M_PI; | | return cross_prod > 0 ? 0 : M_PI; | |
| } | | } | |
| | | | |
| return acos(cross_prod); | | return acos(cross_prod); | |
| } | | } | |
| | | | |
| // note: return is still in radians as that can be multiplied by radius
to get arc length | | // note: return is still in radians as that can be multiplied by radius
to get arc length | |
| inline double spheredist_deg( const Point& p1, const Point& p2 ) { | | inline double spheredist_deg( const Point& p1, const Point& p2 ) { | |
| return spheredist_rad( | | return spheredist_rad( | |
|
| Point( deg2rad(p1._x), deg2rad(p1._y) ), | | Point( deg2rad(p1._x), deg2rad(p1._y) ), | |
| Point( deg2rad(p2._x), deg2rad(p2._y) ) | | Point( deg2rad(p2._x), deg2rad(p2._y) ) | |
| ); | | ); | |
| } | | } | |
| | | | |
| } | | } | |
| | | | |
End of changes. 47 change blocks. |
| 46 lines changed or deleted | | 130 lines changed or added | |
|
| curop.h | | curop.h | |
|
| // curop.h | | // @file curop.h | |
| | | | |
| /* | | /* | |
| * Copyright (C) 2010 10gen Inc. | | * Copyright (C) 2010 10gen Inc. | |
| * | | * | |
| * This program is free software: you can redistribute it and/or modify | | * 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, | | * it under the terms of the GNU Affero General Public License, version
3, | |
| * as published by the Free Software Foundation. | | * as published by the Free Software Foundation. | |
| * | | * | |
| * This program is distributed in the hope that it will be useful, | | * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Affero General Public License for more details. | | * GNU Affero General Public License for more details. | |
| * | | * | |
| * You should have received a copy of the GNU Affero General Public Lice
nse | | * You should have received a copy of the GNU Affero General Public Lice
nse | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
|
| #include "namespace.h" | | #include "namespace-inl.h" | |
| #include "client.h" | | #include "client.h" | |
| #include "../bson/util/atomic_int.h" | | #include "../bson/util/atomic_int.h" | |
| #include "../util/concurrency/spin_lock.h" | | #include "../util/concurrency/spin_lock.h" | |
|
| #include "db.h" | | #include "../util/time_support.h" | |
| | | #include "../util/net/hostandport.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| | | class CurOp; | |
| | | | |
| /* lifespan is different than CurOp because of recursives with DBDirect
Client */ | | /* lifespan is different than CurOp because of recursives with DBDirect
Client */ | |
| class OpDebug { | | class OpDebug { | |
| public: | | public: | |
|
| StringBuilder str; | | OpDebug() : ns(""){ reset(); } | |
| | | | |
|
| void reset(){ | | void reset(); | |
| str.reset(); | | | |
| } | | string toString() const; | |
| | | void append( const CurOp& curop, BSONObjBuilder& b ) const; | |
| | | | |
| | | // ------------------- | |
| | | | |
| | | StringBuilder extra; // weird things we need to fix later | |
| | | | |
| | | // basic options | |
| | | int op; | |
| | | bool iscommand; | |
| | | Namespace ns; | |
| | | BSONObj query; | |
| | | BSONObj updateobj; | |
| | | | |
| | | // detailed options | |
| | | long long cursorid; | |
| | | int ntoreturn; | |
| | | int ntoskip; | |
| | | bool exhaust; | |
| | | | |
| | | // debugging/profile info | |
| | | int nscanned; | |
| | | bool idhack; | |
| | | bool scanAndOrder; | |
| | | bool moved; | |
| | | bool fastmod; | |
| | | bool fastmodinsert; | |
| | | bool upsert; | |
| | | unsigned keyUpdates; | |
| | | | |
| | | // error handling | |
| | | ExceptionInfo exceptionInfo; | |
| | | | |
| | | // response info | |
| | | int executionTime; | |
| | | int nreturned; | |
| | | int responseLength; | |
| }; | | }; | |
| | | | |
|
| | | /** | |
| | | * stores a copy of a bson obj in a fixed size buffer | |
| | | * if its too big for the buffer, says "too big" | |
| | | * useful for keeping a copy around indefinitely without wasting a lot
of space or doing malloc | |
| | | */ | |
| class CachedBSONObj { | | class CachedBSONObj { | |
| public: | | public: | |
|
| | | enum { TOO_BIG_SENTINEL = 1 } ; | |
| static BSONObj _tooBig; // { $msg : "query not recording (too large
)" } | | static BSONObj _tooBig; // { $msg : "query not recording (too large
)" } | |
| | | | |
|
| CachedBSONObj(){ | | CachedBSONObj() { | |
| _size = (int*)_buf; | | _size = (int*)_buf; | |
| reset(); | | reset(); | |
| } | | } | |
| | | | |
|
| void reset( int sz = 0 ){ | | void reset( int sz = 0 ) { | |
| _size[0] = sz; | | _lock.lock(); | |
| | | _reset( sz ); | |
| | | _lock.unlock(); | |
| } | | } | |
| | | | |
|
| void set( const BSONObj& o ){ | | void set( const BSONObj& o ) { | |
| _lock.lock(); | | _lock.lock(); | |
| try { | | try { | |
| int sz = o.objsize(); | | int sz = o.objsize(); | |
| | | | |
| if ( sz > (int) sizeof(_buf) ) { | | if ( sz > (int) sizeof(_buf) ) { | |
|
| reset(1); // flag as too big and return | | _reset(TOO_BIG_SENTINEL); | |
| } | | } | |
| else { | | else { | |
| memcpy(_buf, o.objdata(), sz ); | | memcpy(_buf, o.objdata(), sz ); | |
| } | | } | |
| | | | |
| _lock.unlock(); | | _lock.unlock(); | |
| } | | } | |
|
| catch ( ... ){ | | catch ( ... ) { | |
| _lock.unlock(); | | _lock.unlock(); | |
| throw; | | throw; | |
| } | | } | |
| | | | |
| } | | } | |
| | | | |
| int size() const { return *_size; } | | int size() const { return *_size; } | |
| bool have() const { return size() > 0; } | | bool have() const { return size() > 0; } | |
| | | | |
|
| BSONObj get( bool threadSafe ){ | | BSONObj get() const { | |
| _lock.lock(); | | _lock.lock(); | |
|
| | | | |
| BSONObj o; | | BSONObj o; | |
|
| | | | |
| try { | | try { | |
|
| o = _get( threadSafe ); | | o = _get(); | |
| _lock.unlock(); | | _lock.unlock(); | |
| } | | } | |
|
| catch ( ... ){ | | catch ( ... ) { | |
| _lock.unlock(); | | _lock.unlock(); | |
| throw; | | throw; | |
| } | | } | |
|
| | | | |
| return o; | | return o; | |
|
| | | | |
| } | | } | |
| | | | |
|
| void append( BSONObjBuilder& b , const StringData& name ){ | | void append( BSONObjBuilder& b , const StringData& name ) const { | |
| _lock.lock(); | | scoped_spinlock lk(_lock); | |
| try { | | BSONObj temp = _get(); | |
| b.append( name , _get( false ) ); | | b.append( name , temp ); | |
| _lock.unlock(); | | | |
| } | | | |
| catch ( ... ){ | | | |
| _lock.unlock(); | | | |
| throw; | | | |
| } | | | |
| } | | } | |
| | | | |
| private: | | private: | |
|
| | | | |
| /** you have to be locked when you call this */ | | /** you have to be locked when you call this */ | |
|
| BSONObj _get( bool getCopy ){ | | BSONObj _get() const { | |
| int sz = size(); | | int sz = size(); | |
|
| | | | |
| if ( sz == 0 ) | | if ( sz == 0 ) | |
| return BSONObj(); | | return BSONObj(); | |
|
| | | if ( sz == TOO_BIG_SENTINEL ) | |
| if ( sz == 1 ) | | | |
| return _tooBig; | | return _tooBig; | |
|
| | | | |
| return BSONObj( _buf ).copy(); | | return BSONObj( _buf ).copy(); | |
| } | | } | |
| | | | |
|
| SpinLock _lock; | | /** you have to be locked when you call this */ | |
| char _buf[512]; | | void _reset( int sz ) { _size[0] = sz; } | |
| | | | |
| | | mutable SpinLock _lock; | |
| int * _size; | | int * _size; | |
|
| | | char _buf[512]; | |
| }; | | }; | |
| | | | |
| /* Current operation (for the current Client). | | /* Current operation (for the current Client). | |
|
| an embedded member of Client class, and typically used from within t
he mutex there. */ | | an embedded member of Client class, and typically used from within t
he mutex there. | |
| | | */ | |
| class CurOp : boost::noncopyable { | | class CurOp : boost::noncopyable { | |
|
| static AtomicUInt _nextOpNum; | | | |
| | | | |
| Client * _client; | | | |
| CurOp * _wrapped; | | | |
| | | | |
| unsigned long long _start; | | | |
| unsigned long long _checkpoint; | | | |
| unsigned long long _end; | | | |
| | | | |
| bool _active; | | | |
| int _op; | | | |
| bool _command; | | | |
| int _lockType; // see concurrency.h for values | | | |
| bool _waitingForLock; | | | |
| int _dbprofile; // 0=off, 1=slow, 2=all | | | |
| AtomicUInt _opNum; | | | |
| char _ns[Namespace::MaxNsLen+2]; | | | |
| struct SockAddr _remote; | | | |
| CachedBSONObj _query; | | | |
| | | | |
| OpDebug _debug; | | | |
| | | | |
| ThreadSafeString _message; | | | |
| ProgressMeter _progressMeter; | | | |
| | | | |
| void _reset(){ | | | |
| _command = false; | | | |
| _lockType = 0; | | | |
| _dbprofile = 0; | | | |
| _end = 0; | | | |
| _waitingForLock = false; | | | |
| _message = ""; | | | |
| _progressMeter.finished(); | | | |
| } | | | |
| | | | |
| void setNS(const char *ns) { | | | |
| strncpy(_ns, ns, Namespace::MaxNsLen); | | | |
| } | | | |
| | | | |
| public: | | public: | |
|
| | | CurOp( Client * client , CurOp * wrapped = 0 ); | |
| | | ~CurOp(); | |
| | | | |
| bool haveQuery() const { return _query.have(); } | | bool haveQuery() const { return _query.have(); } | |
|
| BSONObj query( bool threadSafe = false ){ return _query.get( thread
Safe ); } | | BSONObj query() { return _query.get(); } | |
| | | void appendQuery( BSONObjBuilder& b , const StringData& name ) cons
t { _query.append( b , name ); } | |
| | | | |
|
| void ensureStarted(){ | | void ensureStarted() { | |
| if ( _start == 0 ) | | if ( _start == 0 ) | |
| _start = _checkpoint = curTimeMicros64(); | | _start = _checkpoint = curTimeMicros64(); | |
| } | | } | |
|
| void enter( Client::Context * context ){ | | bool isStarted() const { return _start > 0; } | |
| ensureStarted(); | | | |
| setNS( context->ns() ); | | | |
| if ( context->_db && context->_db->profile > _dbprofile ) | | | |
| _dbprofile = context->_db->profile; | | | |
| } | | | |
| | | | |
|
| void leave( Client::Context * context ){ | | void enter( Client::Context * context ); | |
| unsigned long long now = curTimeMicros64(); | | | |
| Top::global.record( _ns , _op , _lockType , now - _checkpoint ,
_command ); | | | |
| _checkpoint = now; | | | |
| } | | | |
| | | | |
|
| void reset(){ | | void leave( Client::Context * context ); | |
| | | | |
| | | void reset() { | |
| _reset(); | | _reset(); | |
| _start = _checkpoint = 0; | | _start = _checkpoint = 0; | |
|
| _active = true; | | | |
| _opNum = _nextOpNum++; | | _opNum = _nextOpNum++; | |
|
| _ns[0] = '?'; // just in case not set later | | _ns[0] = 0; | |
| _debug.reset(); | | _debug.reset(); | |
| _query.reset(); | | _query.reset(); | |
|
| | | _active = true; // this should be last for ui clarity | |
| } | | } | |
| | | | |
|
| void reset( const SockAddr & remote, int op ) { | | void reset( const HostAndPort& remote, int op ) { | |
| reset(); | | reset(); | |
| _remote = remote; | | _remote = remote; | |
| _op = op; | | _op = op; | |
| } | | } | |
| | | | |
|
| void markCommand(){ | | void markCommand() { _command = true; } | |
| _command = true; | | | |
| } | | | |
| | | | |
|
| void waitingForLock( int type ){ | | void waitingForLock( int type ) { | |
| _waitingForLock = true; | | _waitingForLock = true; | |
| if ( type > 0 ) | | if ( type > 0 ) | |
| _lockType = 1; | | _lockType = 1; | |
| else | | else | |
| _lockType = -1; | | _lockType = -1; | |
| } | | } | |
|
| void gotLock(){ | | void gotLock() { _waitingForLock = false; } | |
| _waitingForLock = false; | | OpDebug& debug() { return _debug; } | |
| } | | int profileLevel() const { return _dbprofile; } | |
| | | const char * getNS() const { return _ns; } | |
| OpDebug& debug(){ | | | |
| return _debug; | | | |
| } | | | |
| | | | |
| int profileLevel() const { | | | |
| return _dbprofile; | | | |
| } | | | |
| | | | |
| const char * getNS() const { | | | |
| return _ns; | | | |
| } | | | |
| | | | |
| bool shouldDBProfile( int ms ) const { | | bool shouldDBProfile( int ms ) const { | |
| if ( _dbprofile <= 0 ) | | if ( _dbprofile <= 0 ) | |
| return false; | | return false; | |
| | | | |
| return _dbprofile >= 2 || ms >= cmdLine.slowMS; | | return _dbprofile >= 2 || ms >= cmdLine.slowMS; | |
| } | | } | |
| | | | |
| AtomicUInt opNum() const { return _opNum; } | | AtomicUInt opNum() const { return _opNum; } | |
| | | | |
| | | | |
| skipping to change at line 265 | | skipping to change at line 246 | |
| void done() { | | void done() { | |
| _active = false; | | _active = false; | |
| _end = curTimeMicros64(); | | _end = curTimeMicros64(); | |
| } | | } | |
| | | | |
| unsigned long long totalTimeMicros() { | | unsigned long long totalTimeMicros() { | |
| massert( 12601 , "CurOp not marked done yet" , ! _active ); | | massert( 12601 , "CurOp not marked done yet" , ! _active ); | |
| return _end - startTime(); | | return _end - startTime(); | |
| } | | } | |
| | | | |
|
| int totalTimeMillis() { | | int totalTimeMillis() { return (int) (totalTimeMicros() / 1000); } | |
| return (int) (totalTimeMicros() / 1000); | | | |
| } | | | |
| | | | |
| int elapsedMillis() { | | int elapsedMillis() { | |
| unsigned long long total = curTimeMicros64() - startTime(); | | unsigned long long total = curTimeMicros64() - startTime(); | |
| return (int) (total / 1000); | | return (int) (total / 1000); | |
| } | | } | |
| | | | |
|
| int elapsedSeconds() { | | int elapsedSeconds() { return elapsedMillis() / 1000; } | |
| return elapsedMillis() / 1000; | | | |
| } | | | |
| | | | |
| void setQuery(const BSONObj& query) { | | | |
| _query.set( query ); | | | |
| } | | | |
| | | | |
| Client * getClient() const { | | | |
| return _client; | | | |
| } | | | |
| | | | |
|
| CurOp( Client * client , CurOp * wrapped = 0 ) { | | void setQuery(const BSONObj& query) { _query.set( query ); } | |
| _client = client; | | | |
| _wrapped = wrapped; | | | |
| if ( _wrapped ){ | | | |
| _client->_curOp = this; | | | |
| } | | | |
| _start = _checkpoint = 0; | | | |
| _active = false; | | | |
| _reset(); | | | |
| _op = 0; | | | |
| // These addresses should never be written to again. The zeroe
s are | | | |
| // placed here as a precaution because currentOp may be accesse
d | | | |
| // without the db mutex. | | | |
| memset(_ns, 0, sizeof(_ns)); | | | |
| } | | | |
| | | | |
|
| ~CurOp(); | | Client * getClient() const { return _client; } | |
| | | | |
| BSONObj info() { | | BSONObj info() { | |
| if( ! cc().getAuthenticationInfo()->isAuthorized("admin") ) { | | if( ! cc().getAuthenticationInfo()->isAuthorized("admin") ) { | |
| BSONObjBuilder b; | | BSONObjBuilder b; | |
| b.append("err", "unauthorized"); | | b.append("err", "unauthorized"); | |
| return b.obj(); | | return b.obj(); | |
| } | | } | |
| return infoNoauth(); | | return infoNoauth(); | |
| } | | } | |
| | | | |
| BSONObj infoNoauth(); | | BSONObj infoNoauth(); | |
| | | | |
|
| string getRemoteString( bool includePort = true ){ | | string getRemoteString( bool includePort = true ) { return _remote.
toString(includePort); } | |
| return _remote.toString(includePort); | | | |
| } | | | |
| | | | |
| ProgressMeter& setMessage( const char * msg , long long progressMet
erTotal = 0 , int secondsBetween = 3 ){ | | | |
| | | | |
|
| if ( progressMeterTotal ){ | | ProgressMeter& setMessage( const char * msg , unsigned long long pr
ogressMeterTotal = 0 , int secondsBetween = 3 ) { | |
| if ( _progressMeter.isActive() ){ | | if ( progressMeterTotal ) { | |
| | | if ( _progressMeter.isActive() ) { | |
| cout << "about to assert, old _message: " << _message <
< " new message:" << msg << endl; | | cout << "about to assert, old _message: " << _message <
< " new message:" << msg << endl; | |
| assert( ! _progressMeter.isActive() ); | | assert( ! _progressMeter.isActive() ); | |
| } | | } | |
| _progressMeter.reset( progressMeterTotal , secondsBetween )
; | | _progressMeter.reset( progressMeterTotal , secondsBetween )
; | |
| } | | } | |
| else { | | else { | |
| _progressMeter.finished(); | | _progressMeter.finished(); | |
| } | | } | |
| | | | |
| _message = msg; | | _message = msg; | |
| | | | |
| return _progressMeter; | | return _progressMeter; | |
| } | | } | |
| | | | |
| string getMessage() const { return _message.toString(); } | | string getMessage() const { return _message.toString(); } | |
| ProgressMeter& getProgressMeter() { return _progressMeter; } | | ProgressMeter& getProgressMeter() { return _progressMeter; } | |
|
| | | CurOp *parent() const { return _wrapped; } | |
| | | void kill() { _killed = true; } | |
| | | bool killed() const { return _killed; } | |
| | | void yielded() { _numYields++; } | |
| | | void setNS(const char *ns) { | |
| | | strncpy(_ns, ns, Namespace::MaxNsLen); | |
| | | _ns[Namespace::MaxNsLen] = 0; | |
| | | } | |
| friend class Client; | | friend class Client; | |
|
| | | | |
| | | private: | |
| | | static AtomicUInt _nextOpNum; | |
| | | Client * _client; | |
| | | CurOp * _wrapped; | |
| | | unsigned long long _start; | |
| | | unsigned long long _checkpoint; | |
| | | unsigned long long _end; | |
| | | bool _active; | |
| | | int _op; | |
| | | bool _command; | |
| | | int _lockType; // see concurrency.h for values | |
| | | bool _waitingForLock; | |
| | | int _dbprofile; // 0=off, 1=slow, 2=all | |
| | | AtomicUInt _opNum; | |
| | | char _ns[Namespace::MaxNsLen+2]; | |
| | | HostAndPort _remote; | |
| | | CachedBSONObj _query; | |
| | | OpDebug _debug; | |
| | | ThreadSafeString _message; | |
| | | ProgressMeter _progressMeter; | |
| | | volatile bool _killed; | |
| | | int _numYields; | |
| | | | |
| | | void _reset() { | |
| | | _command = false; | |
| | | _lockType = 0; | |
| | | _dbprofile = 0; | |
| | | _end = 0; | |
| | | _waitingForLock = false; | |
| | | _message = ""; | |
| | | _progressMeter.finished(); | |
| | | _killed = false; | |
| | | _numYields = 0; | |
| | | } | |
| }; | | }; | |
| | | | |
|
| /* 0 = ok | | /* _globalKill: we are shutting down | |
| 1 = kill current operation and reset this to 0 | | otherwise kill attribute set on specified CurOp | |
| future: maybe use this as a "going away" thing on process terminatio
n with a higher flag value | | this class does not handle races between interruptJs and the checkFo
rInterrupt functions - those must be | |
| | | handled by the client of this class | |
| */ | | */ | |
| extern class KillCurrentOp { | | extern class KillCurrentOp { | |
|
| enum { Off, On, All } state; | | | |
| AtomicUInt toKill; | | | |
| public: | | public: | |
|
| void killAll() { state = All; } | | void killAll(); | |
| void kill(AtomicUInt i) { toKill = i; state = On; } | | void kill(AtomicUInt i); | |
| | | | |
|
| void checkForInterrupt() { | | /** @return true if global interrupt and should terminate the opera
tion */ | |
| if( state != Off ) { | | bool globalInterruptCheck() const { return _globalKill; } | |
| if( state == All ) | | | |
| uasserted(11600,"interrupted at shutdown"); | | void checkForInterrupt( bool heedMutex = true ) { | |
| if( cc().curop()->opNum() == toKill ) { | | if ( heedMutex && dbMutex.isWriteLocked() ) | |
| state = Off; | | return; | |
| uasserted(11601,"interrupted"); | | if( _globalKill ) | |
| } | | uasserted(11600,"interrupted at shutdown"); | |
| } | | if( cc().curop()->killed() ) | |
| | | uasserted(11601,"interrupted"); | |
| } | | } | |
|
| | | | |
| | | /** @return "" if not interrupted. otherwise, you should stop. */ | |
| | | const char *checkForInterruptNoAssert( bool heedMutex = true ) { | |
| | | if ( heedMutex && dbMutex.isWriteLocked() ) | |
| | | return ""; | |
| | | if( _globalKill ) | |
| | | return "interrupted at shutdown"; | |
| | | if( cc().curop()->killed() ) | |
| | | return "interrupted"; | |
| | | return ""; | |
| | | } | |
| | | | |
| | | private: | |
| | | void interruptJs( AtomicUInt *op ); | |
| | | volatile bool _globalKill; | |
| } killCurrentOp; | | } killCurrentOp; | |
|
| | | | |
| } | | } | |
| | | | |
End of changes. 57 change blocks. |
| 169 lines changed or deleted | | 179 lines changed or added | |
|
| d_logic.h | | d_logic.h | |
|
| // d_logic.h | | // @file d_logic.h | |
| /* | | /* | |
| * Copyright (C) 2010 10gen Inc. | | * Copyright (C) 2010 10gen Inc. | |
| * | | * | |
| * This program is free software: you can redistribute it and/or modify | | * 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, | | * it under the terms of the GNU Affero General Public License, version
3, | |
| * as published by the Free Software Foundation. | | * as published by the Free Software Foundation. | |
| * | | * | |
| * This program is distributed in the hope that it will be useful, | | * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| | | | |
| skipping to change at line 21 | | skipping to change at line 21 | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Affero General Public License for more details. | | * GNU Affero General Public License for more details. | |
| * | | * | |
| * You should have received a copy of the GNU Affero General Public Lice
nse | | * You should have received a copy of the GNU Affero General Public Lice
nse | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../pch.h" | | #include "../pch.h" | |
|
| | | | |
| #include "../db/jsobj.h" | | #include "../db/jsobj.h" | |
|
| | | | |
| | | #include "d_chunk_manager.h" | |
| #include "util.h" | | #include "util.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| class ShardingState; | | class Database; | |
| | | class DiskLoc; | |
| | | | |
| typedef ShardChunkVersion ConfigVersion; | | typedef ShardChunkVersion ConfigVersion; | |
| typedef map<string,ConfigVersion> NSVersionMap; | | typedef map<string,ConfigVersion> NSVersionMap; | |
| | | | |
|
| // ----------- | | | |
| | | | |
| class ChunkMatcher { | | | |
| typedef map<BSONObj,pair<BSONObj,BSONObj>,BSONObjCmp> MyMap; | | | |
| public: | | | |
| | | | |
| bool belongsToMe( const BSONObj& key , const DiskLoc& loc ) const; | | | |
| | | | |
| private: | | | |
| ChunkMatcher( ConfigVersion version ); | | | |
| | | | |
| void gotRange( const BSONObj& min , const BSONObj& max ); | | | |
| | | | |
| ConfigVersion _version; | | | |
| BSONObj _key; | | | |
| MyMap _map; | | | |
| | | | |
| friend class ShardingState; | | | |
| }; | | | |
| | | | |
| typedef shared_ptr<ChunkMatcher> ChunkMatcherPtr; | | | |
| | | | |
| // -------------- | | // -------------- | |
| // --- global state --- | | // --- global state --- | |
| // -------------- | | // -------------- | |
| | | | |
| class ShardingState { | | class ShardingState { | |
| public: | | public: | |
| ShardingState(); | | ShardingState(); | |
| | | | |
| bool enabled() const { return _enabled; } | | bool enabled() const { return _enabled; } | |
| const string& getConfigServer() const { return _configServer; } | | const string& getConfigServer() const { return _configServer; } | |
| void enable( const string& server ); | | void enable( const string& server ); | |
| | | | |
| void gotShardName( const string& name ); | | void gotShardName( const string& name ); | |
|
| void gotShardHost( const string& host ); | | void gotShardHost( string host ); | |
| | | | |
| | | /** Reverts back to a state where this mongod is not sharded. */ | |
| | | void resetShardingState(); | |
| | | | |
| | | // versioning support | |
| | | | |
| bool hasVersion( const string& ns ); | | bool hasVersion( const string& ns ); | |
| bool hasVersion( const string& ns , ConfigVersion& version ); | | bool hasVersion( const string& ns , ConfigVersion& version ); | |
|
| ConfigVersion& getVersion( const string& ns ); // TODO: this is dan
geroues | | const ConfigVersion getVersion( const string& ns ) const; | |
| void setVersion( const string& ns , const ConfigVersion& version ); | | | |
| | | /** | |
| | | * Uninstalls the manager for a given collection. This should be us
ed when the collection is dropped. | |
| | | * | |
| | | * NOTE: | |
| | | * An existing collection with no chunks on this shard will have
a manager on version 0, which is different than a | |
| | | * a dropped collection, which will not have a manager. | |
| | | * | |
| | | * TODO | |
| | | * When sharding state is enabled, absolutely all collections sho
uld have a manager. (The non-sharded ones are | |
| | | * a be degenerate case of one-chunk collections). | |
| | | * For now, a dropped collection and an non-sharded one are indis
tinguishable (SERVER-1849) | |
| | | * | |
| | | * @param ns the collection to be dropped | |
| | | */ | |
| | | void resetVersion( const string& ns ); | |
| | | | |
| | | /** | |
| | | * Requests to access a collection at a certain version. If the col
lection's manager is not at that version it | |
| | | * will try to update itself to the newest version. The request is
only granted if the version is the current or | |
| | | * the newest one. | |
| | | * | |
| | | * @param ns collection to be accessed | |
| | | * @param version (IN) the client belive this collection is on and
(OUT) the version the manager is actually in | |
| | | * @return true if the access can be allowed at the provided versio
n | |
| | | */ | |
| | | bool trySetVersion( const string& ns , ConfigVersion& version ); | |
| | | | |
| void appendInfo( BSONObjBuilder& b ); | | void appendInfo( BSONObjBuilder& b ); | |
| | | | |
|
| ChunkMatcherPtr getChunkMatcher( const string& ns ); | | // querying support | |
| | | | |
| | | bool needShardChunkManager( const string& ns ) const; | |
| | | ShardChunkManagerPtr getShardChunkManager( const string& ns ); | |
| | | | |
| | | // chunk migrate and split support | |
| | | | |
| | | /** | |
| | | * Creates and installs a new chunk manager for a given collection
by "forgetting" about one of its chunks. | |
| | | * The new manager uses the provided version, which has to be highe
r than the current manager's. | |
| | | * One exception: if the forgotten chunk is the last one in this sh
ard for the collection, version has to be 0. | |
| | | * | |
| | | * If it runs successfully, clients need to grab the new version to
access the collection. | |
| | | * | |
| | | * @param ns the collection | |
| | | * @param min max the chunk to eliminate from the current manager | |
| | | * @param version at which the new manager should be at | |
| | | */ | |
| | | void donateChunk( const string& ns , const BSONObj& min , const BSO
NObj& max , ShardChunkVersion version ); | |
| | | | |
| | | /** | |
| | | * Creates and installs a new chunk manager for a given collection
by reclaiming a previously donated chunk. | |
| | | * The previous manager's version has to be provided. | |
| | | * | |
| | | * If it runs successfully, clients that became stale by the previo
us donateChunk will be able to access the | |
| | | * collection again. | |
| | | * | |
| | | * @param ns the collection | |
| | | * @param min max the chunk to reclaim and add to the current manag
er | |
| | | * @param version at which the new manager should be at | |
| | | */ | |
| | | void undoDonateChunk( const string& ns , const BSONObj& min , const
BSONObj& max , ShardChunkVersion version ); | |
| | | | |
| | | /** | |
| | | * Creates and installs a new chunk manager for a given collection
by splitting one of its chunks in two or more. | |
| | | * The version for the first split chunk should be provided. The su
bsequent chunks' version would be the latter with the | |
| | | * minor portion incremented. | |
| | | * | |
| | | * The effect on clients will depend on the version used. If the ma
jor portion is the same as the current shards, | |
| | | * clients shouldn't perceive the split. | |
| | | * | |
| | | * @param ns the collection | |
| | | * @param min max the chunk that should be split | |
| | | * @param splitKeys point in which to split | |
| | | * @param version at which the new manager should be at | |
| | | */ | |
| | | void splitChunk( const string& ns , const BSONObj& min , const BSON
Obj& max , const vector<BSONObj>& splitKeys , | |
| | | ShardChunkVersion version ); | |
| | | | |
| bool inCriticalMigrateSection(); | | bool inCriticalMigrateSection(); | |
|
| private: | | | |
| | | | |
|
| | | private: | |
| bool _enabled; | | bool _enabled; | |
| | | | |
| string _configServer; | | string _configServer; | |
| | | | |
| string _shardName; | | string _shardName; | |
| string _shardHost; | | string _shardHost; | |
| | | | |
|
| mongo::mutex _mutex; | | // protects state below | |
| NSVersionMap _versions; | | mutable mongo::mutex _mutex; | |
| map<string,ChunkMatcherPtr> _chunks; | | | |
| | | // map from a namespace into the ensemble of chunk ranges that are
stored in this mongod | |
| | | // a ShardChunkManager carries all state we need for a collection a
t this shard, including its version information | |
| | | typedef map<string,ShardChunkManagerPtr> ChunkManagersMap; | |
| | | ChunkManagersMap _chunks; | |
| }; | | }; | |
| | | | |
| extern ShardingState shardingState; | | extern ShardingState shardingState; | |
| | | | |
|
| // -------------- | | /** | |
| // --- per connection --- | | * one per connection from mongos | |
| // -------------- | | * holds version state for each namesapce | |
| | | */ | |
| class ShardedConnectionInfo { | | class ShardedConnectionInfo { | |
| public: | | public: | |
| ShardedConnectionInfo(); | | ShardedConnectionInfo(); | |
| | | | |
| const OID& getID() const { return _id; } | | const OID& getID() const { return _id; } | |
| bool hasID() const { return _id.isSet(); } | | bool hasID() const { return _id.isSet(); } | |
| void setID( const OID& id ); | | void setID( const OID& id ); | |
| | | | |
|
| ConfigVersion& getVersion( const string& ns ); // TODO: this is dan
geroues | | const ConfigVersion getVersion( const string& ns ) const; | |
| void setVersion( const string& ns , const ConfigVersion& version ); | | void setVersion( const string& ns , const ConfigVersion& version ); | |
| | | | |
| static ShardedConnectionInfo* get( bool create ); | | static ShardedConnectionInfo* get( bool create ); | |
| static void reset(); | | static void reset(); | |
|
| | | static void addHook(); | |
| | | | |
|
| bool inForceMode() const { | | bool inForceVersionOkMode() const { | |
| return _forceMode; | | return _forceVersionOk; | |
| } | | } | |
| | | | |
|
| void enterForceMode(){ _forceMode = true; } | | void enterForceVersionOkMode() { _forceVersionOk = true; } | |
| void leaveForceMode(){ _forceMode = false; } | | void leaveForceVersionOkMode() { _forceVersionOk = false; } | |
| | | | |
| private: | | private: | |
| | | | |
| OID _id; | | OID _id; | |
| NSVersionMap _versions; | | NSVersionMap _versions; | |
|
| bool _forceMode; | | bool _forceVersionOk; // if this is true, then chunk version #s are
n't check, and all ops are allowed | |
| | | | |
| static boost::thread_specific_ptr<ShardedConnectionInfo> _tl; | | static boost::thread_specific_ptr<ShardedConnectionInfo> _tl; | |
| }; | | }; | |
| | | | |
|
| struct ShardForceModeBlock { | | struct ShardForceVersionOkModeBlock { | |
| ShardForceModeBlock(){ | | ShardForceVersionOkModeBlock() { | |
| info = ShardedConnectionInfo::get( false ); | | info = ShardedConnectionInfo::get( false ); | |
| if ( info ) | | if ( info ) | |
|
| info->enterForceMode(); | | info->enterForceVersionOkMode(); | |
| } | | } | |
|
| ~ShardForceModeBlock(){ | | ~ShardForceVersionOkModeBlock() { | |
| if ( info ) | | if ( info ) | |
|
| info->leaveForceMode(); | | info->leaveForceVersionOkMode(); | |
| } | | } | |
| | | | |
| ShardedConnectionInfo * info; | | ShardedConnectionInfo * info; | |
| }; | | }; | |
| | | | |
| // ----------------- | | // ----------------- | |
| // --- core --- | | // --- core --- | |
| // ----------------- | | // ----------------- | |
| | | | |
| unsigned long long extractVersion( BSONElement e , string& errmsg ); | | unsigned long long extractVersion( BSONElement e , string& errmsg ); | |
| | | | |
| /** | | /** | |
| * @return true if we have any shard info for the ns | | * @return true if we have any shard info for the ns | |
| */ | | */ | |
| bool haveLocalShardingInfo( const string& ns ); | | bool haveLocalShardingInfo( const string& ns ); | |
| | | | |
| /** | | /** | |
| * @return true if the current threads shard version is ok, or not in s
harded version | | * @return true if the current threads shard version is ok, or not in s
harded version | |
| */ | | */ | |
|
| bool shardVersionOk( const string& ns , bool write , string& errmsg ); | | bool shardVersionOk( const string& ns , string& errmsg ); | |
| | | | |
| /** | | /** | |
| * @return true if we took care of the message and nothing else should
be done | | * @return true if we took care of the message and nothing else should
be done | |
| */ | | */ | |
|
| bool handlePossibleShardedMessage( Message &m, DbResponse * dbresponse
); | | struct DbResponse; | |
| | | | |
|
| void logOpForSharding( const char * opstr , const char * ns , const BSO
NObj& obj , BSONObj * patt ); | | bool _handlePossibleShardedMessage( Message &m, DbResponse * dbresponse
); | |
| | | | |
|
| // ----------------- | | /** What does this do? document please? */ | |
| // --- writeback --- | | inline bool handlePossibleShardedMessage( Message &m, DbResponse * dbre
sponse ) { | |
| // ----------------- | | if( !shardingState.enabled() ) | |
| | | return false; | |
| | | return _handlePossibleShardedMessage(m, dbresponse); | |
| | | } | |
| | | | |
|
| /* queue a write back on a remote server for a failed write */ | | void logOpForSharding( const char * opstr , const char * ns , const BSO
NObj& obj , BSONObj * patt ); | |
| void queueWriteBack( const string& remote , const BSONObj& o ); | | void aboutToDeleteForSharding( const Database* db , const DiskLoc& dl )
; | |
| | | | |
| } | | } | |
| | | | |
End of changes. 26 change blocks. |
| 55 lines changed or deleted | | 123 lines changed or added | |
|
| dbclient.h | | dbclient.h | |
|
| /** @file dbclient.h - connect to a Mongo database as a database, from C++
*/ | | /** @file dbclient.h | |
| | | | |
| | | Core MongoDB C++ driver interfaces are defined here. | |
| | | */ | |
| | | | |
| /* Copyright 2009 10gen Inc. | | /* Copyright 2009 10gen Inc. | |
| * | | * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | | * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | | * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | | * You may obtain a copy of the License at | |
| * | | * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | | * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | | * | |
| * Unless required by applicable law or agreed to in writing, software | | * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | | * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | |
| * See the License for the specific language governing permissions and | | * See the License for the specific language governing permissions and | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../pch.h" | | #include "../pch.h" | |
|
| #include "../util/message.h" | | #include "../util/net/message.h" | |
| | | #include "../util/net/message_port.h" | |
| #include "../db/jsobj.h" | | #include "../db/jsobj.h" | |
| #include "../db/json.h" | | #include "../db/json.h" | |
| #include <stack> | | #include <stack> | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| /** the query field 'options' can have these bits set: */ | | /** the query field 'options' can have these bits set: */ | |
| enum QueryOptions { | | enum QueryOptions { | |
| /** Tailable means cursor is not closed when the last data is retri
eved. rather, the cursor marks | | /** Tailable means cursor is not closed when the last data is retri
eved. rather, the cursor marks | |
| the final object's position. you can resume using the cursor la
ter, from where it was located, | | the final object's position. you can resume using the cursor la
ter, from where it was located, | |
| | | | |
| skipping to change at line 73 | | skipping to change at line 77 | |
| | | | |
| /** Stream the data down full blast in multiple "more" packages, on
the assumption that the client | | /** Stream the data down full blast in multiple "more" packages, on
the assumption that the client | |
| will fully read all data queried. Faster when you are pulling
a lot of data and know you want to | | will fully read all data queried. Faster when you are pulling
a lot of data and know you want to | |
| pull it all down. Note: it is not allowed to not read all the
data unless you close the connection. | | pull it all down. Note: it is not allowed to not read all the
data unless you close the connection. | |
| | | | |
| Use the query( boost::function<void(const BSONObj&)> f, ... ) v
ersion of the connection's query() | | Use the query( boost::function<void(const BSONObj&)> f, ... ) v
ersion of the connection's query() | |
| method, and it will take care of all the details for you. | | method, and it will take care of all the details for you. | |
| */ | | */ | |
| QueryOption_Exhaust = 1 << 6, | | QueryOption_Exhaust = 1 << 6, | |
| | | | |
|
| QueryOption_AllSupported = QueryOption_CursorTailable | QueryOption
_SlaveOk | QueryOption_OplogReplay | QueryOption_NoCursorTimeout | QueryOpt
ion_AwaitData | QueryOption_Exhaust | | /** When sharded, this means its ok to return partial results | |
| | | Usually we will fail a query if all required shards aren't up | |
| | | If this is set, it'll be a partial result set | |
| | | */ | |
| | | QueryOption_PartialResults = 1 << 7 , | |
| | | | |
| | | QueryOption_AllSupported = QueryOption_CursorTailable | QueryOption
_SlaveOk | QueryOption_OplogReplay | QueryOption_NoCursorTimeout | QueryOpt
ion_AwaitData | QueryOption_Exhaust | QueryOption_PartialResults | |
| | | | |
| }; | | }; | |
| | | | |
| enum UpdateOptions { | | enum UpdateOptions { | |
| /** Upsert - that is, insert the item if no matching item is found.
*/ | | /** Upsert - that is, insert the item if no matching item is found.
*/ | |
| UpdateOption_Upsert = 1 << 0, | | UpdateOption_Upsert = 1 << 0, | |
| | | | |
| /** Update multiple documents (if multiple documents match query ex
pression). | | /** Update multiple documents (if multiple documents match query ex
pression). | |
| (Default is update a single document and stop.) */ | | (Default is update a single document and stop.) */ | |
| UpdateOption_Multi = 1 << 1, | | UpdateOption_Multi = 1 << 1, | |
| | | | |
| skipping to change at line 97 | | skipping to change at line 107 | |
| }; | | }; | |
| | | | |
| enum RemoveOptions { | | enum RemoveOptions { | |
| /** only delete one option */ | | /** only delete one option */ | |
| RemoveOption_JustOne = 1 << 0, | | RemoveOption_JustOne = 1 << 0, | |
| | | | |
| /** flag from mongo saying this update went everywhere */ | | /** flag from mongo saying this update went everywhere */ | |
| RemoveOption_Broadcast = 1 << 1 | | RemoveOption_Broadcast = 1 << 1 | |
| }; | | }; | |
| | | | |
|
| | | /** | |
| | | * need to put in DbMesssage::ReservedOptions as well | |
| | | */ | |
| | | enum InsertOptions { | |
| | | /** With muli-insert keep processing inserts if one fails */ | |
| | | InsertOption_ContinueOnError = 1 << 0 | |
| | | }; | |
| | | | |
| class DBClientBase; | | class DBClientBase; | |
| | | | |
|
| | | /** | |
| | | * ConnectionString handles parsing different ways to connect to mongo
and determining method | |
| | | * samples: | |
| | | * server | |
| | | * server:port | |
| | | * foo/server:port,server:port SET | |
| | | * server,server,server SYNC | |
| | | * | |
| | | * tyipcal use | |
| | | * string errmsg, | |
| | | * ConnectionString cs = ConnectionString::parse( url , errmsg ); | |
| | | * if ( ! cs.isValid() ) throw "bad: " + errmsg; | |
| | | * DBClientBase * conn = cs.connect( errmsg ); | |
| | | */ | |
| class ConnectionString { | | class ConnectionString { | |
| public: | | public: | |
| enum ConnectionType { INVALID , MASTER , PAIR , SET , SYNC }; | | enum ConnectionType { INVALID , MASTER , PAIR , SET , SYNC }; | |
| | | | |
|
| ConnectionString( const HostAndPort& server ){ | | ConnectionString() { | |
| | | _type = INVALID; | |
| | | } | |
| | | | |
| | | ConnectionString( const HostAndPort& server ) { | |
| _type = MASTER; | | _type = MASTER; | |
| _servers.push_back( server ); | | _servers.push_back( server ); | |
| _finishInit(); | | _finishInit(); | |
| } | | } | |
| | | | |
|
| // TODO Delete if nobody is using | | ConnectionString( ConnectionType type , const string& s , const str
ing& setName = "" ) { | |
| //ConnectionString( ConnectionType type , const vector<HostAndPort>
& servers ) | | | |
| // : _type( type ) , _servers( servers ){ | | | |
| // _finishInit(); | | | |
| //} | | | |
| | | | |
| ConnectionString( ConnectionType type , const string& s , const str
ing& setName = "" ){ | | | |
| _type = type; | | _type = type; | |
| _setName = setName; | | _setName = setName; | |
| _fillServers( s ); | | _fillServers( s ); | |
| | | | |
|
| switch ( _type ){ | | switch ( _type ) { | |
| case MASTER: | | case MASTER: | |
| assert( _servers.size() == 1 ); | | assert( _servers.size() == 1 ); | |
| break; | | break; | |
| case SET: | | case SET: | |
| assert( _setName.size() ); | | assert( _setName.size() ); | |
| assert( _servers.size() >= 1 ); // 1 is ok since we can der
ive | | assert( _servers.size() >= 1 ); // 1 is ok since we can der
ive | |
| break; | | break; | |
| case PAIR: | | case PAIR: | |
| assert( _servers.size() == 2 ); | | assert( _servers.size() == 2 ); | |
| break; | | break; | |
| default: | | default: | |
| assert( _servers.size() > 0 ); | | assert( _servers.size() > 0 ); | |
| } | | } | |
| | | | |
| _finishInit(); | | _finishInit(); | |
| } | | } | |
| | | | |
|
| ConnectionString( const string& s , ConnectionType favoredMultipleT
ype ){ | | ConnectionString( const string& s , ConnectionType favoredMultipleT
ype ) { | |
| | | _type = INVALID; | |
| | | | |
| _fillServers( s ); | | _fillServers( s ); | |
|
| if ( _servers.size() == 1 ){ | | if ( _type != INVALID ) { | |
| | | // set already | |
| | | } | |
| | | else if ( _servers.size() == 1 ) { | |
| _type = MASTER; | | _type = MASTER; | |
| } | | } | |
| else { | | else { | |
| _type = favoredMultipleType; | | _type = favoredMultipleType; | |
|
| assert( _type != MASTER ); | | assert( _type == SET || _type == SYNC ); | |
| } | | } | |
| _finishInit(); | | _finishInit(); | |
| } | | } | |
| | | | |
| bool isValid() const { return _type != INVALID; } | | bool isValid() const { return _type != INVALID; } | |
| | | | |
|
| string toString() const { | | string toString() const { return _string; } | |
| return _string; | | | |
| } | | | |
| | | | |
|
| DBClientBase* connect( string& errmsg ) const; | | DBClientBase* connect( string& errmsg, double socketTimeout = 0 ) c
onst; | |
| | | | |
|
| static ConnectionString parse( const string& url , string& errmsg )
; | | string getSetName() const { return _setName; } | |
| | | | |
|
| string getSetName() const{ | | vector<HostAndPort> getServers() const { return _servers; } | |
| return _setName; | | | |
| } | | | |
| | | | |
|
| vector<HostAndPort> getServers() const { | | ConnectionType type() const { return _type; } | |
| return _servers; | | | |
| } | | | |
| | | | |
|
| private: | | static ConnectionString parse( const string& url , string& errmsg )
; | |
| | | | |
|
| ConnectionString(){ | | static string typeToString( ConnectionType type ); | |
| _type = INVALID; | | | |
| } | | | |
| | | | |
|
| void _fillServers( string s ){ | | private: | |
| string::size_type idx; | | | |
| while ( ( idx = s.find( ',' ) ) != string::npos ){ | | | |
| _servers.push_back( s.substr( 0 , idx ) ); | | | |
| s = s.substr( idx + 1 ); | | | |
| } | | | |
| _servers.push_back( s ); | | | |
| } | | | |
| | | | |
|
| void _finishInit(){ | | void _fillServers( string s ); | |
| stringstream ss; | | void _finishInit(); | |
| if ( _type == SET ) | | | |
| ss << _setName << "/"; | | | |
| for ( unsigned i=0; i<_servers.size(); i++ ){ | | | |
| if ( i > 0 ) | | | |
| ss << ","; | | | |
| ss << _servers[i].toString(); | | | |
| } | | | |
| _string = ss.str(); | | | |
| } | | | |
| | | | |
| ConnectionType _type; | | ConnectionType _type; | |
| vector<HostAndPort> _servers; | | vector<HostAndPort> _servers; | |
| string _string; | | string _string; | |
| string _setName; | | string _setName; | |
| }; | | }; | |
| | | | |
| /** | | /** | |
| * controls how much a clients cares about writes | | * controls how much a clients cares about writes | |
| * default is NORMAL | | * default is NORMAL | |
| | | | |
| skipping to change at line 300 | | skipping to change at line 311 | |
| of these as "bind variables". | | of these as "bind variables". | |
| | | | |
| Examples: | | Examples: | |
| conn.findOne("test.coll", Query("{a:3}").where("this.b == 2 |
| this.c == 3")); | | conn.findOne("test.coll", Query("{a:3}").where("this.b == 2 |
| this.c == 3")); | |
| Query badBalance = Query().where("this.debits - this.credits
< 0"); | | Query badBalance = Query().where("this.debits - this.credits
< 0"); | |
| */ | | */ | |
| Query& where(const string &jscode, BSONObj scope); | | Query& where(const string &jscode, BSONObj scope); | |
| Query& where(const string &jscode) { return where(jscode, BSONObj()
); } | | Query& where(const string &jscode) { return where(jscode, BSONObj()
); } | |
| | | | |
| /** | | /** | |
|
| * if this query has an orderby, hint, or some other field | | * @return true if this query has an orderby, hint, or some other f
ield | |
| */ | | */ | |
| bool isComplex( bool * hasDollar = 0 ) const; | | bool isComplex( bool * hasDollar = 0 ) const; | |
| | | | |
| BSONObj getFilter() const; | | BSONObj getFilter() const; | |
| BSONObj getSort() const; | | BSONObj getSort() const; | |
| BSONObj getHint() const; | | BSONObj getHint() const; | |
| bool isExplain() const; | | bool isExplain() const; | |
| | | | |
| string toString() const; | | string toString() const; | |
| operator string() const { return toString(); } | | operator string() const { return toString(); } | |
| | | | |
| skipping to change at line 323 | | skipping to change at line 334 | |
| template< class T > | | template< class T > | |
| void appendComplex( const char *fieldName, const T& val ) { | | void appendComplex( const char *fieldName, const T& val ) { | |
| makeComplex(); | | makeComplex(); | |
| BSONObjBuilder b; | | BSONObjBuilder b; | |
| b.appendElements(obj); | | b.appendElements(obj); | |
| b.append(fieldName, val); | | b.append(fieldName, val); | |
| obj = b.obj(); | | obj = b.obj(); | |
| } | | } | |
| }; | | }; | |
| | | | |
|
| /** Typically one uses the QUERY(...) macro to construct a Query object. | | /** Typically one uses the QUERY(...) macro to construct a Query object
. | |
| Example: QUERY( "age" << 33 << "school" << "UCLA" ) | | Example: QUERY( "age" << 33 << "school" << "UCLA" ) | |
| */ | | */ | |
| #define QUERY(x) mongo::Query( BSON(x) ) | | #define QUERY(x) mongo::Query( BSON(x) ) | |
| | | | |
| /** | | /** | |
| interface that handles communication with the db | | interface that handles communication with the db | |
| */ | | */ | |
| class DBConnector { | | class DBConnector { | |
| public: | | public: | |
| virtual ~DBConnector() {} | | virtual ~DBConnector() {} | |
|
| virtual bool call( Message &toSend, Message &response, bool assertO
k=true ) = 0; | | /** actualServer is set to the actual server where they call went i
f there was a choice (SlaveOk) */ | |
| virtual void say( Message &toSend ) = 0; | | virtual bool call( Message &toSend, Message &response, bool assertO
k=true , string * actualServer = 0 ) = 0; | |
| | | virtual void say( Message &toSend, bool isRetry = false ) = 0; | |
| virtual void sayPiggyBack( Message &toSend ) = 0; | | virtual void sayPiggyBack( Message &toSend ) = 0; | |
|
| virtual void checkResponse( const char* data, int nReturned ) {} | | | |
| | | | |
| /* used by QueryOption_Exhaust. To use that your subclass must imp
lement this. */ | | /* used by QueryOption_Exhaust. To use that your subclass must imp
lement this. */ | |
|
| virtual void recv( Message& m ) { assert(false); } | | virtual bool recv( Message& m ) { assert(false); return false; } | |
| | | // In general, for lazy queries, we'll need to say, recv, then chec
kResponse | |
| virtual string getServerAddress() const = 0; | | virtual void checkResponse( const char* data, int nReturned, bool*
retry = NULL, string* targetHost = NULL ) { | |
| | | if( retry ) *retry = false; if( targetHost ) *targetHost = ""; | |
| | | } | |
| | | virtual bool lazySupported() const = 0; | |
| }; | | }; | |
| | | | |
| /** | | /** | |
| The interface that any db connection should implement | | The interface that any db connection should implement | |
| */ | | */ | |
| class DBClientInterface : boost::noncopyable { | | class DBClientInterface : boost::noncopyable { | |
| public: | | public: | |
| virtual auto_ptr<DBClientCursor> query(const string &ns, Query quer
y, int nToReturn = 0, int nToSkip = 0, | | virtual auto_ptr<DBClientCursor> query(const string &ns, Query quer
y, int nToReturn = 0, int nToSkip = 0, | |
| const BSONObj *fieldsToRetur
n = 0, int queryOptions = 0 , int batchSize = 0 ) = 0; | | const BSONObj *fieldsToRetur
n = 0, int queryOptions = 0 , int batchSize = 0 ) = 0; | |
| | | | |
|
| /** don't use this - called automatically by DBClientCursor for you
*/ | | virtual void insert( const string &ns, BSONObj obj , int flags=0) =
0; | |
| virtual auto_ptr<DBClientCursor> getMore( const string &ns, long lo
ng cursorId, int nToReturn = 0, int options = 0 ) = 0; | | | |
| | | | |
| virtual void insert( const string &ns, BSONObj obj ) = 0; | | | |
| | | | |
|
| virtual void insert( const string &ns, const vector< BSONObj >& v )
= 0; | | virtual void insert( const string &ns, const vector< BSONObj >& v ,
int flags=0) = 0; | |
| | | | |
| virtual void remove( const string &ns , Query query, bool justOne =
0 ) = 0; | | virtual void remove( const string &ns , Query query, bool justOne =
0 ) = 0; | |
| | | | |
| virtual void update( const string &ns , Query query , BSONObj obj ,
bool upsert = 0 , bool multi = 0 ) = 0; | | virtual void update( const string &ns , Query query , BSONObj obj ,
bool upsert = 0 , bool multi = 0 ) = 0; | |
| | | | |
| virtual ~DBClientInterface() { } | | virtual ~DBClientInterface() { } | |
| | | | |
| /** | | /** | |
| @return a single object that matches the query. if none do, the
n the object is empty | | @return a single object that matches the query. if none do, the
n the object is empty | |
| @throws AssertionException | | @throws AssertionException | |
| */ | | */ | |
| virtual BSONObj findOne(const string &ns, const Query& query, const
BSONObj *fieldsToReturn = 0, int queryOptions = 0); | | virtual BSONObj findOne(const string &ns, const Query& query, const
BSONObj *fieldsToReturn = 0, int queryOptions = 0); | |
| | | | |
|
| | | /** query N objects from the database into an array. makes sense m
ostly when you want a small number of results. if a huge number, use | |
| | | query() and iterate the cursor. | |
| | | */ | |
| | | void findN(vector<BSONObj>& out, const string&ns, Query query, int
nToReturn, int nToSkip = 0, const BSONObj *fieldsToReturn = 0, int queryOpt
ions = 0); | |
| | | | |
| | | virtual string getServerAddress() const = 0; | |
| | | | |
| | | /** don't use this - called automatically by DBClientCursor for you
*/ | |
| | | virtual auto_ptr<DBClientCursor> getMore( const string &ns, long lo
ng cursorId, int nToReturn = 0, int options = 0 ) = 0; | |
| }; | | }; | |
| | | | |
| /** | | /** | |
| DB "commands" | | DB "commands" | |
| Basically just invocations of connection.$cmd.findOne({...}); | | Basically just invocations of connection.$cmd.findOne({...}); | |
| */ | | */ | |
| class DBClientWithCommands : public DBClientInterface { | | class DBClientWithCommands : public DBClientInterface { | |
| set<string> _seenIndexes; | | set<string> _seenIndexes; | |
| public: | | public: | |
| /** controls how chatty the client is about network errors & such.
See log.h */ | | /** controls how chatty the client is about network errors & such.
See log.h */ | |
| | | | |
| skipping to change at line 399 | | skipping to change at line 418 | |
| @param command -- command name | | @param command -- command name | |
| @return true if the command returned "ok". | | @return true if the command returned "ok". | |
| */ | | */ | |
| bool simpleCommand(const string &dbname, BSONObj *info, const strin
g &command); | | bool simpleCommand(const string &dbname, BSONObj *info, const strin
g &command); | |
| | | | |
| /** Run a database command. Database commands are represented as B
SON objects. Common database | | /** Run a database command. Database commands are represented as B
SON objects. Common database | |
| commands have prebuilt helper functions -- see below. If a hel
per is not available you can | | commands have prebuilt helper functions -- see below. If a hel
per is not available you can | |
| directly call runCommand. | | directly call runCommand. | |
| | | | |
| @param dbname database name. Use "admin" for global administra
tive commands. | | @param dbname database name. Use "admin" for global administra
tive commands. | |
|
| @param cmd the command object to execute. For exam
ple, { ismaster : 1 } | | @param cmd the command object to execute. For example, { isma
ster : 1 } | |
| @param info the result object the database returns.
Typically has { ok : ..., errmsg : ... } fields | | @param info the result object the database returns. Typically h
as { ok : ..., errmsg : ... } fields | |
| set. | | set. | |
| @param options see enum QueryOptions - normally not needed to r
un a command | | @param options see enum QueryOptions - normally not needed to r
un a command | |
| @return true if the command returned "ok". | | @return true if the command returned "ok". | |
| */ | | */ | |
| virtual bool runCommand(const string &dbname, const BSONObj& cmd, B
SONObj &info, int options=0); | | virtual bool runCommand(const string &dbname, const BSONObj& cmd, B
SONObj &info, int options=0); | |
| | | | |
| /** Authorize access to a particular database. | | /** Authorize access to a particular database. | |
| Authentication is separate for each database on the server -- y
ou may authenticate for any | | Authentication is separate for each database on the server -- y
ou may authenticate for any | |
| number of databases on a single connection. | | number of databases on a single connection. | |
| The "admin" database is special and once authenticated provides
access to all databases on the | | The "admin" database is special and once authenticated provides
access to all databases on the | |
| server. | | server. | |
| @param digestPassword if password is plain text, set this to tr
ue. otherwise assumed to be pre-digested | | @param digestPassword if password is plain text, set this to tr
ue. otherwise assumed to be pre-digested | |
| @return true if successful | | @return true if successful | |
| */ | | */ | |
| virtual bool auth(const string &dbname, const string &username, con
st string &pwd, string& errmsg, bool digestPassword = true); | | virtual bool auth(const string &dbname, const string &username, con
st string &pwd, string& errmsg, bool digestPassword = true); | |
| | | | |
| /** count number of objects in collection ns that match the query c
riteria specified | | /** count number of objects in collection ns that match the query c
riteria specified | |
| throws UserAssertion if database returns an error | | throws UserAssertion if database returns an error | |
| */ | | */ | |
|
| unsigned long long count(const string &ns, const BSONObj& query = B
SONObj(), int options=0 ); | | virtual unsigned long long count(const string &ns, const BSONObj& q
uery = BSONObj(), int options=0, int limit=0, int skip=0 ); | |
| | | | |
| string createPasswordDigest( const string &username , const string
&clearTextPassword ); | | string createPasswordDigest( const string &username , const string
&clearTextPassword ); | |
| | | | |
| /** returns true in isMaster parm if this db is the current master | | /** returns true in isMaster parm if this db is the current master | |
| of a replica pair. | | of a replica pair. | |
| | | | |
| pass in info for more details e.g.: | | pass in info for more details e.g.: | |
| { "ismaster" : 1.0 , "msg" : "not paired" , "ok" : 1.0 } | | { "ismaster" : 1.0 , "msg" : "not paired" , "ok" : 1.0 } | |
| | | | |
| returns true if command invoked successfully. | | returns true if command invoked successfully. | |
| | | | |
| skipping to change at line 452 | | skipping to change at line 471 | |
| Must be <= 1000000000 for normal collections. | | Must be <= 1000000000 for normal collections. | |
| For fixed size (capped) collections, this size is
the total/max size of the | | For fixed size (capped) collections, this size is
the total/max size of the | |
| collection. | | collection. | |
| @param capped if true, this is a fixed size collection (where ol
d data rolls out). | | @param capped if true, this is a fixed size collection (where ol
d data rolls out). | |
| @param max maximum number of objects if capped (optional). | | @param max maximum number of objects if capped (optional). | |
| | | | |
| returns true if successful. | | returns true if successful. | |
| */ | | */ | |
| bool createCollection(const string &ns, long long size = 0, bool ca
pped = false, int max = 0, BSONObj *info = 0); | | bool createCollection(const string &ns, long long size = 0, bool ca
pped = false, int max = 0, BSONObj *info = 0); | |
| | | | |
|
| /** Get error result from the last operation on this connection. | | /** Get error result from the last write operation (insert/update/d
elete) on this connection. | |
| @return error message text, or empty string if no error. | | @return error message text, or empty string if no error. | |
| */ | | */ | |
| string getLastError(); | | string getLastError(); | |
|
| /** Get error result from the last operation on this connect
ion. | | | |
| @return full error object. | | | |
| */ | | | |
| virtual BSONObj getLastErrorDetailed(); | | | |
| | | | |
|
| | | /** Get error result from the last write operation (insert/update/d
elete) on this connection. | |
| | | @return full error object. | |
| | | */ | |
| | | virtual BSONObj getLastErrorDetailed(); | |
| | | | |
| | | /** Can be called with the returned value from getLastErrorDetailed
to extract an error string. | |
| | | If all you need is the string, just call getLastError() instead
. | |
| | | */ | |
| static string getLastErrorString( const BSONObj& res ); | | static string getLastErrorString( const BSONObj& res ); | |
| | | | |
| /** Return the last error which has occurred, even if not the very
last operation. | | /** Return the last error which has occurred, even if not the very
last operation. | |
| | | | |
| @return { err : <error message>, nPrev : <how_many_ops_back_occu
rred>, ok : 1 } | | @return { err : <error message>, nPrev : <how_many_ops_back_occu
rred>, ok : 1 } | |
| | | | |
| result.err will be null if no error has occurred. | | result.err will be null if no error has occurred. | |
| */ | | */ | |
| BSONObj getPrevError(); | | BSONObj getPrevError(); | |
| | | | |
| /** Reset the previous error state for this connection (accessed vi
a getLastError and | | /** Reset the previous error state for this connection (accessed vi
a getLastError and | |
| getPrevError). Useful when performing several operations at on
ce and then checking | | getPrevError). Useful when performing several operations at on
ce and then checking | |
| for an error after attempting all operations. | | for an error after attempting all operations. | |
| */ | | */ | |
| bool resetError() { return simpleCommand("admin", 0, "reseterror");
} | | bool resetError() { return simpleCommand("admin", 0, "reseterror");
} | |
| | | | |
| /** Delete the specified collection. */ | | /** Delete the specified collection. */ | |
|
| virtual bool dropCollection( const string &ns ){ | | virtual bool dropCollection( const string &ns ) { | |
| string db = nsGetDB( ns ); | | string db = nsGetDB( ns ); | |
| string coll = nsGetCollection( ns ); | | string coll = nsGetCollection( ns ); | |
| uassert( 10011 , "no collection name", coll.size() ); | | uassert( 10011 , "no collection name", coll.size() ); | |
| | | | |
| BSONObj info; | | BSONObj info; | |
| | | | |
| bool res = runCommand( db.c_str() , BSON( "drop" << coll ) , in
fo ); | | bool res = runCommand( db.c_str() , BSON( "drop" << coll ) , in
fo ); | |
| resetIndexCache(); | | resetIndexCache(); | |
| return res; | | return res; | |
| } | | } | |
| | | | |
| skipping to change at line 531 | | skipping to change at line 554 | |
| */ | | */ | |
| enum ProfilingLevel { | | enum ProfilingLevel { | |
| ProfileOff = 0, | | ProfileOff = 0, | |
| ProfileSlow = 1, // log very slow (>100ms) operations | | ProfileSlow = 1, // log very slow (>100ms) operations | |
| ProfileAll = 2 | | ProfileAll = 2 | |
| | | | |
| }; | | }; | |
| bool setDbProfilingLevel(const string &dbname, ProfilingLevel level
, BSONObj *info = 0); | | bool setDbProfilingLevel(const string &dbname, ProfilingLevel level
, BSONObj *info = 0); | |
| bool getDbProfilingLevel(const string &dbname, ProfilingLevel& leve
l, BSONObj *info = 0); | | bool getDbProfilingLevel(const string &dbname, ProfilingLevel& leve
l, BSONObj *info = 0); | |
| | | | |
|
| | | /** This implicitly converts from char*, string, and BSONObj to be
an argument to mapreduce | |
| | | You shouldn't need to explicitly construct this | |
| | | */ | |
| | | struct MROutput { | |
| | | MROutput(const char* collection) : out(BSON("replace" << collec
tion)) {} | |
| | | MROutput(const string& collection) : out(BSON("replace" << coll
ection)) {} | |
| | | MROutput(const BSONObj& obj) : out(obj) {} | |
| | | | |
| | | BSONObj out; | |
| | | }; | |
| | | static MROutput MRInline; | |
| | | | |
| /** Run a map/reduce job on the server. | | /** Run a map/reduce job on the server. | |
| | | | |
| See http://www.mongodb.org/display/DOCS/MapReduce | | See http://www.mongodb.org/display/DOCS/MapReduce | |
| | | | |
| ns namespace (db+collection name) of input data | | ns namespace (db+collection name) of input data | |
| jsmapf javascript map function code | | jsmapf javascript map function code | |
| jsreducef javascript reduce function code. | | jsreducef javascript reduce function code. | |
| query optional query filter for the input | | query optional query filter for the input | |
|
| output optional permanent output collection name. if not sp
ecified server will | | output either a string collection name or an object represen
ting output type | |
| generate a temporary collection and return its name. | | if not specified uses inline output type | |
| | | | |
| returns a result object which contains: | | returns a result object which contains: | |
| { result : <collection_name>, | | { result : <collection_name>, | |
| numObjects : <number_of_objects_scanned>, | | numObjects : <number_of_objects_scanned>, | |
| timeMillis : <job_time>, | | timeMillis : <job_time>, | |
| ok : <1_if_ok>, | | ok : <1_if_ok>, | |
| [, err : <errmsg_if_error>] | | [, err : <errmsg_if_error>] | |
| } | | } | |
| | | | |
| For example one might call: | | For example one might call: | |
| result.getField("ok").trueValue() | | result.getField("ok").trueValue() | |
| on the result to check if ok. | | on the result to check if ok. | |
| */ | | */ | |
|
| BSONObj mapreduce(const string &ns, const string &jsmapf, const str
ing &jsreducef, BSONObj query = BSONObj(), const string& output = ""); | | BSONObj mapreduce(const string &ns, const string &jsmapf, const str
ing &jsreducef, BSONObj query = BSONObj(), MROutput output = MRInline); | |
| | | | |
| /** Run javascript code on the database server. | | /** Run javascript code on the database server. | |
| dbname database SavedContext in which the code runs. The java
script variable 'db' will be assigned | | dbname database SavedContext in which the code runs. The java
script variable 'db' will be assigned | |
| to this database when the function is invoked. | | to this database when the function is invoked. | |
| jscode source code for a javascript function. | | jscode source code for a javascript function. | |
| info the command object which contains any information on t
he invocation result including | | info the command object which contains any information on t
he invocation result including | |
| the return value and other information. If an error
occurs running the jscode, error | | the return value and other information. If an error
occurs running the jscode, error | |
|
| information will be in info. (try "out() <
< info.toString()") | | information will be in info. (try "out() << info.toSt
ring()") | |
| retValue return value from the jscode function. | | retValue return value from the jscode function. | |
| args args to pass to the jscode function. when invoked, th
e 'args' variable will be defined | | args args to pass to the jscode function. when invoked, th
e 'args' variable will be defined | |
| for use by the jscode. | | for use by the jscode. | |
| | | | |
| returns true if runs ok. | | returns true if runs ok. | |
| | | | |
| See testDbEval() in dbclient.cpp for an example of usage. | | See testDbEval() in dbclient.cpp for an example of usage. | |
| */ | | */ | |
| bool eval(const string &dbname, const string &jscode, BSONObj& info
, BSONElement& retValue, BSONObj *args = 0); | | bool eval(const string &dbname, const string &jscode, BSONObj& info
, BSONElement& retValue, BSONObj *args = 0); | |
| | | | |
|
| /** | | /** validate a collection, checking for errors and reporting back s
tatistics. | |
| | | this operation is slow and blocking. | |
| */ | | */ | |
|
| bool validate( const string &ns , bool scandata=true ){ | | bool validate( const string &ns , bool scandata=true ) { | |
| BSONObj cmd = BSON( "validate" << nsGetCollection( ns ) << "sca
ndata" << scandata ); | | BSONObj cmd = BSON( "validate" << nsGetCollection( ns ) << "sca
ndata" << scandata ); | |
| BSONObj info; | | BSONObj info; | |
| return runCommand( nsGetDB( ns ).c_str() , cmd , info ); | | return runCommand( nsGetDB( ns ).c_str() , cmd , info ); | |
| } | | } | |
| | | | |
| /* The following helpers are simply more convenient forms of eval()
for certain common cases */ | | /* The following helpers are simply more convenient forms of eval()
for certain common cases */ | |
| | | | |
| /* invocation with no return value of interest -- with or without o
ne simple parameter */ | | /* invocation with no return value of interest -- with or without o
ne simple parameter */ | |
| bool eval(const string &dbname, const string &jscode); | | bool eval(const string &dbname, const string &jscode); | |
| template< class T > | | template< class T > | |
| | | | |
| skipping to change at line 630 | | skipping to change at line 665 | |
| list<string> getCollectionNames( const string& db ); | | list<string> getCollectionNames( const string& db ); | |
| | | | |
| bool exists( const string& ns ); | | bool exists( const string& ns ); | |
| | | | |
| /** Create an index if it does not already exist. | | /** Create an index if it does not already exist. | |
| ensureIndex calls are remembered so it is safe/fast to call thi
s function many | | ensureIndex calls are remembered so it is safe/fast to call thi
s function many | |
| times in your code. | | times in your code. | |
| @param ns collection to be indexed | | @param ns collection to be indexed | |
| @param keys the "key pattern" for the index. e.g., { name : 1 } | | @param keys the "key pattern" for the index. e.g., { name : 1 } | |
| @param unique if true, indicates that key uniqueness should be e
nforced for this index | | @param unique if true, indicates that key uniqueness should be e
nforced for this index | |
|
| @param name if not isn't specified, it will be created from the
keys (recommended) | | @param name if not specified, it will be created from the keys a
utomatically (which is recommended) | |
| | | @param cache if set to false, the index cache for the connection
won't remember this call | |
| | | @param background build index in the background (see mongodb doc
s/wiki for details) | |
| | | @param v index version. leave at default value. (unit tests set
this parameter.) | |
| @return whether or not sent message to db. | | @return whether or not sent message to db. | |
| should be true on first call, false on subsequent unless reset
IndexCache was called | | should be true on first call, false on subsequent unless reset
IndexCache was called | |
| */ | | */ | |
|
| virtual bool ensureIndex( const string &ns , BSONObj keys , bool un
ique = false, const string &name = "" ); | | virtual bool ensureIndex( const string &ns , BSONObj keys , bool un
ique = false, const string &name = "", | |
| | | bool cache = true, bool background = fals
e, int v = -1 ); | |
| | | | |
| /** | | /** | |
| clears the index cache, so the subsequent call to ensureIndex fo
r any index will go to the server | | clears the index cache, so the subsequent call to ensureIndex fo
r any index will go to the server | |
| */ | | */ | |
| virtual void resetIndexCache(); | | virtual void resetIndexCache(); | |
| | | | |
| virtual auto_ptr<DBClientCursor> getIndexes( const string &ns ); | | virtual auto_ptr<DBClientCursor> getIndexes( const string &ns ); | |
| | | | |
| virtual void dropIndex( const string& ns , BSONObj keys ); | | virtual void dropIndex( const string& ns , BSONObj keys ); | |
| virtual void dropIndex( const string& ns , const string& indexName
); | | virtual void dropIndex( const string& ns , const string& indexName
); | |
| | | | |
| skipping to change at line 665 | | skipping to change at line 704 | |
| /** Erase / drop an entire database */ | | /** Erase / drop an entire database */ | |
| virtual bool dropDatabase(const string &dbname, BSONObj *info = 0)
{ | | virtual bool dropDatabase(const string &dbname, BSONObj *info = 0)
{ | |
| bool ret = simpleCommand(dbname, info, "dropDatabase"); | | bool ret = simpleCommand(dbname, info, "dropDatabase"); | |
| resetIndexCache(); | | resetIndexCache(); | |
| return ret; | | return ret; | |
| } | | } | |
| | | | |
| virtual string toString() = 0; | | virtual string toString() = 0; | |
| | | | |
| /** @return the database name portion of an ns string */ | | /** @return the database name portion of an ns string */ | |
|
| string nsGetDB( const string &ns ){ | | string nsGetDB( const string &ns ) { | |
| string::size_type pos = ns.find( "." ); | | string::size_type pos = ns.find( "." ); | |
| if ( pos == string::npos ) | | if ( pos == string::npos ) | |
| return ns; | | return ns; | |
| | | | |
| return ns.substr( 0 , pos ); | | return ns.substr( 0 , pos ); | |
| } | | } | |
| | | | |
| /** @return the collection name portion of an ns string */ | | /** @return the collection name portion of an ns string */ | |
|
| string nsGetCollection( const string &ns ){ | | string nsGetCollection( const string &ns ) { | |
| string::size_type pos = ns.find( "." ); | | string::size_type pos = ns.find( "." ); | |
| if ( pos == string::npos ) | | if ( pos == string::npos ) | |
| return ""; | | return ""; | |
| | | | |
| return ns.substr( pos + 1 ); | | return ns.substr( pos + 1 ); | |
| } | | } | |
| | | | |
| protected: | | protected: | |
| bool isOk(const BSONObj&); | | bool isOk(const BSONObj&); | |
| | | | |
|
| | | BSONObj _countCmd(const string &ns, const BSONObj& query, int optio
ns, int limit, int skip ); | |
| | | | |
| enum QueryOptions availableOptions(); | | enum QueryOptions availableOptions(); | |
| | | | |
| private: | | private: | |
| enum QueryOptions _cachedAvailableOptions; | | enum QueryOptions _cachedAvailableOptions; | |
| bool _haveCachedAvailableOptions; | | bool _haveCachedAvailableOptions; | |
| }; | | }; | |
| | | | |
| /** | | /** | |
| abstract class that implements the core db operations | | abstract class that implements the core db operations | |
| */ | | */ | |
| class DBClientBase : public DBClientWithCommands, public DBConnector { | | class DBClientBase : public DBClientWithCommands, public DBConnector { | |
| protected: | | protected: | |
| WriteConcern _writeConcern; | | WriteConcern _writeConcern; | |
| | | | |
| public: | | public: | |
|
| DBClientBase(){ | | DBClientBase() { | |
| _writeConcern = W_NORMAL; | | _writeConcern = W_NORMAL; | |
| } | | } | |
| | | | |
| WriteConcern getWriteConcern() const { return _writeConcern; } | | WriteConcern getWriteConcern() const { return _writeConcern; } | |
|
| void setWriteConcern( WriteConcern w ){ _writeConcern = w; } | | void setWriteConcern( WriteConcern w ) { _writeConcern = w; } | |
| | | | |
| /** send a query to the database. | | /** send a query to the database. | |
| @param ns namespace to query, format is <dbname>.<collectname>[.<c
ollectname>]* | | @param ns namespace to query, format is <dbname>.<collectname>[.<c
ollectname>]* | |
| @param query query to perform on the collection. this is a BSONOb
j (binary JSON) | | @param query query to perform on the collection. this is a BSONOb
j (binary JSON) | |
| You may format as | | You may format as | |
| { query: { ... }, orderby: { ... } } | | { query: { ... }, orderby: { ... } } | |
| to specify a sort order. | | to specify a sort order. | |
|
| @param nToReturn n to return. 0 = unlimited | | @param nToReturn n to return (i.e., limit). 0 = unlimited | |
| @param nToSkip start with the nth item | | @param nToSkip start with the nth item | |
| @param fieldsToReturn optional template of which fields to select.
if unspecified, returns all fields | | @param fieldsToReturn optional template of which fields to select.
if unspecified, returns all fields | |
| @param queryOptions see options enum at top of this file | | @param queryOptions see options enum at top of this file | |
| | | | |
| @return cursor. 0 if error (connection failure) | | @return cursor. 0 if error (connection failure) | |
| @throws AssertionException | | @throws AssertionException | |
| */ | | */ | |
| virtual auto_ptr<DBClientCursor> query(const string &ns, Query quer
y, int nToReturn = 0, int nToSkip = 0, | | virtual auto_ptr<DBClientCursor> query(const string &ns, Query quer
y, int nToReturn = 0, int nToSkip = 0, | |
| const BSONObj *fieldsToRetur
n = 0, int queryOptions = 0 , int batchSize = 0 ); | | const BSONObj *fieldsToRetur
n = 0, int queryOptions = 0 , int batchSize = 0 ); | |
| | | | |
| /** don't use this - called automatically by DBClientCursor for you | | /** don't use this - called automatically by DBClientCursor for you | |
| @param cursorId id of cursor to retrieve | | @param cursorId id of cursor to retrieve | |
| @return an handle to a previously allocated cursor | | @return an handle to a previously allocated cursor | |
| @throws AssertionException | | @throws AssertionException | |
| */ | | */ | |
| virtual auto_ptr<DBClientCursor> getMore( const string &ns, long lo
ng cursorId, int nToReturn = 0, int options = 0 ); | | virtual auto_ptr<DBClientCursor> getMore( const string &ns, long lo
ng cursorId, int nToReturn = 0, int options = 0 ); | |
| | | | |
| /** | | /** | |
| insert an object into the database | | insert an object into the database | |
| */ | | */ | |
|
| virtual void insert( const string &ns , BSONObj obj ); | | virtual void insert( const string &ns , BSONObj obj , int flags=0); | |
| | | | |
| /** | | /** | |
| insert a vector of objects into the database | | insert a vector of objects into the database | |
| */ | | */ | |
|
| virtual void insert( const string &ns, const vector< BSONObj >& v )
; | | virtual void insert( const string &ns, const vector< BSONObj >& v ,
int flags=0); | |
| | | | |
| /** | | /** | |
| remove matching objects from the database | | remove matching objects from the database | |
| @param justOne if this true, then once a single match is found w
ill stop | | @param justOne if this true, then once a single match is found w
ill stop | |
| */ | | */ | |
| virtual void remove( const string &ns , Query q , bool justOne = 0
); | | virtual void remove( const string &ns , Query q , bool justOne = 0
); | |
| | | | |
| /** | | /** | |
| updates objects matching query | | updates objects matching query | |
| */ | | */ | |
| virtual void update( const string &ns , Query query , BSONObj obj ,
bool upsert = false , bool multi = false ); | | virtual void update( const string &ns , Query query , BSONObj obj ,
bool upsert = false , bool multi = false ); | |
| | | | |
| virtual bool isFailed() const = 0; | | virtual bool isFailed() const = 0; | |
| | | | |
| virtual void killCursor( long long cursorID ) = 0; | | virtual void killCursor( long long cursorID ) = 0; | |
| | | | |
|
| static int countCommas( const string& s ){ | | | |
| int n = 0; | | | |
| for ( unsigned i=0; i<s.size(); i++ ) | | | |
| if ( s[i] == ',' ) | | | |
| n++; | | | |
| return n; | | | |
| } | | | |
| | | | |
| virtual bool callRead( Message& toSend , Message& response ) = 0; | | virtual bool callRead( Message& toSend , Message& response ) = 0; | |
| // virtual bool callWrite( Message& toSend , Message& response ) =
0; // TODO: add this if needed | | // virtual bool callWrite( Message& toSend , Message& response ) =
0; // TODO: add this if needed | |
|
| virtual void say( Message& toSend ) = 0; | | | |
| | | | |
| virtual ConnectionString::ConnectionType type() const = 0; | | virtual ConnectionString::ConnectionType type() const = 0; | |
| | | | |
|
| /** @return true if conn is either equal to or contained in this co
nnection */ | | virtual double getSoTimeout() const = 0; | |
| virtual bool isMember( const DBConnector * conn ) const = 0; | | | |
| }; // DBClientBase | | }; // DBClientBase | |
| | | | |
| class DBClientReplicaSet; | | class DBClientReplicaSet; | |
| | | | |
| class ConnectException : public UserException { | | class ConnectException : public UserException { | |
| public: | | public: | |
| ConnectException(string msg) : UserException(9000,msg) { } | | ConnectException(string msg) : UserException(9000,msg) { } | |
| }; | | }; | |
| | | | |
| /** | | /** | |
| A basic connection to the database. | | A basic connection to the database. | |
| This is the main entry point for talking to a simple Mongo setup | | This is the main entry point for talking to a simple Mongo setup | |
| */ | | */ | |
| class DBClientConnection : public DBClientBase { | | class DBClientConnection : public DBClientBase { | |
|
| DBClientReplicaSet *clientSet; | | | |
| boost::scoped_ptr<MessagingPort> p; | | | |
| boost::scoped_ptr<SockAddr> server; | | | |
| bool failed; // true if some sort of fatal error has ever happened | | | |
| bool autoReconnect; | | | |
| time_t lastReconnectTry; | | | |
| HostAndPort _server; // remember for reconnects | | | |
| string _serverString; | | | |
| int _port; | | | |
| void _checkConnection(); | | | |
| void checkConnection() { if( failed ) _checkConnection(); } | | | |
| map< string, pair<string,string> > authCache; | | | |
| int _timeout; | | | |
| | | | |
| bool _connect( string& errmsg ); | | | |
| public: | | public: | |
|
| | | | |
| /** | | /** | |
| @param _autoReconnect if true, automatically reconnect on a conn
ection failure | | @param _autoReconnect if true, automatically reconnect on a conn
ection failure | |
| @param cp used by DBClientReplicaSet. You do not need to specif
y this parameter | | @param cp used by DBClientReplicaSet. You do not need to specif
y this parameter | |
| @param timeout tcp timeout in seconds - this is for read/write,
not connect. | | @param timeout tcp timeout in seconds - this is for read/write,
not connect. | |
| Connect timeout is fixed, but short, at 5 seconds. | | Connect timeout is fixed, but short, at 5 seconds. | |
| */ | | */ | |
|
| DBClientConnection(bool _autoReconnect=false, DBClientReplicaSet* c
p=0, int timeout=0) : | | DBClientConnection(bool _autoReconnect=false, DBClientReplicaSet* c
p=0, double so_timeout=0) : | |
| clientSet(cp), failed(false), autoReconnect(_autoReconnect)
, lastReconnectTry(0), _timeout(timeout) { } | | clientSet(cp), _failed(false), autoReconnect(_autoReconnect), l
astReconnectTry(0), _so_timeout(so_timeout) { | |
| | | _numConnections++; | |
| | | } | |
| | | | |
| | | virtual ~DBClientConnection() { | |
| | | _numConnections--; | |
| | | } | |
| | | | |
| /** Connect to a Mongo database server. | | /** Connect to a Mongo database server. | |
| | | | |
| If autoReconnect is true, you can try to use the DBClientConnect
ion even when | | If autoReconnect is true, you can try to use the DBClientConnect
ion even when | |
| false was returned -- it will try to connect again. | | false was returned -- it will try to connect again. | |
| | | | |
| @param serverHostname host to connect to. can include port numb
er ( 127.0.0.1 , 127.0.0.1:5555 ) | | @param serverHostname host to connect to. can include port numb
er ( 127.0.0.1 , 127.0.0.1:5555 ) | |
| If you use IPv6 you must add a port number
( ::1:27017 ) | | If you use IPv6 you must add a port number
( ::1:27017 ) | |
| @param errmsg any relevant error message will appended to the st
ring | | @param errmsg any relevant error message will appended to the st
ring | |
| @deprecated please use HostAndPort | | @deprecated please use HostAndPort | |
| @return false if fails to connect. | | @return false if fails to connect. | |
| */ | | */ | |
|
| virtual bool connect(const char * hostname, string& errmsg){ | | virtual bool connect(const char * hostname, string& errmsg) { | |
| // TODO: remove this method | | // TODO: remove this method | |
| HostAndPort t( hostname ); | | HostAndPort t( hostname ); | |
| return connect( t , errmsg ); | | return connect( t , errmsg ); | |
| } | | } | |
| | | | |
| /** Connect to a Mongo database server. | | /** Connect to a Mongo database server. | |
| | | | |
| If autoReconnect is true, you can try to use the DBClientConnect
ion even when | | If autoReconnect is true, you can try to use the DBClientConnect
ion even when | |
| false was returned -- it will try to connect again. | | false was returned -- it will try to connect again. | |
| | | | |
| | | | |
| skipping to change at line 862 | | skipping to change at line 884 | |
| } | | } | |
| | | | |
| virtual bool auth(const string &dbname, const string &username, con
st string &pwd, string& errmsg, bool digestPassword = true); | | virtual bool auth(const string &dbname, const string &username, con
st string &pwd, string& errmsg, bool digestPassword = true); | |
| | | | |
| virtual auto_ptr<DBClientCursor> query(const string &ns, Query quer
y=Query(), int nToReturn = 0, int nToSkip = 0, | | virtual auto_ptr<DBClientCursor> query(const string &ns, Query quer
y=Query(), int nToReturn = 0, int nToSkip = 0, | |
| const BSONObj *fieldsToRetur
n = 0, int queryOptions = 0 , int batchSize = 0 ) { | | const BSONObj *fieldsToRetur
n = 0, int queryOptions = 0 , int batchSize = 0 ) { | |
| checkConnection(); | | checkConnection(); | |
| return DBClientBase::query( ns, query, nToReturn, nToSkip, fiel
dsToReturn, queryOptions , batchSize ); | | return DBClientBase::query( ns, query, nToReturn, nToSkip, fiel
dsToReturn, queryOptions , batchSize ); | |
| } | | } | |
| | | | |
|
| /** uses QueryOption_Exhaust | | /** Uses QueryOption_Exhaust | |
| use DBClientCursorBatchIterator if you want to do items in larg
e blocks, perhpas to avoid granular locking and such. | | Exhaust mode sends back all data queries as fast as possible, w
ith no back-and-for for OP_GETMORE. If you are certain | |
| | | you will exhaust the query, it could be useful. | |
| | | | |
| | | Use DBClientCursorBatchIterator version if you want to do items
in large blocks, perhaps to avoid granular locking and such. | |
| */ | | */ | |
| unsigned long long query( boost::function<void(const BSONObj&)> f,
const string& ns, Query query, const BSONObj *fieldsToReturn = 0, int query
Options = 0); | | unsigned long long query( boost::function<void(const BSONObj&)> f,
const string& ns, Query query, const BSONObj *fieldsToReturn = 0, int query
Options = 0); | |
| unsigned long long query( boost::function<void(DBClientCursorBatchI
terator&)> f, const string& ns, Query query, const BSONObj *fieldsToReturn
= 0, int queryOptions = 0); | | unsigned long long query( boost::function<void(DBClientCursorBatchI
terator&)> f, const string& ns, Query query, const BSONObj *fieldsToReturn
= 0, int queryOptions = 0); | |
| | | | |
| /** | | /** | |
| @return true if this connection is currently in a failed state.
When autoreconnect is on, | | @return true if this connection is currently in a failed state.
When autoreconnect is on, | |
| a connection will transition back to an ok state after r
econnecting. | | a connection will transition back to an ok state after r
econnecting. | |
| */ | | */ | |
|
| bool isFailed() const { | | bool isFailed() const { return _failed; } | |
| return failed; | | | |
| } | | | |
| | | | |
|
| MessagingPort& port() { | | MessagingPort& port() { assert(p); return *p; } | |
| return *p; | | | |
| } | | | |
| | | | |
| string toStringLong() const { | | string toStringLong() const { | |
| stringstream ss; | | stringstream ss; | |
| ss << _serverString; | | ss << _serverString; | |
|
| if ( failed ) ss << " failed"; | | if ( _failed ) ss << " failed"; | |
| return ss.str(); | | return ss.str(); | |
| } | | } | |
| | | | |
| /** Returns the address of the server */ | | /** Returns the address of the server */ | |
|
| string toString() { | | string toString() { return _serverString; } | |
| return _serverString; | | | |
| } | | | |
| | | | |
|
| string getServerAddress() const { | | string getServerAddress() const { return _serverString; } | |
| return _serverString; | | | |
| } | | | |
| | | | |
| virtual void killCursor( long long cursorID ); | | virtual void killCursor( long long cursorID ); | |
|
| | | virtual bool callRead( Message& toSend , Message& response ) { retu
rn call( toSend , response ); } | |
| virtual bool callRead( Message& toSend , Message& response ){ | | virtual void say( Message &toSend, bool isRetry = false ); | |
| return call( toSend , response ); | | virtual bool recv( Message& m ); | |
| } | | virtual void checkResponse( const char *data, int nReturned, bool*
retry = NULL, string* host = NULL ); | |
| | | virtual bool call( Message &toSend, Message &response, bool assertO
k = true , string * actualServer = 0 ); | |
| virtual void say( Message &toSend ); | | | |
| virtual bool call( Message &toSend, Message &response, bool assertO
k = true ); | | | |
| | | | |
| virtual ConnectionString::ConnectionType type() const { return Conn
ectionString::MASTER; } | | virtual ConnectionString::ConnectionType type() const { return Conn
ectionString::MASTER; } | |
|
| | | void setSoTimeout(double to) { _so_timeout = to; } | |
| | | double getSoTimeout() const { return _so_timeout; } | |
| | | | |
|
| virtual bool isMember( const DBConnector * conn ) const { return th
is == conn; }; | | virtual bool lazySupported() const { return true; } | |
| | | | |
|
| virtual void checkResponse( const char *data, int nReturned ); | | static int getNumConnections() { | |
| | | return _numConnections; | |
| | | } | |
| | | | |
| | | static void setLazyKillCursor( bool lazy ) { _lazyKillCursor = lazy
; } | |
| | | static bool getLazyKillCursor() { return _lazyKillCursor; } | |
| | | | |
| protected: | | protected: | |
| friend class SyncClusterConnection; | | friend class SyncClusterConnection; | |
|
| virtual void recv( Message& m ); | | | |
| virtual void sayPiggyBack( Message &toSend ); | | virtual void sayPiggyBack( Message &toSend ); | |
| | | | |
|
| }; | | DBClientReplicaSet *clientSet; | |
| | | boost::scoped_ptr<MessagingPort> p; | |
| /** Use this class to connect to a replica set of servers. The class w
ill manage | | boost::scoped_ptr<SockAddr> server; | |
| checking for which server in a replica set is master, and do failove
r automatically. | | bool _failed; | |
| | | const bool autoReconnect; | |
| This can also be used to connect to replica pairs since pairs are a
subset of sets | | time_t lastReconnectTry; | |
| | | HostAndPort _server; // remember for reconnects | |
| On a failover situation, expect at least one operation to return
an error (throw | | string _serverString; | |
| an exception) before the failover is complete. Operations are no
t retried. | | void _checkConnection(); | |
| */ | | | |
| class DBClientReplicaSet : public DBClientBase { | | | |
| string _name; | | | |
| DBClientConnection * _currentMaster; | | | |
| vector<HostAndPort> _servers; | | | |
| vector<DBClientConnection*> _conns; | | | |
| | | | |
| void _checkMaster(); | | | |
| DBClientConnection * checkMaster(); | | | |
| | | | |
| public: | | | |
| /** Call connect() after constructing. autoReconnect is always on f
or DBClientReplicaSet connections. */ | | | |
| DBClientReplicaSet( const string& name , const vector<HostAndPort>&
servers ); | | | |
| virtual ~DBClientReplicaSet(); | | | |
| | | | |
| /** Returns false if nomember of the set were reachable, or neither
is | | | |
| master, although, | | | |
| when false returned, you can still try to use this connection ob
ject, it will | | | |
| try reconnects. | | | |
| */ | | | |
| bool connect(); | | | |
| | | | |
| /** Authorize. Authorizes all nodes as needed | | | |
| */ | | | |
| virtual bool auth(const string &dbname, const string &username, con
st string &pwd, string& errmsg, bool digestPassword = true ); | | | |
| | | | |
| /** throws userassertion "no master found" */ | | | |
| virtual | | | |
| auto_ptr<DBClientCursor> query(const string &ns, Query query, int n
ToReturn = 0, int nToSkip = 0, | | | |
| const BSONObj *fieldsToReturn = 0, i
nt queryOptions = 0 , int batchSize = 0 ); | | | |
| | | | |
| /** throws userassertion "no master found" */ | | | |
| virtual | | | |
| BSONObj findOne(const string &ns, const Query& query, const BSONObj
*fieldsToReturn = 0, int queryOptions = 0); | | | |
| | | | |
| /** insert */ | | | |
| virtual void insert( const string &ns , BSONObj obj ) { | | | |
| checkMaster()->insert(ns, obj); | | | |
| } | | | |
| | | | |
| /** insert multiple objects. Note that single object insert is asy
nchronous, so this version | | | |
| is only nominally faster and not worth a special effort to try
to use. */ | | | |
| virtual void insert( const string &ns, const vector< BSONObj >& v )
{ | | | |
| checkMaster()->insert(ns, v); | | | |
| } | | | |
| | | | |
| /** remove */ | | | |
| virtual void remove( const string &ns , Query obj , bool justOne =
0 ) { | | | |
| checkMaster()->remove(ns, obj, justOne); | | | |
| } | | | |
| | | | |
| /** update */ | | | |
| virtual void update( const string &ns , Query query , BSONObj obj ,
bool upsert = 0 , bool multi = 0 ) { | | | |
| return checkMaster()->update(ns, query, obj, upsert,multi); | | | |
| } | | | |
| | | | |
| virtual void killCursor( long long cursorID ){ | | | |
| checkMaster()->killCursor( cursorID ); | | | |
| } | | | |
| | | | |
| string toString(); | | | |
| | | | |
| /* this is the callback from our underlying connections to notify u
s that we got a "not master" error. | | | |
| */ | | | |
| void isntMaster() { | | | |
| _currentMaster = 0; | | | |
| } | | | |
| | | | |
| string getServerAddress() const; | | | |
| | | | |
| DBClientConnection& masterConn(); | | | |
| DBClientConnection& slaveConn(); | | | |
| | | | |
| virtual bool call( Message &toSend, Message &response, bool assertO
k=true ) { return checkMaster()->call( toSend , response , assertOk ); } | | | |
| virtual void say( Message &toSend ) { checkMaster()->say( toSend );
} | | | |
| virtual bool callRead( Message& toSend , Message& response ){ retur
n checkMaster()->callRead( toSend , response ); } | | | |
| | | | |
| virtual ConnectionString::ConnectionType type() const { return Conn
ectionString::SET; } | | | |
| | | | |
|
| virtual bool isMember( const DBConnector * conn ) const; | | // throws SocketException if in failed state and not reconnecting o
r if waiting to reconnect | |
| | | void checkConnection() { if( _failed ) _checkConnection(); } | |
| | | | |
|
| virtual void checkResponse( const char *data, int nReturned ) { che
ckMaster()->checkResponse( data , nReturned ); } | | map< string, pair<string,string> > authCache; | |
| | | double _so_timeout; | |
| | | bool _connect( string& errmsg ); | |
| | | | |
|
| protected: | | static AtomicUInt _numConnections; | |
| virtual void sayPiggyBack( Message &toSend ) { checkMaster()->say(
toSend ); } | | static bool _lazyKillCursor; // lazy means we piggy back kill curso
rs on next op | |
| | | | |
|
| bool isFailed() const { | | #ifdef MONGO_SSL | |
| return _currentMaster == 0 || _currentMaster->isFailed(); | | static SSLManager* sslManager(); | |
| } | | static SSLManager* _sslManager; | |
| | | #endif | |
| }; | | }; | |
| | | | |
| /** pings server to check if it's up | | /** pings server to check if it's up | |
| */ | | */ | |
| bool serverAlive( const string &uri ); | | bool serverAlive( const string &uri ); | |
| | | | |
| DBClientBase * createDirectClient(); | | DBClientBase * createDirectClient(); | |
| | | | |
|
| | | BSONElement getErrField( const BSONObj& result ); | |
| | | bool hasErrField( const BSONObj& result ); | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
| #include "dbclientcursor.h" | | #include "dbclientcursor.h" | |
|
| | | #include "dbclient_rs.h" | |
| #include "undef_macros.h" | | #include "undef_macros.h" | |
| | | | |
End of changes. 75 change blocks. |
| 241 lines changed or deleted | | 191 lines changed or added | |
|
| engine.h | | engine.h | |
| | | | |
| skipping to change at line 30 | | skipping to change at line 30 | |
| #include "../pch.h" | | #include "../pch.h" | |
| #include "../db/jsobj.h" | | #include "../db/jsobj.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| struct JSFile { | | struct JSFile { | |
| const char* name; | | const char* name; | |
| const StringData& source; | | const StringData& source; | |
| }; | | }; | |
| | | | |
|
| namespace JSFiles { | | | |
| extern const JSFile collection; | | | |
| extern const JSFile db; | | | |
| extern const JSFile mongo; | | | |
| extern const JSFile mr; | | | |
| extern const JSFile query; | | | |
| extern const JSFile servers; | | | |
| extern const JSFile utils; | | | |
| } | | | |
| | | | |
| typedef unsigned long long ScriptingFunction; | | typedef unsigned long long ScriptingFunction; | |
|
| typedef BSONObj (*NativeFunction) ( const BSONObj &args ); | | typedef BSONObj (*NativeFunction) ( const BSONObj &args, void* data ); | |
| | | | |
| class Scope : boost::noncopyable { | | class Scope : boost::noncopyable { | |
| public: | | public: | |
| Scope(); | | Scope(); | |
| virtual ~Scope(); | | virtual ~Scope(); | |
| | | | |
| virtual void reset() = 0; | | virtual void reset() = 0; | |
|
| virtual void init( BSONObj * data ) = 0; | | virtual void init( const BSONObj * data ) = 0; | |
| void init( const char * data ){ | | void init( const char * data ) { | |
| BSONObj o( data , 0 ); | | BSONObj o( data ); | |
| init( &o ); | | init( &o ); | |
| } | | } | |
| | | | |
| virtual void localConnect( const char * dbName ) = 0; | | virtual void localConnect( const char * dbName ) = 0; | |
| virtual void externalSetup() = 0; | | virtual void externalSetup() = 0; | |
| | | | |
| class NoDBAccess { | | class NoDBAccess { | |
| Scope * _s; | | Scope * _s; | |
| public: | | public: | |
|
| NoDBAccess( Scope * s ){ | | NoDBAccess( Scope * s ) { | |
| _s = s; | | _s = s; | |
| } | | } | |
|
| ~NoDBAccess(){ | | ~NoDBAccess() { | |
| _s->rename( "____db____" , "db" ); | | _s->rename( "____db____" , "db" ); | |
| } | | } | |
| }; | | }; | |
|
| NoDBAccess disableDBAccess( const char * why ){ | | NoDBAccess disableDBAccess( const char * why ) { | |
| rename( "db" , "____db____" ); | | rename( "db" , "____db____" ); | |
| return NoDBAccess( this ); | | return NoDBAccess( this ); | |
| } | | } | |
| | | | |
| virtual double getNumber( const char *field ) = 0; | | virtual double getNumber( const char *field ) = 0; | |
|
| virtual int getNumberInt( const char *field ){ return (int)getNumbe
r( field ); } | | virtual int getNumberInt( const char *field ) { return (int)getNumb
er( field ); } | |
| virtual long long getNumberLongLong( const char *field ){ return (l
ong long)getNumber( field ); } | | virtual long long getNumberLongLong( const char *field ) { return (
long long)getNumber( field ); } | |
| virtual string getString( const char *field ) = 0; | | virtual string getString( const char *field ) = 0; | |
| virtual bool getBoolean( const char *field ) = 0; | | virtual bool getBoolean( const char *field ) = 0; | |
| virtual BSONObj getObject( const char *field ) = 0; | | virtual BSONObj getObject( const char *field ) = 0; | |
| | | | |
| virtual int type( const char *field ) = 0; | | virtual int type( const char *field ) = 0; | |
| | | | |
|
| void append( BSONObjBuilder & builder , const char * fieldName , co
nst char * scopeName ); | | virtual void append( BSONObjBuilder & builder , const char * fieldN
ame , const char * scopeName ); | |
| | | | |
| virtual void setElement( const char *field , const BSONElement& e )
= 0; | | virtual void setElement( const char *field , const BSONElement& e )
= 0; | |
| virtual void setNumber( const char *field , double val ) = 0; | | virtual void setNumber( const char *field , double val ) = 0; | |
| virtual void setString( const char *field , const char * val ) = 0; | | virtual void setString( const char *field , const char * val ) = 0; | |
| virtual void setObject( const char *field , const BSONObj& obj , bo
ol readOnly=true ) = 0; | | virtual void setObject( const char *field , const BSONObj& obj , bo
ol readOnly=true ) = 0; | |
| virtual void setBoolean( const char *field , bool val ) = 0; | | virtual void setBoolean( const char *field , bool val ) = 0; | |
|
| virtual void setThis( const BSONObj * obj ) = 0; | | virtual void setFunction( const char *field , const char * code ) =
0; | |
| | | // virtual void setThis( const BSONObj * obj ) = 0; | |
| | | | |
| virtual ScriptingFunction createFunction( const char * code ); | | virtual ScriptingFunction createFunction( const char * code ); | |
| | | | |
| virtual void rename( const char * from , const char * to ) = 0; | | virtual void rename( const char * from , const char * to ) = 0; | |
| /** | | /** | |
| * @return 0 on success | | * @return 0 on success | |
| */ | | */ | |
|
| virtual int invoke( ScriptingFunction func , const BSONObj& args, i
nt timeoutMs = 0 , bool ignoreReturn = false ) = 0; | | virtual int invoke( ScriptingFunction func , const BSONObj* args, c
onst BSONObj* recv, int timeoutMs = 0 , bool ignoreReturn = false, bool rea
dOnlyArgs = false, bool readOnlyRecv = false ) = 0; | |
| void invokeSafe( ScriptingFunction func , const BSONObj& args, int
timeoutMs = 0 ){ | | void invokeSafe( ScriptingFunction func , const BSONObj* args, cons
t BSONObj* recv, int timeoutMs = 0, bool readOnlyArgs = false, bool readOnl
yRecv = false ) { | |
| int res = invoke( func , args , timeoutMs ); | | int res = invoke( func , args , recv, timeoutMs, readOnlyArgs,
readOnlyRecv ); | |
| if ( res == 0 ) | | if ( res == 0 ) | |
| return; | | return; | |
| throw UserException( 9004 , (string)"invoke failed: " + getErro
r() ); | | throw UserException( 9004 , (string)"invoke failed: " + getErro
r() ); | |
| } | | } | |
| virtual string getError() = 0; | | virtual string getError() = 0; | |
| | | | |
|
| int invoke( const char* code , const BSONObj& args, int timeoutMs =
0 ); | | int invoke( const char* code , const BSONObj* args, const BSONObj*
recv, int timeoutMs = 0 ); | |
| void invokeSafe( const char* code , const BSONObj& args, int timeou
tMs = 0 ){ | | void invokeSafe( const char* code , const BSONObj* args, const BSON
Obj* recv, int timeoutMs = 0 ) { | |
| if ( invoke( code , args , timeoutMs ) == 0 ) | | if ( invoke( code , args , recv, timeoutMs ) == 0 ) | |
| return; | | return; | |
| throw UserException( 9005 , (string)"invoke failed: " + getErro
r() ); | | throw UserException( 9005 , (string)"invoke failed: " + getErro
r() ); | |
| } | | } | |
| | | | |
| virtual bool exec( const StringData& code , const string& name , bo
ol printResult , bool reportError , bool assertOnError, int timeoutMs = 0 )
= 0; | | virtual bool exec( const StringData& code , const string& name , bo
ol printResult , bool reportError , bool assertOnError, int timeoutMs = 0 )
= 0; | |
|
| virtual void execSetup( const StringData& code , const string& name
= "setup" ){ | | virtual void execSetup( const StringData& code , const string& name
= "setup" ) { | |
| exec( code , name , false , true , true , 0 ); | | exec( code , name , false , true , true , 0 ); | |
| } | | } | |
| | | | |
|
| void execSetup( const JSFile& file){ | | void execSetup( const JSFile& file) { | |
| execSetup(file.source, file.name); | | execSetup(file.source, file.name); | |
| } | | } | |
| | | | |
|
| void execCoreFiles(){ | | void execCoreFiles(); | |
| // keeping same order as in SConstruct | | | |
| execSetup(JSFiles::utils); | | | |
| execSetup(JSFiles::db); | | | |
| execSetup(JSFiles::mongo); | | | |
| execSetup(JSFiles::mr); | | | |
| execSetup(JSFiles::query); | | | |
| execSetup(JSFiles::collection); | | | |
| } | | | |
| | | | |
| virtual bool execFile( const string& filename , bool printResult ,
bool reportError , bool assertOnError, int timeoutMs = 0 ); | | virtual bool execFile( const string& filename , bool printResult ,
bool reportError , bool assertOnError, int timeoutMs = 0 ); | |
| | | | |
|
| virtual void injectNative( const char *field, NativeFunction func )
= 0; | | virtual void injectNative( const char *field, NativeFunction func,
void* data = 0 ) = 0; | |
| | | | |
| virtual void gc() = 0; | | virtual void gc() = 0; | |
| | | | |
| void loadStored( bool ignoreNotConnected = false ); | | void loadStored( bool ignoreNotConnected = false ); | |
| | | | |
| /** | | /** | |
| if any changes are made to .system.js, call this | | if any changes are made to .system.js, call this | |
| right now its just global - slightly inefficient, but a lot simple
r | | right now its just global - slightly inefficient, but a lot simple
r | |
| */ | | */ | |
| static void storedFuncMod(); | | static void storedFuncMod(); | |
| | | | |
|
| static int getNumScopes(){ | | static int getNumScopes() { | |
| return _numScopes; | | return _numScopes; | |
| } | | } | |
| | | | |
| static void validateObjectIdString( const string &str ); | | static void validateObjectIdString( const string &str ); | |
| | | | |
| protected: | | protected: | |
| | | | |
| virtual ScriptingFunction _createFunction( const char * code ) = 0; | | virtual ScriptingFunction _createFunction( const char * code ) = 0; | |
| | | | |
| string _localDBName; | | string _localDBName; | |
| | | | |
| skipping to change at line 201 | | skipping to change at line 184 | |
| struct Unlocker { virtual ~Unlocker() {} }; | | struct Unlocker { virtual ~Unlocker() {} }; | |
| virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< U
nlocker >( new Unlocker ); } | | virtual auto_ptr<Unlocker> newThreadUnlocker() { return auto_ptr< U
nlocker >( new Unlocker ); } | |
| | | | |
| void setScopeInitCallback( void ( *func )( Scope & ) ) { _scopeInit
Callback = func; } | | void setScopeInitCallback( void ( *func )( Scope & ) ) { _scopeInit
Callback = func; } | |
| static void setConnectCallback( void ( *func )( DBClientWithCommand
s& ) ) { _connectCallback = func; } | | static void setConnectCallback( void ( *func )( DBClientWithCommand
s& ) ) { _connectCallback = func; } | |
| static void runConnectCallback( DBClientWithCommands &c ) { | | static void runConnectCallback( DBClientWithCommands &c ) { | |
| if ( _connectCallback ) | | if ( _connectCallback ) | |
| _connectCallback( c ); | | _connectCallback( c ); | |
| } | | } | |
| | | | |
|
| | | // engine implementation may either respond to interrupt events or | |
| | | // poll for interrupts | |
| | | | |
| | | // the interrupt functions must not wait indefinitely on a lock | |
| | | virtual void interrupt( unsigned opSpec ) {} | |
| | | virtual void interruptAll() {} | |
| | | | |
| | | static void setGetInterruptSpecCallback( unsigned ( *func )() ) { _
getInterruptSpecCallback = func; } | |
| | | static bool haveGetInterruptSpecCallback() { return _getInterruptSp
ecCallback; } | |
| | | static unsigned getInterruptSpec() { | |
| | | massert( 13474, "no _getInterruptSpecCallback", _getInterruptSp
ecCallback ); | |
| | | return _getInterruptSpecCallback(); | |
| | | } | |
| | | | |
| | | static void setCheckInterruptCallback( const char * ( *func )() ) {
_checkInterruptCallback = func; } | |
| | | static bool haveCheckInterruptCallback() { return _checkInterruptCa
llback; } | |
| | | static const char * checkInterrupt() { | |
| | | return _checkInterruptCallback ? _checkInterruptCallback() : ""
; | |
| | | } | |
| | | static bool interrupted() { | |
| | | const char *r = checkInterrupt(); | |
| | | return r && r[ 0 ]; | |
| | | } | |
| | | | |
| protected: | | protected: | |
| virtual Scope * createScope() = 0; | | virtual Scope * createScope() = 0; | |
| | | | |
| private: | | private: | |
| void ( *_scopeInitCallback )( Scope & ); | | void ( *_scopeInitCallback )( Scope & ); | |
| static void ( *_connectCallback )( DBClientWithCommands & ); | | static void ( *_connectCallback )( DBClientWithCommands & ); | |
|
| | | static const char * ( *_checkInterruptCallback )(); | |
| | | static unsigned ( *_getInterruptSpecCallback )(); | |
| }; | | }; | |
| | | | |
| bool hasJSReturn( const string& s ); | | bool hasJSReturn( const string& s ); | |
| | | | |
|
| | | const char * jsSkipWhiteSpace( const char * raw ); | |
| | | | |
| extern ScriptEngine * globalScriptEngine; | | extern ScriptEngine * globalScriptEngine; | |
| } | | } | |
| | | | |
End of changes. 19 change blocks. |
| 40 lines changed or deleted | | 51 lines changed or added | |
|
| goodies.h | | goodies.h | |
| // @file goodies.h | | // @file goodies.h | |
|
| // miscellaneous junk | | // miscellaneous | |
| | | | |
| /* Copyright 2009 10gen Inc. | | /* Copyright 2009 10gen Inc. | |
| * | | * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | | * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | | * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | | * You may obtain a copy of the License at | |
| * | | * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | | * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | | * | |
| * Unless required by applicable law or agreed to in writing, software | | * Unless required by applicable law or agreed to in writing, software | |
| | | | |
| skipping to change at line 26 | | skipping to change at line 26 | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../bson/util/misc.h" | | #include "../bson/util/misc.h" | |
| #include "concurrency/mutex.h" | | #include "concurrency/mutex.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| void setThreadName(const char * name); | | /* @return a dump of the buffer as hex byte ascii output */ | |
| | | string hexdump(const char *data, unsigned len); | |
| | | | |
| | | /** | |
| | | * @return if this name has an increasing counter associated, return th
e value | |
| | | * otherwise 0 | |
| | | */ | |
| | | unsigned setThreadName(const char * name); | |
| string getThreadName(); | | string getThreadName(); | |
| | | | |
| template<class T> | | template<class T> | |
| inline string ToString(const T& t) { | | inline string ToString(const T& t) { | |
| stringstream s; | | stringstream s; | |
| s << t; | | s << t; | |
| return s.str(); | | return s.str(); | |
| } | | } | |
| | | | |
| #if !defined(_WIN32) && !defined(NOEXECINFO) && !defined(__freebsd__) && !d
efined(__openbsd__) && !defined(__sun__) | | #if !defined(_WIN32) && !defined(NOEXECINFO) && !defined(__freebsd__) && !d
efined(__openbsd__) && !defined(__sun__) | |
| | | | |
| skipping to change at line 52 | | skipping to change at line 59 | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| inline pthread_t GetCurrentThreadId() { | | inline pthread_t GetCurrentThreadId() { | |
| return pthread_self(); | | return pthread_self(); | |
| } | | } | |
| | | | |
| /* use "addr2line -CFe <exe>" to parse. */ | | /* use "addr2line -CFe <exe>" to parse. */ | |
| inline void printStackTrace( ostream &o = cout ) { | | inline void printStackTrace( ostream &o = cout ) { | |
| void *b[20]; | | void *b[20]; | |
|
| size_t size; | | | |
| size_t i; | | | |
| | | | |
|
| size = backtrace(b, 20); | | int size = backtrace(b, 20); | |
| for (i = 0; i < size; i++) | | for (int i = 0; i < size; i++) | |
| o << hex << b[i] << dec << ' '; | | o << hex << b[i] << dec << ' '; | |
| o << endl; | | o << endl; | |
| | | | |
| char **strings; | | char **strings; | |
| | | | |
| strings = backtrace_symbols(b, size); | | strings = backtrace_symbols(b, size); | |
|
| for (i = 0; i < size; i++) | | for (int i = 0; i < size; i++) | |
| o << ' ' << strings[i] << '\n'; | | o << ' ' << strings[i] << '\n'; | |
| o.flush(); | | o.flush(); | |
| free (strings); | | free (strings); | |
| } | | } | |
| #else | | #else | |
| inline void printStackTrace( ostream &o = cout ) { } | | inline void printStackTrace( ostream &o = cout ) { } | |
| #endif | | #endif | |
| | | | |
|
| /* set to TRUE if we are exiting */ | | | |
| extern bool goingAway; | | | |
| | | | |
| /* find the multimap member which matches a particular key and value. | | | |
| | | | |
| note this can be slow if there are a lot with the same key. | | | |
| */ | | | |
| template<class C,class K,class V> inline typename C::iterator kv_find(C
& c, const K& k,const V& v) { | | | |
| pair<typename C::iterator,typename C::iterator> p = c.equal_range(k
); | | | |
| | | | |
| for ( typename C::iterator it=p.first; it!=p.second; ++it) | | | |
| if ( it->second == v ) | | | |
| return it; | | | |
| | | | |
| return c.end(); | | | |
| } | | | |
| | | | |
| bool isPrime(int n); | | bool isPrime(int n); | |
| int nextPrime(int n); | | int nextPrime(int n); | |
| | | | |
| inline void dumpmemory(const char *data, int len) { | | inline void dumpmemory(const char *data, int len) { | |
| if ( len > 1024 ) | | if ( len > 1024 ) | |
| len = 1024; | | len = 1024; | |
| try { | | try { | |
| const char *q = data; | | const char *q = data; | |
| const char *p = q; | | const char *p = q; | |
| while ( len > 0 ) { | | while ( len > 0 ) { | |
| | | | |
| skipping to change at line 113 | | skipping to change at line 101 | |
| cout << '.'; | | cout << '.'; | |
| p++; | | p++; | |
| } | | } | |
| cout << " "; | | cout << " "; | |
| p -= 16; | | p -= 16; | |
| for ( int i = 0; i < 16; i++ ) | | for ( int i = 0; i < 16; i++ ) | |
| cout << (unsigned) ((unsigned char)*p++) << ' '; | | cout << (unsigned) ((unsigned char)*p++) << ' '; | |
| cout << endl; | | cout << endl; | |
| len -= 16; | | len -= 16; | |
| } | | } | |
|
| } catch (...) { | | } | |
| | | catch (...) { | |
| } | | } | |
| } | | } | |
| | | | |
| // PRINT(2+2); prints "2+2: 4" | | // PRINT(2+2); prints "2+2: 4" | |
| #define MONGO_PRINT(x) cout << #x ": " << (x) << endl | | #define MONGO_PRINT(x) cout << #x ": " << (x) << endl | |
| #define PRINT MONGO_PRINT | | #define PRINT MONGO_PRINT | |
| // PRINTFL; prints file:line | | // PRINTFL; prints file:line | |
| #define MONGO_PRINTFL cout << __FILE__ ":" << __LINE__ << endl | | #define MONGO_PRINTFL cout << __FILE__ ":" << __LINE__ << endl | |
| #define PRINTFL MONGO_PRINTFL | | #define PRINTFL MONGO_PRINTFL | |
|
| | | #define MONGO_FLOG log() << __FILE__ ":" << __LINE__ << endl | |
| | | #define FLOG MONGO_FLOG | |
| | | | |
| #undef assert | | #undef assert | |
| #define assert MONGO_assert | | #define assert MONGO_assert | |
| | | | |
|
| struct WrappingInt { | | | |
| WrappingInt() { | | | |
| x = 0; | | | |
| } | | | |
| WrappingInt(unsigned z) : x(z) { } | | | |
| unsigned x; | | | |
| operator unsigned() const { | | | |
| return x; | | | |
| } | | | |
| | | | |
| static int diff(unsigned a, unsigned b) { | | | |
| return a-b; | | | |
| } | | | |
| bool operator<=(WrappingInt r) { | | | |
| // platform dependent | | | |
| int df = (r.x - x); | | | |
| return df >= 0; | | | |
| } | | | |
| bool operator>(WrappingInt r) { | | | |
| return !(r<=*this); | | | |
| } | | | |
| }; | | | |
| | | | |
| inline void time_t_to_Struct(time_t t, struct tm * buf , bool local = f
alse ) { | | | |
| #if defined(_WIN32) | | | |
| if ( local ) | | | |
| localtime_s( buf , &t ); | | | |
| else | | | |
| gmtime_s(buf, &t); | | | |
| #else | | | |
| if ( local ) | | | |
| localtime_r(&t, buf); | | | |
| else | | | |
| gmtime_r(&t, buf); | | | |
| #endif | | | |
| } | | | |
| | | | |
| // uses ISO 8601 dates without trailing Z | | | |
| // colonsOk should be false when creating filenames | | | |
| inline string terseCurrentTime(bool colonsOk=true){ | | | |
| struct tm t; | | | |
| time_t_to_Struct( time(0) , &t ); | | | |
| | | | |
| const char* fmt = (colonsOk ? "%Y-%m-%dT%H:%M:%S" : "%Y-%m-%dT%H-%M
-%S"); | | | |
| char buf[32]; | | | |
| assert(strftime(buf, sizeof(buf), fmt, &t) == 19); | | | |
| return buf; | | | |
| } | | | |
| | | | |
| #define MONGO_asctime _asctime_not_threadsafe_ | | | |
| #define asctime MONGO_asctime | | | |
| #define MONGO_gmtime _gmtime_not_threadsafe_ | | | |
| #define gmtime MONGO_gmtime | | | |
| #define MONGO_localtime _localtime_not_threadsafe_ | | | |
| #define localtime MONGO_localtime | | | |
| #define MONGO_ctime _ctime_is_not_threadsafe_ | | | |
| #define ctime MONGO_ctime | | | |
| | | | |
| #if defined(_WIN32) || defined(__sunos__) | | | |
| inline void sleepsecs(int s) { | | | |
| boost::xtime xt; | | | |
| boost::xtime_get(&xt, boost::TIME_UTC); | | | |
| xt.sec += s; | | | |
| boost::thread::sleep(xt); | | | |
| } | | | |
| inline void sleepmillis(long long s) { | | | |
| boost::xtime xt; | | | |
| boost::xtime_get(&xt, boost::TIME_UTC); | | | |
| xt.sec += (int)( s / 1000 ); | | | |
| xt.nsec += (int)(( s % 1000 ) * 1000000); | | | |
| if ( xt.nsec >= 1000000000 ) { | | | |
| xt.nsec -= 1000000000; | | | |
| xt.sec++; | | | |
| } | | | |
| boost::thread::sleep(xt); | | | |
| } | | | |
| inline void sleepmicros(long long s) { | | | |
| if ( s <= 0 ) | | | |
| return; | | | |
| boost::xtime xt; | | | |
| boost::xtime_get(&xt, boost::TIME_UTC); | | | |
| xt.sec += (int)( s / 1000000 ); | | | |
| xt.nsec += (int)(( s % 1000000 ) * 1000); | | | |
| if ( xt.nsec >= 1000000000 ) { | | | |
| xt.nsec -= 1000000000; | | | |
| xt.sec++; | | | |
| } | | | |
| boost::thread::sleep(xt); | | | |
| } | | | |
| #else | | | |
| inline void sleepsecs(int s) { | | | |
| struct timespec t; | | | |
| t.tv_sec = s; | | | |
| t.tv_nsec = 0; | | | |
| if ( nanosleep( &t , 0 ) ){ | | | |
| cout << "nanosleep failed" << endl; | | | |
| } | | | |
| } | | | |
| inline void sleepmicros(long long s) { | | | |
| if ( s <= 0 ) | | | |
| return; | | | |
| struct timespec t; | | | |
| t.tv_sec = (int)(s / 1000000); | | | |
| t.tv_nsec = 1000 * ( s % 1000000 ); | | | |
| struct timespec out; | | | |
| if ( nanosleep( &t , &out ) ){ | | | |
| cout << "nanosleep failed" << endl; | | | |
| } | | | |
| } | | | |
| inline void sleepmillis(long long s) { | | | |
| sleepmicros( s * 1000 ); | | | |
| } | | | |
| #endif | | | |
| | | | |
| // note this wraps | | | |
| inline int tdiff(unsigned told, unsigned tnew) { | | | |
| return WrappingInt::diff(tnew, told); | | | |
| } | | | |
| inline unsigned curTimeMillis() { | | | |
| boost::xtime xt; | | | |
| boost::xtime_get(&xt, boost::TIME_UTC); | | | |
| unsigned t = xt.nsec / 1000000; | | | |
| return (xt.sec & 0xfffff) * 1000 + t; | | | |
| } | | | |
| | | | |
| inline Date_t jsTime() { | | | |
| boost::xtime xt; | | | |
| boost::xtime_get(&xt, boost::TIME_UTC); | | | |
| unsigned long long t = xt.nsec / 1000000; | | | |
| return ((unsigned long long) xt.sec * 1000) + t; | | | |
| } | | | |
| | | | |
| inline unsigned long long curTimeMicros64() { | | | |
| boost::xtime xt; | | | |
| boost::xtime_get(&xt, boost::TIME_UTC); | | | |
| unsigned long long t = xt.nsec / 1000; | | | |
| return (((unsigned long long) xt.sec) * 1000000) + t; | | | |
| } | | | |
| | | | |
| // measures up to 1024 seconds. or, 512 seconds with tdiff that is... | | | |
| inline unsigned curTimeMicros() { | | | |
| boost::xtime xt; | | | |
| boost::xtime_get(&xt, boost::TIME_UTC); | | | |
| unsigned t = xt.nsec / 1000; | | | |
| unsigned secs = xt.sec % 1024; | | | |
| return secs*1000000 + t; | | | |
| } | | | |
| | | | |
| // simple scoped timer | | | |
| class Timer { | | | |
| public: | | | |
| Timer() { | | | |
| reset(); | | | |
| } | | | |
| Timer( unsigned long long start ) { | | | |
| old = start; | | | |
| } | | | |
| int seconds() const { | | | |
| return (int)(micros() / 1000000); | | | |
| } | | | |
| int millis() const { | | | |
| return (long)(micros() / 1000); | | | |
| } | | | |
| unsigned long long micros() const { | | | |
| unsigned long long n = curTimeMicros64(); | | | |
| return n - old; | | | |
| } | | | |
| unsigned long long micros(unsigned long long & n) const { // return
s cur time in addition to timer result | | | |
| n = curTimeMicros64(); | | | |
| return n - old; | | | |
| } | | | |
| unsigned long long startTime(){ | | | |
| return old; | | | |
| } | | | |
| void reset() { | | | |
| old = curTimeMicros64(); | | | |
| } | | | |
| private: | | | |
| unsigned long long old; | | | |
| }; | | | |
| | | | |
| /* | | | |
| | | | |
| class DebugMutex : boost::noncopyable { | | | |
| friend class lock; | | | |
| mongo::mutex m; | | | |
| int locked; | | | |
| public: | | | |
| DebugMutex() : locked(0); { } | | | |
| bool isLocked() { return locked; } | | | |
| }; | | | |
| | | | |
| */ | | | |
| | | | |
| //typedef scoped_lock lock; | | | |
| | | | |
| inline bool startsWith(const char *str, const char *prefix) { | | inline bool startsWith(const char *str, const char *prefix) { | |
| size_t l = strlen(prefix); | | size_t l = strlen(prefix); | |
| if ( strlen(str) < l ) return false; | | if ( strlen(str) < l ) return false; | |
| return strncmp(str, prefix, l) == 0; | | return strncmp(str, prefix, l) == 0; | |
| } | | } | |
| inline bool startsWith(string s, string p) { return startsWith(s.c_str(
), p.c_str()); } | | inline bool startsWith(string s, string p) { return startsWith(s.c_str(
), p.c_str()); } | |
| | | | |
| inline bool endsWith(const char *p, const char *suffix) { | | inline bool endsWith(const char *p, const char *suffix) { | |
| size_t a = strlen(p); | | size_t a = strlen(p); | |
| size_t b = strlen(suffix); | | size_t b = strlen(suffix); | |
| | | | |
| skipping to change at line 358 | | skipping to change at line 153 | |
| } | | } | |
| #else | | #else | |
| inline unsigned long fixEndian(unsigned long x) { | | inline unsigned long fixEndian(unsigned long x) { | |
| return swapEndian(x); | | return swapEndian(x); | |
| } | | } | |
| #endif | | #endif | |
| | | | |
| #if !defined(_WIN32) | | #if !defined(_WIN32) | |
| typedef int HANDLE; | | typedef int HANDLE; | |
| inline void strcpy_s(char *dst, unsigned len, const char *src) { | | inline void strcpy_s(char *dst, unsigned len, const char *src) { | |
|
| | | assert( strlen(src) < len ); | |
| strcpy(dst, src); | | strcpy(dst, src); | |
| } | | } | |
| #else | | #else | |
| typedef void *HANDLE; | | typedef void *HANDLE; | |
| #endif | | #endif | |
| | | | |
| /* thread local "value" rather than a pointer | | /* thread local "value" rather than a pointer | |
| good for things which have copy constructors (and the copy construct
or is fast enough) | | good for things which have copy constructors (and the copy construct
or is fast enough) | |
| e.g. | | e.g. | |
| ThreadLocalValue<int> myint; | | ThreadLocalValue<int> myint; | |
| */ | | */ | |
| template<class T> | | template<class T> | |
| class ThreadLocalValue { | | class ThreadLocalValue { | |
| public: | | public: | |
| ThreadLocalValue( T def = 0 ) : _default( def ) { } | | ThreadLocalValue( T def = 0 ) : _default( def ) { } | |
| | | | |
|
| T get() { | | T get() const { | |
| T * val = _val.get(); | | T * val = _val.get(); | |
| if ( val ) | | if ( val ) | |
| return *val; | | return *val; | |
| return _default; | | return _default; | |
| } | | } | |
| | | | |
| void set( const T& i ) { | | void set( const T& i ) { | |
| T *v = _val.get(); | | T *v = _val.get(); | |
| if( v ) { | | if( v ) { | |
| *v = i; | | *v = i; | |
| return; | | return; | |
| } | | } | |
| v = new T(i); | | v = new T(i); | |
| _val.reset( v ); | | _val.reset( v ); | |
| } | | } | |
| | | | |
| private: | | private: | |
|
| T _default; | | | |
| boost::thread_specific_ptr<T> _val; | | boost::thread_specific_ptr<T> _val; | |
|
| | | const T _default; | |
| }; | | }; | |
| | | | |
| class ProgressMeter : boost::noncopyable { | | class ProgressMeter : boost::noncopyable { | |
| public: | | public: | |
|
| ProgressMeter( long long total , int secondsBetween = 3 , int check
Interval = 100 ){ | | ProgressMeter( unsigned long long total , int secondsBetween = 3 ,
int checkInterval = 100 ) { | |
| reset( total , secondsBetween , checkInterval ); | | reset( total , secondsBetween , checkInterval ); | |
| } | | } | |
| | | | |
|
| ProgressMeter(){ | | ProgressMeter() { | |
| _active = 0; | | _active = 0; | |
| } | | } | |
| | | | |
|
| void reset( long long total , int secondsBetween = 3 , int checkInt
erval = 100 ){ | | // typically you do ProgressMeterHolder | |
| | | void reset( unsigned long long total , int secondsBetween = 3 , int
checkInterval = 100 ) { | |
| _total = total; | | _total = total; | |
| _secondsBetween = secondsBetween; | | _secondsBetween = secondsBetween; | |
| _checkInterval = checkInterval; | | _checkInterval = checkInterval; | |
| | | | |
| _done = 0; | | _done = 0; | |
| _hits = 0; | | _hits = 0; | |
| _lastTime = (int)time(0); | | _lastTime = (int)time(0); | |
| | | | |
| _active = 1; | | _active = 1; | |
| } | | } | |
| | | | |
|
| void finished(){ | | void finished() { | |
| _active = 0; | | _active = 0; | |
| } | | } | |
| | | | |
|
| bool isActive(){ | | bool isActive() { | |
| return _active; | | return _active; | |
| } | | } | |
| | | | |
|
| bool hit( int n = 1 ){ | | /** | |
| if ( ! _active ){ | | * @param n how far along we are relative to the total # we set in
CurOp::setMessage | |
| | | * @return if row was printed | |
| | | */ | |
| | | bool hit( int n = 1 ) { | |
| | | if ( ! _active ) { | |
| cout << "warning: hit on in-active ProgressMeter" << endl; | | cout << "warning: hit on in-active ProgressMeter" << endl; | |
|
| | | return false; | |
| } | | } | |
| | | | |
| _done += n; | | _done += n; | |
| _hits++; | | _hits++; | |
| if ( _hits % _checkInterval ) | | if ( _hits % _checkInterval ) | |
| return false; | | return false; | |
| | | | |
| int t = (int) time(0); | | int t = (int) time(0); | |
| if ( t - _lastTime < _secondsBetween ) | | if ( t - _lastTime < _secondsBetween ) | |
| return false; | | return false; | |
| | | | |
|
| if ( _total > 0 ){ | | if ( _total > 0 ) { | |
| int per = (int)( ( (double)_done * 100.0 ) / (double)_total
); | | int per = (int)( ( (double)_done * 100.0 ) / (double)_total
); | |
| cout << "\t\t" << _done << "/" << _total << "\t" << per <<
"%" << endl; | | cout << "\t\t" << _done << "/" << _total << "\t" << per <<
"%" << endl; | |
| } | | } | |
| _lastTime = t; | | _lastTime = t; | |
| return true; | | return true; | |
| } | | } | |
| | | | |
|
| long long done(){ | | void setTotalWhileRunning( unsigned long long total ) { | |
| return _done; | | _total = total; | |
| } | | } | |
| | | | |
|
| long long hits(){ | | unsigned long long done() const { return _done; } | |
| return _hits; | | | |
| } | | unsigned long long hits() const { return _hits; } | |
| | | | |
| | | unsigned long long total() const { return _total; } | |
| | | | |
| string toString() const { | | string toString() const { | |
| if ( ! _active ) | | if ( ! _active ) | |
| return ""; | | return ""; | |
| stringstream buf; | | stringstream buf; | |
| buf << _done << "/" << _total << " " << (_done*100)/_total << "
%"; | | buf << _done << "/" << _total << " " << (_done*100)/_total << "
%"; | |
| return buf.str(); | | return buf.str(); | |
| } | | } | |
| | | | |
| bool operator==( const ProgressMeter& other ) const { | | bool operator==( const ProgressMeter& other ) const { | |
| return this == &other; | | return this == &other; | |
| } | | } | |
| private: | | private: | |
| | | | |
| bool _active; | | bool _active; | |
| | | | |
|
| long long _total; | | unsigned long long _total; | |
| int _secondsBetween; | | int _secondsBetween; | |
| int _checkInterval; | | int _checkInterval; | |
| | | | |
|
| long long _done; | | unsigned long long _done; | |
| long long _hits; | | unsigned long long _hits; | |
| int _lastTime; | | int _lastTime; | |
| }; | | }; | |
| | | | |
|
| | | // e.g.: | |
| | | // CurOp * op = cc().curop(); | |
| | | // ProgressMeterHolder pm( op->setMessage( "index: (1/3) external sort"
, d->stats.nrecords , 10 ) ); | |
| | | // loop { pm.hit(); } | |
| class ProgressMeterHolder : boost::noncopyable { | | class ProgressMeterHolder : boost::noncopyable { | |
| public: | | public: | |
| ProgressMeterHolder( ProgressMeter& pm ) | | ProgressMeterHolder( ProgressMeter& pm ) | |
|
| : _pm( pm ){ | | : _pm( pm ) { | |
| } | | } | |
| | | | |
|
| ~ProgressMeterHolder(){ | | ~ProgressMeterHolder() { | |
| _pm.finished(); | | _pm.finished(); | |
| } | | } | |
| | | | |
|
| ProgressMeter* operator->(){ | | ProgressMeter* operator->() { | |
| return &_pm; | | return &_pm; | |
| } | | } | |
| | | | |
|
| bool hit( int n = 1 ){ | | bool hit( int n = 1 ) { | |
| return _pm.hit( n ); | | return _pm.hit( n ); | |
| } | | } | |
| | | | |
|
| void finished(){ | | void finished() { | |
| _pm.finished(); | | _pm.finished(); | |
| } | | } | |
| | | | |
|
| bool operator==( const ProgressMeter& other ){ | | bool operator==( const ProgressMeter& other ) { | |
| return _pm == other; | | return _pm == other; | |
| } | | } | |
| | | | |
| private: | | private: | |
| ProgressMeter& _pm; | | ProgressMeter& _pm; | |
| }; | | }; | |
| | | | |
| class TicketHolder { | | class TicketHolder { | |
| public: | | public: | |
| TicketHolder( int num ) : _mutex("TicketHolder") { | | TicketHolder( int num ) : _mutex("TicketHolder") { | |
| _outof = num; | | _outof = num; | |
| _num = num; | | _num = num; | |
| } | | } | |
| | | | |
|
| bool tryAcquire(){ | | bool tryAcquire() { | |
| scoped_lock lk( _mutex ); | | scoped_lock lk( _mutex ); | |
|
| if ( _num <= 0 ){ | | if ( _num <= 0 ) { | |
| if ( _num < 0 ){ | | if ( _num < 0 ) { | |
| cerr << "DISASTER! in TicketHolder" << endl; | | cerr << "DISASTER! in TicketHolder" << endl; | |
| } | | } | |
| return false; | | return false; | |
| } | | } | |
| _num--; | | _num--; | |
| return true; | | return true; | |
| } | | } | |
| | | | |
|
| void release(){ | | void release() { | |
| scoped_lock lk( _mutex ); | | scoped_lock lk( _mutex ); | |
| _num++; | | _num++; | |
| } | | } | |
| | | | |
|
| void resize( int newSize ){ | | void resize( int newSize ) { | |
| scoped_lock lk( _mutex ); | | scoped_lock lk( _mutex ); | |
| int used = _outof - _num; | | int used = _outof - _num; | |
|
| if ( used > newSize ){ | | if ( used > newSize ) { | |
| cout << "ERROR: can't resize since we're using (" << used <
< ") more than newSize(" << newSize << ")" << endl; | | cout << "ERROR: can't resize since we're using (" << used <
< ") more than newSize(" << newSize << ")" << endl; | |
| return; | | return; | |
| } | | } | |
| | | | |
| _outof = newSize; | | _outof = newSize; | |
| _num = _outof - used; | | _num = _outof - used; | |
| } | | } | |
| | | | |
| int available() const { | | int available() const { | |
| return _num; | | return _num; | |
| | | | |
| skipping to change at line 564 | | skipping to change at line 372 | |
| int outof() const { return _outof; } | | int outof() const { return _outof; } | |
| | | | |
| private: | | private: | |
| int _outof; | | int _outof; | |
| int _num; | | int _num; | |
| mongo::mutex _mutex; | | mongo::mutex _mutex; | |
| }; | | }; | |
| | | | |
| class TicketHolderReleaser { | | class TicketHolderReleaser { | |
| public: | | public: | |
|
| TicketHolderReleaser( TicketHolder * holder ){ | | TicketHolderReleaser( TicketHolder * holder ) { | |
| _holder = holder; | | _holder = holder; | |
| } | | } | |
| | | | |
|
| ~TicketHolderReleaser(){ | | ~TicketHolderReleaser() { | |
| _holder->release(); | | _holder->release(); | |
| } | | } | |
| private: | | private: | |
| TicketHolder * _holder; | | TicketHolder * _holder; | |
| }; | | }; | |
| | | | |
| /** | | /** | |
| * this is a thread safe string | | * this is a thread safe string | |
| * you will never get a bad pointer, though data may be mungedd | | * you will never get a bad pointer, though data may be mungedd | |
| */ | | */ | |
| class ThreadSafeString { | | class ThreadSafeString { | |
| public: | | public: | |
| ThreadSafeString( size_t size=256 ) | | ThreadSafeString( size_t size=256 ) | |
|
| : _size( 256 ) , _buf( new char[256] ){ | | : _size( size ) , _buf( new char[size] ) { | |
| memset( _buf , 0 , _size ); | | memset( _buf , 0 , _size ); | |
| } | | } | |
| | | | |
| ThreadSafeString( const ThreadSafeString& other ) | | ThreadSafeString( const ThreadSafeString& other ) | |
|
| : _size( other._size ) , _buf( new char[_size] ){ | | : _size( other._size ) , _buf( new char[_size] ) { | |
| strncpy( _buf , other._buf , _size ); | | strncpy( _buf , other._buf , _size ); | |
| } | | } | |
| | | | |
|
| ~ThreadSafeString(){ | | ~ThreadSafeString() { | |
| delete[] _buf; | | delete[] _buf; | |
| _buf = 0; | | _buf = 0; | |
| } | | } | |
| | | | |
| string toString() const { | | string toString() const { | |
| string s = _buf; | | string s = _buf; | |
| return s; | | return s; | |
| } | | } | |
| | | | |
|
| ThreadSafeString& operator=( const char * str ){ | | ThreadSafeString& operator=( const char * str ) { | |
| size_t s = strlen(str); | | size_t s = strlen(str); | |
| if ( s >= _size - 2 ) | | if ( s >= _size - 2 ) | |
| s = _size - 2; | | s = _size - 2; | |
| strncpy( _buf , str , s ); | | strncpy( _buf , str , s ); | |
| _buf[s] = 0; | | _buf[s] = 0; | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| bool operator==( const ThreadSafeString& other ) const { | | bool operator==( const ThreadSafeString& other ) const { | |
| return strcmp( _buf , other._buf ) == 0; | | return strcmp( _buf , other._buf ) == 0; | |
| | | | |
| skipping to change at line 633 | | skipping to change at line 441 | |
| return _buf == 0 || _buf[0] == 0; | | return _buf == 0 || _buf[0] == 0; | |
| } | | } | |
| | | | |
| private: | | private: | |
| size_t _size; | | size_t _size; | |
| char * _buf; | | char * _buf; | |
| }; | | }; | |
| | | | |
| ostream& operator<<( ostream &s, const ThreadSafeString &o ); | | ostream& operator<<( ostream &s, const ThreadSafeString &o ); | |
| | | | |
|
| inline bool isNumber( char c ) { | | | |
| return c >= '0' && c <= '9'; | | | |
| } | | | |
| | | | |
| inline unsigned stringToNum(const char *str) { | | | |
| unsigned x = 0; | | | |
| const char *p = str; | | | |
| while( 1 ) { | | | |
| if( !isNumber(*p) ) { | | | |
| if( *p == 0 && p != str ) | | | |
| break; | | | |
| throw 0; | | | |
| } | | | |
| x = x * 10 + *p++ - '0'; | | | |
| } | | | |
| return x; | | | |
| } | | | |
| | | | |
| // for convenience, '{' is greater than anything and stops number parsi
ng | | | |
| inline int lexNumCmp( const char *s1, const char *s2 ) { | | | |
| //cout << "START : " << s1 << "\t" << s2 << endl; | | | |
| while( *s1 && *s2 ) { | | | |
| | | | |
| bool p1 = ( *s1 == (char)255 ); | | | |
| bool p2 = ( *s2 == (char)255 ); | | | |
| //cout << "\t\t " << p1 << "\t" << p2 << endl; | | | |
| if ( p1 && !p2 ) | | | |
| return 1; | | | |
| if ( p2 && !p1 ) | | | |
| return -1; | | | |
| | | | |
| bool n1 = isNumber( *s1 ); | | | |
| bool n2 = isNumber( *s2 ); | | | |
| | | | |
| if ( n1 && n2 ) { | | | |
| // get rid of leading 0s | | | |
| while ( *s1 == '0' ) s1++; | | | |
| while ( *s2 == '0' ) s2++; | | | |
| | | | |
| char * e1 = (char*)s1; | | | |
| char * e2 = (char*)s2; | | | |
| | | | |
| // find length | | | |
| // if end of string, will break immediately ('\0') | | | |
| while ( isNumber (*e1) ) e1++; | | | |
| while ( isNumber (*e2) ) e2++; | | | |
| | | | |
| int len1 = e1-s1; | | | |
| int len2 = e2-s2; | | | |
| | | | |
| int result; | | | |
| // if one is longer than the other, return | | | |
| if ( len1 > len2 ) { | | | |
| return 1; | | | |
| } | | | |
| else if ( len2 > len1 ) { | | | |
| return -1; | | | |
| } | | | |
| // if the lengths are equal, just strcmp | | | |
| else if ( (result = strncmp(s1, s2, len1)) != 0 ) { | | | |
| return result; | | | |
| } | | | |
| | | | |
| // otherwise, the numbers are equal | | | |
| s1 = e1; | | | |
| s2 = e2; | | | |
| continue; | | | |
| } | | | |
| | | | |
| if ( n1 ) | | | |
| return 1; | | | |
| | | | |
| if ( n2 ) | | | |
| return -1; | | | |
| | | | |
| if ( *s1 > *s2 ) | | | |
| return 1; | | | |
| | | | |
| if ( *s2 > *s1 ) | | | |
| return -1; | | | |
| | | | |
| s1++; s2++; | | | |
| } | | | |
| | | | |
| if ( *s1 ) | | | |
| return 1; | | | |
| if ( *s2 ) | | | |
| return -1; | | | |
| return 0; | | | |
| } | | | |
| | | | |
| /** A generic pointer type for function arguments. | | /** A generic pointer type for function arguments. | |
| * It will convert from any pointer type except auto_ptr. | | * It will convert from any pointer type except auto_ptr. | |
| * Semantics are the same as passing the pointer returned from get() | | * Semantics are the same as passing the pointer returned from get() | |
| * const ptr<T> => T * const | | * const ptr<T> => T * const | |
| * ptr<const T> => T const * or const T* | | * ptr<const T> => T const * or const T* | |
| */ | | */ | |
| template <typename T> | | template <typename T> | |
|
| struct ptr{ | | struct ptr { | |
| | | | |
| ptr() : _p(NULL) {} | | ptr() : _p(NULL) {} | |
| | | | |
| // convert to ptr<T> | | // convert to ptr<T> | |
| ptr(T* p) : _p(p) {} // needed for NULL | | ptr(T* p) : _p(p) {} // needed for NULL | |
| template<typename U> ptr(U* p) : _p(p) {} | | template<typename U> ptr(U* p) : _p(p) {} | |
| template<typename U> ptr(const ptr<U>& p) : _p(p) {} | | template<typename U> ptr(const ptr<U>& p) : _p(p) {} | |
| template<typename U> ptr(const boost::shared_ptr<U>& p) : _p(p.get(
)) {} | | template<typename U> ptr(const boost::shared_ptr<U>& p) : _p(p.get(
)) {} | |
| template<typename U> ptr(const boost::scoped_ptr<U>& p) : _p(p.get(
)) {} | | template<typename U> ptr(const boost::scoped_ptr<U>& p) : _p(p.get(
)) {} | |
| //template<typename U> ptr(const auto_ptr<U>& p) : _p(p.get()) {} | | //template<typename U> ptr(const auto_ptr<U>& p) : _p(p.get()) {} | |
| | | | |
End of changes. 45 change blocks. |
| 349 lines changed or deleted | | 66 lines changed or added | |
|
| index.h | | index.h | |
| | | | |
| skipping to change at line 25 | | skipping to change at line 25 | |
| * You should have received a copy of the GNU Affero General Public Licen
se | | * You should have received a copy of the GNU Affero General Public Licen
se | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../pch.h" | | #include "../pch.h" | |
| #include "diskloc.h" | | #include "diskloc.h" | |
| #include "jsobj.h" | | #include "jsobj.h" | |
| #include "indexkey.h" | | #include "indexkey.h" | |
|
| | | #include "key.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| /* Details about a particular index. There is one of these effective
ly for each object in | | class IndexInterface { | |
| system.namespaces (although this also includes the head pointer,
which is not in that | | protected: | |
| collection). | | virtual ~IndexInterface() { } | |
| | | public: | |
| | | virtual int keyCompare(const BSONObj& l,const BSONObj& r, const Ord
ering &ordering) = 0; | |
| | | virtual long long fullValidate(const DiskLoc& thisLoc, const BSONOb
j &order) = 0; | |
| | | virtual DiskLoc findSingle(const IndexDetails &indexdetails , const
DiskLoc& thisLoc, const BSONObj& key) const = 0; | |
| | | virtual bool unindex(const DiskLoc thisLoc, IndexDetails& id, const
BSONObj& key, const DiskLoc recordLoc) const = 0; | |
| | | virtual int bt_insert(const DiskLoc thisLoc, const DiskLoc recordLo
c, | |
| | | const BSONObj& key, const Ordering &order, bool dupsAllowed, | |
| | | IndexDetails& idx, bool toplevel = true) const = 0; | |
| | | virtual DiskLoc addBucket(const IndexDetails&) = 0; | |
| | | virtual void uassertIfDups(IndexDetails& idx, vector<BSONObj*>& add
edKeys, DiskLoc head, | |
| | | DiskLoc self, const Ordering& ordering) = 0; | |
| | | | |
| | | // these are for geo | |
| | | virtual bool isUsed(DiskLoc thisLoc, int pos) = 0; | |
| | | virtual void keyAt(DiskLoc thisLoc, int pos, BSONObj&, DiskLoc& rec
ordLoc) = 0; | |
| | | virtual BSONObj keyAt(DiskLoc thisLoc, int pos) = 0; | |
| | | virtual DiskLoc locate(const IndexDetails &idx , const DiskLoc& thi
sLoc, const BSONObj& key, const Ordering &order, | |
| | | int& pos, bool& found, const DiskLoc &record
Loc, int direction=1) = 0; | |
| | | virtual DiskLoc advance(const DiskLoc& thisLoc, int& keyOfs, int di
rection, const char *caller) = 0; | |
| | | }; | |
| | | | |
| | | /* Details about a particular index. There is one of these effectively
for each object in | |
| | | system.namespaces (although this also includes the head pointer, whi
ch is not in that | |
| | | collection). | |
| | | | |
| ** MemoryMapped Record ** (i.e., this is on disk data) | | ** MemoryMapped Record ** (i.e., this is on disk data) | |
|
| */ | | */ | |
| class IndexDetails { | | class IndexDetails { | |
| public: | | public: | |
|
| DiskLoc head; /* btree head disk location */ | | /** | |
| | | * btree head disk location | |
| | | * TODO We should make this variable private, since btree operation
s | |
| | | * may change its value and we don't want clients to rely on an old | |
| | | * value. If we create a btree class, we can provide a btree objec
t | |
| | | * to clients instead of 'head'. | |
| | | */ | |
| | | DiskLoc head; | |
| | | | |
| /* Location of index info object. Format: | | /* Location of index info object. Format: | |
| | | | |
| { name:"nameofindex", ns:"parentnsname", key: {keypattobject} | | { name:"nameofindex", ns:"parentnsname", key: {keypattobject} | |
|
| [, unique: <bool>, background: <bool>] | | [, unique: <bool>, background: <bool>, v:<version>] | |
| } | | } | |
| | | | |
| This object is in the system.indexes collection. Note that sinc
e we | | This object is in the system.indexes collection. Note that sinc
e we | |
| have a pointer to the object here, the object in system.indexes
MUST NEVER MOVE. | | have a pointer to the object here, the object in system.indexes
MUST NEVER MOVE. | |
| */ | | */ | |
| DiskLoc info; | | DiskLoc info; | |
| | | | |
| /* extract key value from the query object | | /* extract key value from the query object | |
| e.g., if key() == { x : 1 }, | | e.g., if key() == { x : 1 }, | |
| { x : 70, y : 3 } -> { x : 70 } | | { x : 70, y : 3 } -> { x : 70 } | |
| | | | |
| skipping to change at line 64 | | skipping to change at line 96 | |
| BSONObj k = keyPattern(); | | BSONObj k = keyPattern(); | |
| BSONObj res = query.extractFieldsUnDotted(k); | | BSONObj res = query.extractFieldsUnDotted(k); | |
| return res; | | return res; | |
| } | | } | |
| | | | |
| /* pull out the relevant key objects from obj, so we | | /* pull out the relevant key objects from obj, so we | |
| can index them. Note that the set is multiple elements | | can index them. Note that the set is multiple elements | |
| only when it's a "multikey" array. | | only when it's a "multikey" array. | |
| keys will be left empty if key not found in the object. | | keys will be left empty if key not found in the object. | |
| */ | | */ | |
|
| void getKeysFromObject( const BSONObj& obj, BSONObjSetDefaultOrder&
keys) const; | | void getKeysFromObject( const BSONObj& obj, BSONObjSet& keys) const
; | |
| | | | |
| /* get the key pattern for this object. | | /* get the key pattern for this object. | |
| e.g., { lastname:1, firstname:1 } | | e.g., { lastname:1, firstname:1 } | |
| */ | | */ | |
| BSONObj keyPattern() const { | | BSONObj keyPattern() const { | |
| return info.obj().getObjectField("key"); | | return info.obj().getObjectField("key"); | |
| } | | } | |
| | | | |
|
| | | /** | |
| | | * @return offset into keyPattern for key | |
| | | -1 if doesn't exist | |
| | | */ | |
| | | int keyPatternOffset( const string& key ) const; | |
| | | bool inKeyPattern( const string& key ) const { return keyPatternOff
set( key ) >= 0; } | |
| | | | |
| /* true if the specified key is in the index */ | | /* true if the specified key is in the index */ | |
| bool hasKey(const BSONObj& key); | | bool hasKey(const BSONObj& key); | |
|
| bool wouldCreateDup(const BSONObj& key, DiskLoc self); | | | |
| | | | |
| // returns name of this index's storage area | | // returns name of this index's storage area | |
| // database.table.$index | | // database.table.$index | |
| string indexNamespace() const { | | string indexNamespace() const { | |
| BSONObj io = info.obj(); | | BSONObj io = info.obj(); | |
| string s; | | string s; | |
| s.reserve(Namespace::MaxNsLen); | | s.reserve(Namespace::MaxNsLen); | |
| s = io.getStringField("ns"); | | s = io.getStringField("ns"); | |
| assert( !s.empty() ); | | assert( !s.empty() ); | |
| s += ".$"; | | s += ".$"; | |
| | | | |
| skipping to change at line 115 | | skipping to change at line 153 | |
| } | | } | |
| | | | |
| /* gets not our namespace name (indexNamespace for that), | | /* gets not our namespace name (indexNamespace for that), | |
| but the collection we index, its name. | | but the collection we index, its name. | |
| */ | | */ | |
| string parentNS() const { | | string parentNS() const { | |
| BSONObj io = info.obj(); | | BSONObj io = info.obj(); | |
| return io.getStringField("ns"); | | return io.getStringField("ns"); | |
| } | | } | |
| | | | |
|
| | | static int versionForIndexObj( const BSONObj &obj ) { | |
| | | BSONElement e = obj["v"]; | |
| | | if( e.type() == NumberInt ) | |
| | | return e._numberInt(); | |
| | | // should normally be an int. this is for backward compatibili
ty | |
| | | int v = e.numberInt(); | |
| | | uassert(14802, "index v field should be Integer type", v == 0); | |
| | | return v; | |
| | | } | |
| | | | |
| | | int version() const { | |
| | | return versionForIndexObj( info.obj() ); | |
| | | } | |
| | | | |
| | | /** @return true if index has unique constraint */ | |
| bool unique() const { | | bool unique() const { | |
| BSONObj io = info.obj(); | | BSONObj io = info.obj(); | |
| return io["unique"].trueValue() || | | return io["unique"].trueValue() || | |
|
| /* temp: can we juse make unique:true always be there for _
id and get rid of this? */ | | /* temp: can we juse make unique:true always be there fo
r _id and get rid of this? */ | |
| isIdIndex(); | | isIdIndex(); | |
| } | | } | |
| | | | |
|
| /* if set, when building index, if any duplicates, drop the duplica
ting object */ | | /** return true if dropDups was set when building index (if any dup
licates, dropdups drops the duplicating objects) */ | |
| bool dropDups() const { | | bool dropDups() const { | |
| return info.obj().getBoolField( "dropDups" ); | | return info.obj().getBoolField( "dropDups" ); | |
| } | | } | |
| | | | |
|
| /* delete this index. does NOT clean up the system catalog | | /** delete this index. does NOT clean up the system catalog | |
| (system.indexes or system.namespaces) -- only NamespaceIndex. | | (system.indexes or system.namespaces) -- only NamespaceIndex. | |
| */ | | */ | |
| void kill_idx(); | | void kill_idx(); | |
| | | | |
| const IndexSpec& getSpec() const; | | const IndexSpec& getSpec() const; | |
| | | | |
| string toString() const { | | string toString() const { | |
| return info.obj().toString(); | | return info.obj().toString(); | |
| } | | } | |
|
| | | | |
| | | /** @return true if supported. supported means we can use the inde
x, including adding new keys. | |
| | | it may not mean we can build the index version in quest
ion: we may not maintain building | |
| | | of indexes in old formats in the future. | |
| | | */ | |
| | | static bool isASupportedIndexVersionNumber(int v) { return (v&1)==v
; } // v == 0 || v == 1 | |
| | | | |
| | | /** @return the interface for this interface, which varies with the
index version. | |
| | | used for backward compatibility of index versions/formats. | |
| | | */ | |
| | | IndexInterface& idxInterface() const { | |
| | | int v = version(); | |
| | | dassert( isASupportedIndexVersionNumber(v) ); | |
| | | return *iis[v&1]; | |
| | | } | |
| | | | |
| | | static IndexInterface *iis[]; | |
| }; | | }; | |
| | | | |
|
| struct IndexChanges/*on an update*/ { | | struct IndexChanges { /*on an update*/ | |
| BSONObjSetDefaultOrder oldkeys; | | BSONObjSet oldkeys; | |
| BSONObjSetDefaultOrder newkeys; | | BSONObjSet newkeys; | |
| vector<BSONObj*> removed; // these keys were removed as part of the
change | | vector<BSONObj*> removed; // these keys were removed as part of the
change | |
| vector<BSONObj*> added; // these keys were added as part of the c
hange | | vector<BSONObj*> added; // these keys were added as part of the c
hange | |
| | | | |
| /** @curObjLoc - the object we want to add's location. if it is al
ready in the | | /** @curObjLoc - the object we want to add's location. if it is al
ready in the | |
| index, that is allowed here (for bg indexing case)
. | | index, that is allowed here (for bg indexing case)
. | |
| */ | | */ | |
| void dupCheck(IndexDetails& idx, DiskLoc curObjLoc) { | | void dupCheck(IndexDetails& idx, DiskLoc curObjLoc) { | |
| if( added.empty() || !idx.unique() ) | | if( added.empty() || !idx.unique() ) | |
| return; | | return; | |
|
| for( vector<BSONObj*>::iterator i = added.begin(); i != added.e
nd(); i++ ) { | | const Ordering ordering = Ordering::make(idx.keyPattern()); | |
| bool dup = idx.wouldCreateDup(**i, curObjLoc); | | idx.idxInterface().uassertIfDups(idx, added, idx.head, curObjLo
c, ordering); // "E11001 duplicate key on update" | |
| uassert( 11001 , "E11001 duplicate key on update", !dup); | | | |
| } | | | |
| } | | } | |
| }; | | }; | |
| | | | |
| class NamespaceDetails; | | class NamespaceDetails; | |
| // changedId should be initialized to false | | // changedId should be initialized to false | |
| void getIndexChanges(vector<IndexChanges>& v, NamespaceDetails& d, BSON
Obj newObj, BSONObj oldObj, bool &cangedId); | | void getIndexChanges(vector<IndexChanges>& v, NamespaceDetails& d, BSON
Obj newObj, BSONObj oldObj, bool &cangedId); | |
| void dupCheck(vector<IndexChanges>& v, NamespaceDetails& d, DiskLoc cur
ObjLoc); | | void dupCheck(vector<IndexChanges>& v, NamespaceDetails& d, DiskLoc cur
ObjLoc); | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 15 change blocks. |
| 20 lines changed or deleted | | 88 lines changed or added | |
|
| log.h | | log.h | |
| | | | |
| skipping to change at line 32 | | skipping to change at line 32 | |
| #include "../bson/util/builder.h" | | #include "../bson/util/builder.h" | |
| | | | |
| #ifndef _WIN32 | | #ifndef _WIN32 | |
| //#include <syslog.h> | | //#include <syslog.h> | |
| #endif | | #endif | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| enum LogLevel { LL_DEBUG , LL_INFO , LL_NOTICE , LL_WARNING , LL_ERROR
, LL_SEVERE }; | | enum LogLevel { LL_DEBUG , LL_INFO , LL_NOTICE , LL_WARNING , LL_ERROR
, LL_SEVERE }; | |
| | | | |
|
| inline const char * logLevelToString( LogLevel l ){ | | inline const char * logLevelToString( LogLevel l ) { | |
| switch ( l ){ | | switch ( l ) { | |
| case LL_DEBUG: | | case LL_DEBUG: | |
| case LL_INFO: | | case LL_INFO: | |
| case LL_NOTICE: | | case LL_NOTICE: | |
| return ""; | | return ""; | |
| case LL_WARNING: | | case LL_WARNING: | |
| return "warning" ; | | return "warning" ; | |
| case LL_ERROR: | | case LL_ERROR: | |
| return "ERROR"; | | return "ERROR"; | |
| case LL_SEVERE: | | case LL_SEVERE: | |
| return "SEVERE"; | | return "SEVERE"; | |
| default: | | default: | |
| return "UNKNOWN"; | | return "UNKNOWN"; | |
| } | | } | |
| } | | } | |
| | | | |
|
| | | class LabeledLevel { | |
| | | public: | |
| | | | |
| | | LabeledLevel( int level ) : _level( level ) {} | |
| | | LabeledLevel( const char* label, int level ) : _label( label ), _lev
el( level ) {} | |
| | | LabeledLevel( const string& label, int level ) : _label( label ), _l
evel( level ) {} | |
| | | | |
| | | LabeledLevel operator+( int i ) const { | |
| | | return LabeledLevel( _label, _level + i ); | |
| | | } | |
| | | | |
| | | LabeledLevel operator+( const char* label ) const { | |
| | | if( _label == "" ) | |
| | | return LabeledLevel( label, _level ); | |
| | | return LabeledLevel( _label + string("::") + label, _level ); | |
| | | } | |
| | | | |
| | | LabeledLevel operator+( string& label ) const { | |
| | | return LabeledLevel( _label + string("::") + label, _level ); | |
| | | } | |
| | | | |
| | | LabeledLevel operator-( int i ) const { | |
| | | return LabeledLevel( _label, _level - i ); | |
| | | } | |
| | | | |
| | | const string& getLabel() const { return _label; } | |
| | | int getLevel() const { return _level; } | |
| | | | |
| | | private: | |
| | | string _label; | |
| | | int _level; | |
| | | }; | |
| | | | |
| class LazyString { | | class LazyString { | |
| public: | | public: | |
| virtual ~LazyString() {} | | virtual ~LazyString() {} | |
| virtual string val() const = 0; | | virtual string val() const = 0; | |
| }; | | }; | |
| | | | |
| // Utility class for stringifying object only when val() called. | | // Utility class for stringifying object only when val() called. | |
| template< class T > | | template< class T > | |
| class LazyStringImpl : public LazyString { | | class LazyStringImpl : public LazyString { | |
| public: | | public: | |
| LazyStringImpl( const T &t ) : t_( t ) {} | | LazyStringImpl( const T &t ) : t_( t ) {} | |
| virtual string val() const { return t_.toString(); } | | virtual string val() const { return t_.toString(); } | |
| private: | | private: | |
| const T& t_; | | const T& t_; | |
| }; | | }; | |
| | | | |
| class Tee { | | class Tee { | |
| public: | | public: | |
|
| virtual ~Tee(){} | | virtual ~Tee() {} | |
| virtual void write(LogLevel level , const string& str) = 0; | | virtual void write(LogLevel level , const string& str) = 0; | |
| }; | | }; | |
| | | | |
| class Nullstream { | | class Nullstream { | |
| public: | | public: | |
| virtual Nullstream& operator<< (Tee* tee) { | | virtual Nullstream& operator<< (Tee* tee) { | |
| return *this; | | return *this; | |
| } | | } | |
| virtual ~Nullstream() {} | | virtual ~Nullstream() {} | |
| virtual Nullstream& operator<<(const char *) { | | virtual Nullstream& operator<<(const char *) { | |
| | | | |
| skipping to change at line 107 | | skipping to change at line 140 | |
| } | | } | |
| virtual Nullstream& operator<<(unsigned long) { | | virtual Nullstream& operator<<(unsigned long) { | |
| return *this; | | return *this; | |
| } | | } | |
| virtual Nullstream& operator<<(long) { | | virtual Nullstream& operator<<(long) { | |
| return *this; | | return *this; | |
| } | | } | |
| virtual Nullstream& operator<<(unsigned) { | | virtual Nullstream& operator<<(unsigned) { | |
| return *this; | | return *this; | |
| } | | } | |
|
| | | virtual Nullstream& operator<<(unsigned short) { | |
| | | return *this; | |
| | | } | |
| virtual Nullstream& operator<<(double) { | | virtual Nullstream& operator<<(double) { | |
| return *this; | | return *this; | |
| } | | } | |
| virtual Nullstream& operator<<(void *) { | | virtual Nullstream& operator<<(void *) { | |
| return *this; | | return *this; | |
| } | | } | |
| virtual Nullstream& operator<<(const void *) { | | virtual Nullstream& operator<<(const void *) { | |
| return *this; | | return *this; | |
| } | | } | |
| virtual Nullstream& operator<<(long long) { | | virtual Nullstream& operator<<(long long) { | |
| | | | |
| skipping to change at line 137 | | skipping to change at line 173 | |
| } | | } | |
| template< class T > | | template< class T > | |
| Nullstream& operator<<(T *t) { | | Nullstream& operator<<(T *t) { | |
| return operator<<( static_cast<void*>( t ) ); | | return operator<<( static_cast<void*>( t ) ); | |
| } | | } | |
| template< class T > | | template< class T > | |
| Nullstream& operator<<(const T *t) { | | Nullstream& operator<<(const T *t) { | |
| return operator<<( static_cast<const void*>( t ) ); | | return operator<<( static_cast<const void*>( t ) ); | |
| } | | } | |
| template< class T > | | template< class T > | |
|
| Nullstream& operator<<(const shared_ptr<T> p ){ | | Nullstream& operator<<(const shared_ptr<T> p ) { | |
| | | T * t = p.get(); | |
| | | if ( ! t ) | |
| | | *this << "null"; | |
| | | else | |
| | | *this << *t; | |
| return *this; | | return *this; | |
| } | | } | |
| template< class T > | | template< class T > | |
| Nullstream& operator<<(const T &t) { | | Nullstream& operator<<(const T &t) { | |
| return operator<<( static_cast<const LazyString&>( LazyStringIm
pl< T >( t ) ) ); | | return operator<<( static_cast<const LazyString&>( LazyStringIm
pl< T >( t ) ) ); | |
| } | | } | |
|
| | | | |
| virtual Nullstream& operator<< (ostream& ( *endl )(ostream&)) { | | virtual Nullstream& operator<< (ostream& ( *endl )(ostream&)) { | |
| return *this; | | return *this; | |
| } | | } | |
| virtual Nullstream& operator<< (ios_base& (*hex)(ios_base&)) { | | virtual Nullstream& operator<< (ios_base& (*hex)(ios_base&)) { | |
| return *this; | | return *this; | |
| } | | } | |
|
| | | | |
| virtual void flush(Tee *t = 0) {} | | virtual void flush(Tee *t = 0) {} | |
| }; | | }; | |
| extern Nullstream nullstream; | | extern Nullstream nullstream; | |
| | | | |
| class Logstream : public Nullstream { | | class Logstream : public Nullstream { | |
| static mongo::mutex mutex; | | static mongo::mutex mutex; | |
| static int doneSetup; | | static int doneSetup; | |
| stringstream ss; | | stringstream ss; | |
|
| | | int indent; | |
| LogLevel logLevel; | | LogLevel logLevel; | |
| static FILE* logfile; | | static FILE* logfile; | |
| static boost::scoped_ptr<ostream> stream; | | static boost::scoped_ptr<ostream> stream; | |
| static vector<Tee*> * globalTees; | | static vector<Tee*> * globalTees; | |
| public: | | public: | |
|
| | | | |
| inline static void logLockless( const StringData& s ); | | inline static void logLockless( const StringData& s ); | |
| | | | |
|
| static void setLogFile(FILE* f){ | | static void setLogFile(FILE* f) { | |
| scoped_lock lk(mutex); | | scoped_lock lk(mutex); | |
| logfile = f; | | logfile = f; | |
| } | | } | |
| | | | |
|
| static int magicNumber(){ | | static int magicNumber() { | |
| return 1717; | | return 1717; | |
| } | | } | |
| | | | |
| static int getLogDesc() { | | static int getLogDesc() { | |
| int fd = -1; | | int fd = -1; | |
| if (logfile != NULL) | | if (logfile != NULL) | |
|
| | | #if defined(_WIN32) | |
| | | // the ISO C++ conformant name is _fileno | |
| | | fd = _fileno( logfile ); | |
| | | #else | |
| fd = fileno( logfile ); | | fd = fileno( logfile ); | |
|
| | | #endif | |
| return fd; | | return fd; | |
| } | | } | |
| | | | |
| inline void flush(Tee *t = 0); | | inline void flush(Tee *t = 0); | |
| | | | |
|
| inline Nullstream& setLogLevel(LogLevel l){ | | inline Nullstream& setLogLevel(LogLevel l) { | |
| logLevel = l; | | logLevel = l; | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
| /** note these are virtual */ | | /** note these are virtual */ | |
| Logstream& operator<<(const char *x) { ss << x; return *this; } | | Logstream& operator<<(const char *x) { ss << x; return *this; } | |
| Logstream& operator<<(const string& x) { ss << x; return *this; } | | Logstream& operator<<(const string& x) { ss << x; return *this; } | |
| Logstream& operator<<(const StringData& x) { ss << x.data(); return
*this; } | | Logstream& operator<<(const StringData& x) { ss << x.data(); return
*this; } | |
| Logstream& operator<<(char *x) { ss << x; return *this; } | | Logstream& operator<<(char *x) { ss << x; return *this; } | |
| Logstream& operator<<(char x) { ss << x; return *this; } | | Logstream& operator<<(char x) { ss << x; return *this; } | |
| Logstream& operator<<(int x) { ss << x; return *this; } | | Logstream& operator<<(int x) { ss << x; return *this; } | |
| Logstream& operator<<(ExitCode x) { ss << x; return *this; } | | Logstream& operator<<(ExitCode x) { ss << x; return *this; } | |
| Logstream& operator<<(long x) { ss << x; return *this; } | | Logstream& operator<<(long x) { ss << x; return *this; } | |
| Logstream& operator<<(unsigned long x) { ss << x; return *this; } | | Logstream& operator<<(unsigned long x) { ss << x; return *this; } | |
| Logstream& operator<<(unsigned x) { ss << x; return *this; } | | Logstream& operator<<(unsigned x) { ss << x; return *this; } | |
|
| | | Logstream& operator<<(unsigned short x){ ss << x; return *this; } | |
| Logstream& operator<<(double x) { ss << x; return *this; } | | Logstream& operator<<(double x) { ss << x; return *this; } | |
| Logstream& operator<<(void *x) { ss << x; return *this; } | | Logstream& operator<<(void *x) { ss << x; return *this; } | |
| Logstream& operator<<(const void *x) { ss << x; return *this; } | | Logstream& operator<<(const void *x) { ss << x; return *this; } | |
| Logstream& operator<<(long long x) { ss << x; return *this; } | | Logstream& operator<<(long long x) { ss << x; return *this; } | |
| Logstream& operator<<(unsigned long long x) { ss << x; return *this
; } | | Logstream& operator<<(unsigned long long x) { ss << x; return *this
; } | |
| Logstream& operator<<(bool x) { ss << x; return *this
; } | | Logstream& operator<<(bool x) { ss << x; return *this
; } | |
| | | | |
| Logstream& operator<<(const LazyString& x) { | | Logstream& operator<<(const LazyString& x) { | |
| ss << x.val(); | | ss << x.val(); | |
| return *this; | | return *this; | |
| | | | |
| skipping to change at line 226 | | skipping to change at line 275 | |
| Logstream& operator<< (ostream& ( *_endl )(ostream&)) { | | Logstream& operator<< (ostream& ( *_endl )(ostream&)) { | |
| ss << '\n'; | | ss << '\n'; | |
| flush(0); | | flush(0); | |
| return *this; | | return *this; | |
| } | | } | |
| Logstream& operator<< (ios_base& (*_hex)(ios_base&)) { | | Logstream& operator<< (ios_base& (*_hex)(ios_base&)) { | |
| ss << _hex; | | ss << _hex; | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
|
| template< class T > | | | |
| Nullstream& operator<<(const shared_ptr<T> p ){ | | | |
| T * t = p.get(); | | | |
| if ( ! t ) | | | |
| *this << "null"; | | | |
| else | | | |
| *this << *t; | | | |
| return *this; | | | |
| } | | | |
| | | | |
| Logstream& prolog() { | | Logstream& prolog() { | |
| return *this; | | return *this; | |
| } | | } | |
| | | | |
|
| void addGlobalTee( Tee * t ){ | | void addGlobalTee( Tee * t ) { | |
| if ( ! globalTees ) | | if ( ! globalTees ) | |
| globalTees = new vector<Tee*>(); | | globalTees = new vector<Tee*>(); | |
| globalTees->push_back( t ); | | globalTees->push_back( t ); | |
| } | | } | |
| | | | |
|
| | | void indentInc(){ indent++; } | |
| | | void indentDec(){ indent--; } | |
| | | int getIndent() const { return indent; } | |
| | | | |
| private: | | private: | |
| static thread_specific_ptr<Logstream> tsp; | | static thread_specific_ptr<Logstream> tsp; | |
|
| Logstream(){ | | Logstream() { | |
| | | indent = 0; | |
| _init(); | | _init(); | |
| } | | } | |
|
| void _init(){ | | void _init() { | |
| ss.str(""); | | ss.str(""); | |
| logLevel = LL_INFO; | | logLevel = LL_INFO; | |
| } | | } | |
| public: | | public: | |
| static Logstream& get() { | | static Logstream& get() { | |
|
| | | if ( StaticObserver::_destroyingStatics ) { | |
| | | cout << "Logstream::get called in uninitialized state" << e
ndl; | |
| | | } | |
| Logstream *p = tsp.get(); | | Logstream *p = tsp.get(); | |
| if( p == 0 ) | | if( p == 0 ) | |
| tsp.reset( p = new Logstream() ); | | tsp.reset( p = new Logstream() ); | |
| return *p; | | return *p; | |
| } | | } | |
| }; | | }; | |
| | | | |
| extern int logLevel; | | extern int logLevel; | |
| extern int tlogLevel; | | extern int tlogLevel; | |
| | | | |
| | | | |
| skipping to change at line 281 | | skipping to change at line 328 | |
| } | | } | |
| | | | |
| /* flush the log stream if the log level is | | /* flush the log stream if the log level is | |
| at the specified level or higher. */ | | at the specified level or higher. */ | |
| inline void logflush(int level = 0) { | | inline void logflush(int level = 0) { | |
| if( level > logLevel ) | | if( level > logLevel ) | |
| Logstream::get().flush(0); | | Logstream::get().flush(0); | |
| } | | } | |
| | | | |
| /* without prolog */ | | /* without prolog */ | |
|
| inline Nullstream& _log( int level = 0 ){ | | inline Nullstream& _log( int level = 0 ) { | |
| if ( level > logLevel ) | | if ( level > logLevel ) | |
| return nullstream; | | return nullstream; | |
| return Logstream::get(); | | return Logstream::get(); | |
| } | | } | |
| | | | |
|
| /** logging which we may not want during unit tests runs. | | /** logging which we may not want during unit tests (dbtests) runs. | |
| set tlogLevel to -1 to suppress tlog() output in a test program. */ | | set tlogLevel to -1 to suppress tlog() output in a test program. */ | |
| inline Nullstream& tlog( int level = 0 ) { | | inline Nullstream& tlog( int level = 0 ) { | |
| if ( level > tlogLevel || level > logLevel ) | | if ( level > tlogLevel || level > logLevel ) | |
| return nullstream; | | return nullstream; | |
| return Logstream::get().prolog(); | | return Logstream::get().prolog(); | |
| } | | } | |
| | | | |
| inline Nullstream& log( int level ) { | | inline Nullstream& log( int level ) { | |
| if ( level > logLevel ) | | if ( level > logLevel ) | |
| return nullstream; | | return nullstream; | |
| return Logstream::get().prolog(); | | return Logstream::get().prolog(); | |
| } | | } | |
| | | | |
|
| | | #define MONGO_LOG(level) if ( MONGO_unlikely(logLevel >= (level)) ) log( le
vel ) | |
| | | #define LOG MONGO_LOG | |
| | | | |
| inline Nullstream& log( LogLevel l ) { | | inline Nullstream& log( LogLevel l ) { | |
| return Logstream::get().prolog().setLogLevel( l ); | | return Logstream::get().prolog().setLogLevel( l ); | |
| } | | } | |
| | | | |
|
| | | inline Nullstream& log( const LabeledLevel& ll ) { | |
| | | Nullstream& stream = log( ll.getLevel() ); | |
| | | if( ll.getLabel() != "" ) | |
| | | stream << "[" << ll.getLabel() << "] "; | |
| | | return stream; | |
| | | } | |
| | | | |
| inline Nullstream& log() { | | inline Nullstream& log() { | |
| return Logstream::get().prolog(); | | return Logstream::get().prolog(); | |
| } | | } | |
| | | | |
| inline Nullstream& error() { | | inline Nullstream& error() { | |
| return log( LL_ERROR ); | | return log( LL_ERROR ); | |
| } | | } | |
| | | | |
| inline Nullstream& warning() { | | inline Nullstream& warning() { | |
| return log( LL_WARNING ); | | return log( LL_WARNING ); | |
| | | | |
| skipping to change at line 383 | | skipping to change at line 440 | |
| s << strerror(x); | | s << strerror(x); | |
| #endif | | #endif | |
| return s.str(); | | return s.str(); | |
| } | | } | |
| | | | |
| /** output the error # and error message with prefix. | | /** output the error # and error message with prefix. | |
| handy for use as parm in uassert/massert. | | handy for use as parm in uassert/massert. | |
| */ | | */ | |
| string errnoWithPrefix( const char * prefix ); | | string errnoWithPrefix( const char * prefix ); | |
| | | | |
|
| void Logstream::logLockless( const StringData& s ){ | | void Logstream::logLockless( const StringData& s ) { | |
| if ( doneSetup == 1717 ){ | | if ( s.size() == 0 ) | |
| if(fwrite(s.data(), s.size(), 1, logfile)){ | | return; | |
| | | | |
| | | if ( doneSetup == 1717 ) { | |
| | | if (fwrite(s.data(), s.size(), 1, logfile)) { | |
| fflush(logfile); | | fflush(logfile); | |
|
| }else{ | | } | |
| | | else { | |
| int x = errno; | | int x = errno; | |
| cout << "Failed to write to logfile: " << errnoWithDescript
ion(x) << endl; | | cout << "Failed to write to logfile: " << errnoWithDescript
ion(x) << endl; | |
| } | | } | |
| } | | } | |
| else { | | else { | |
| cout << s.data(); | | cout << s.data(); | |
| cout.flush(); | | cout.flush(); | |
| } | | } | |
| } | | } | |
| | | | |
| void Logstream::flush(Tee *t) { | | void Logstream::flush(Tee *t) { | |
| // this ensures things are sane | | // this ensures things are sane | |
| if ( doneSetup == 1717 ) { | | if ( doneSetup == 1717 ) { | |
| string msg = ss.str(); | | string msg = ss.str(); | |
| string threadName = getThreadName(); | | string threadName = getThreadName(); | |
| const char * type = logLevelToString(logLevel); | | const char * type = logLevelToString(logLevel); | |
| | | | |
|
| int spaceNeeded = msg.size() + 64 + threadName.size(); | | int spaceNeeded = (int)(msg.size() + 64 + threadName.size()); | |
| int bufSize = 128; | | int bufSize = 128; | |
| while ( bufSize < spaceNeeded ) | | while ( bufSize < spaceNeeded ) | |
| bufSize += 128; | | bufSize += 128; | |
| | | | |
| BufBuilder b(bufSize); | | BufBuilder b(bufSize); | |
| time_t_to_String( time(0) , b.grow(20) ); | | time_t_to_String( time(0) , b.grow(20) ); | |
|
| if (!threadName.empty()){ | | if (!threadName.empty()) { | |
| b.appendChar( '[' ); | | b.appendChar( '[' ); | |
| b.appendStr( threadName , false ); | | b.appendStr( threadName , false ); | |
| b.appendChar( ']' ); | | b.appendChar( ']' ); | |
| b.appendChar( ' ' ); | | b.appendChar( ' ' ); | |
| } | | } | |
|
| if ( type[0] ){ | | | |
| | | for ( int i=0; i<indent; i++ ) | |
| | | b.appendChar( '\t' ); | |
| | | | |
| | | if ( type[0] ) { | |
| b.appendStr( type , false ); | | b.appendStr( type , false ); | |
| b.appendStr( ": " , false ); | | b.appendStr( ": " , false ); | |
| } | | } | |
|
| | | | |
| b.appendStr( msg ); | | b.appendStr( msg ); | |
| | | | |
| string out( b.buf() , b.len() - 1); | | string out( b.buf() , b.len() - 1); | |
| | | | |
| scoped_lock lk(mutex); | | scoped_lock lk(mutex); | |
| | | | |
| if( t ) t->write(logLevel,out); | | if( t ) t->write(logLevel,out); | |
|
| if ( globalTees ){ | | if ( globalTees ) { | |
| for ( unsigned i=0; i<globalTees->size(); i++ ) | | for ( unsigned i=0; i<globalTees->size(); i++ ) | |
| (*globalTees)[i]->write(logLevel,out); | | (*globalTees)[i]->write(logLevel,out); | |
| } | | } | |
| | | | |
| #ifndef _WIN32 | | #ifndef _WIN32 | |
| //syslog( LOG_INFO , "%s" , cc ); | | //syslog( LOG_INFO , "%s" , cc ); | |
| #endif | | #endif | |
|
| if(fwrite(out.data(), out.size(), 1, logfile)){ | | if(fwrite(out.data(), out.size(), 1, logfile)) { | |
| fflush(logfile); | | fflush(logfile); | |
|
| }else{ | | } | |
| | | else { | |
| int x = errno; | | int x = errno; | |
| cout << "Failed to write to logfile: " << errnoWithDescript
ion(x) << ": " << out << endl; | | cout << "Failed to write to logfile: " << errnoWithDescript
ion(x) << ": " << out << endl; | |
| } | | } | |
| } | | } | |
| _init(); | | _init(); | |
| } | | } | |
| | | | |
|
| | | struct LogIndentLevel { | |
| | | LogIndentLevel(){ | |
| | | Logstream::get().indentInc(); | |
| | | } | |
| | | ~LogIndentLevel(){ | |
| | | Logstream::get().indentDec(); | |
| | | } | |
| | | }; | |
| | | | |
| | | extern Tee* const warnings; // Things put here go in serverStatus | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 35 change blocks. |
| 33 lines changed or deleted | | 111 lines changed or added | |
|
| matcher.h | | matcher.h | |
| | | | |
| skipping to change at line 35 | | skipping to change at line 35 | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| class Cursor; | | class Cursor; | |
| class CoveredIndexMatcher; | | class CoveredIndexMatcher; | |
| class Matcher; | | class Matcher; | |
| class FieldRangeVector; | | class FieldRangeVector; | |
| | | | |
| class RegexMatcher { | | class RegexMatcher { | |
| public: | | public: | |
|
| const char *fieldName; | | const char *_fieldName; | |
| const char *regex; | | const char *_regex; | |
| const char *flags; | | const char *_flags; | |
| string prefix; | | string _prefix; | |
| shared_ptr< pcrecpp::RE > re; | | shared_ptr< pcrecpp::RE > _re; | |
| bool isNot; | | bool _isNot; | |
| RegexMatcher() : isNot() {} | | RegexMatcher() : _isNot() {} | |
| }; | | }; | |
| | | | |
|
| struct element_lt | | struct element_lt { | |
| { | | bool operator()(const BSONElement& l, const BSONElement& r) const { | |
| bool operator()(const BSONElement& l, const BSONElement& r) const | | | |
| { | | | |
| int x = (int) l.canonicalType() - (int) r.canonicalType(); | | int x = (int) l.canonicalType() - (int) r.canonicalType(); | |
| if ( x < 0 ) return true; | | if ( x < 0 ) return true; | |
| else if ( x > 0 ) return false; | | else if ( x > 0 ) return false; | |
| return compareElementValues(l,r) < 0; | | return compareElementValues(l,r) < 0; | |
| } | | } | |
| }; | | }; | |
| | | | |
| class ElementMatcher { | | class ElementMatcher { | |
| public: | | public: | |
| | | | |
| ElementMatcher() { | | ElementMatcher() { | |
| } | | } | |
| | | | |
|
| ElementMatcher( BSONElement _e , int _op, bool _isNot ); | | ElementMatcher( BSONElement e , int op, bool isNot ); | |
| | | | |
|
| ElementMatcher( BSONElement _e , int _op , const BSONObj& array, bo
ol _isNot ); | | ElementMatcher( BSONElement e , int op , const BSONObj& array, bool
isNot ); | |
| | | | |
| ~ElementMatcher() { } | | ~ElementMatcher() { } | |
| | | | |
|
| BSONElement toMatch; | | BSONElement _toMatch; | |
| int compareOp; | | int _compareOp; | |
| bool isNot; | | bool _isNot; | |
| shared_ptr< set<BSONElement,element_lt> > myset; | | shared_ptr< set<BSONElement,element_lt> > _myset; | |
| shared_ptr< vector<RegexMatcher> > myregex; | | shared_ptr< vector<RegexMatcher> > _myregex; | |
| | | | |
| // these are for specific operators | | // these are for specific operators | |
|
| int mod; | | int _mod; | |
| int modm; | | int _modm; | |
| BSONType type; | | BSONType _type; | |
| | | | |
|
| shared_ptr<Matcher> subMatcher; | | shared_ptr<Matcher> _subMatcher; | |
| | | bool _subMatcherOnPrimitives ; | |
| | | | |
|
| vector< shared_ptr<Matcher> > allMatchers; | | vector< shared_ptr<Matcher> > _allMatchers; | |
| }; | | }; | |
| | | | |
| class Where; // used for $where javascript eval | | class Where; // used for $where javascript eval | |
| class DiskLoc; | | class DiskLoc; | |
| | | | |
| struct MatchDetails { | | struct MatchDetails { | |
|
| MatchDetails(){ | | MatchDetails() { | |
| reset(); | | reset(); | |
| } | | } | |
| | | | |
|
| void reset(){ | | void reset() { | |
| loadedObject = false; | | _loadedObject = false; | |
| elemMatchKey = 0; | | _elemMatchKey = 0; | |
| } | | } | |
| | | | |
| string toString() const { | | string toString() const { | |
| stringstream ss; | | stringstream ss; | |
|
| ss << "loadedObject: " << loadedObject << " "; | | ss << "loadedObject: " << _loadedObject << " "; | |
| ss << "elemMatchKey: " << ( elemMatchKey ? elemMatchKey : "NULL
" ) << " "; | | ss << "elemMatchKey: " << ( _elemMatchKey ? _elemMatchKey : "NU
LL" ) << " "; | |
| return ss.str(); | | return ss.str(); | |
| } | | } | |
| | | | |
|
| bool loadedObject; | | bool _loadedObject; | |
| const char * elemMatchKey; // warning, this may go out of scope if
matched object does | | const char * _elemMatchKey; // warning, this may go out of scope if
matched object does | |
| }; | | }; | |
| | | | |
| /* Match BSON objects against a query pattern. | | /* Match BSON objects against a query pattern. | |
| | | | |
| e.g. | | e.g. | |
| db.foo.find( { a : 3 } ); | | db.foo.find( { a : 3 } ); | |
| | | | |
| { a : 3 } is the pattern object. See wiki documentation for full in
fo. | | { a : 3 } is the pattern object. See wiki documentation for full in
fo. | |
| | | | |
| GT/LT: | | GT/LT: | |
| | | | |
| skipping to change at line 137 | | skipping to change at line 136 | |
| int matchesNe( | | int matchesNe( | |
| const char *fieldName, | | const char *fieldName, | |
| const BSONElement &toMatch, const BSONObj &obj, | | const BSONElement &toMatch, const BSONObj &obj, | |
| const ElementMatcher&bm, MatchDetails * details ); | | const ElementMatcher&bm, MatchDetails * details ); | |
| | | | |
| public: | | public: | |
| static int opDirection(int op) { | | static int opDirection(int op) { | |
| return op <= BSONObj::LTE ? -1 : 1; | | return op <= BSONObj::LTE ? -1 : 1; | |
| } | | } | |
| | | | |
|
| Matcher(const BSONObj &pattern, bool subMatcher = false); | | Matcher(const BSONObj &pattern, bool nested=false); | |
| | | | |
| ~Matcher(); | | ~Matcher(); | |
| | | | |
| bool matches(const BSONObj& j, MatchDetails * details = 0 ); | | bool matches(const BSONObj& j, MatchDetails * details = 0 ); | |
| | | | |
|
| // fast rough check to see if we must load the real doc - we also | | | |
| // compare field counts against covereed index matcher; for $or cla
uses | | | |
| // we just compare field counts | | | |
| bool keyMatch() const { return !all && !haveSize && !hasArray && !h
aveNeg; } | | | |
| | | | |
| bool atomic() const { return _atomic; } | | bool atomic() const { return _atomic; } | |
| | | | |
|
| bool hasType( BSONObj::MatchType type ) const; | | | |
| | | | |
| string toString() const { | | string toString() const { | |
|
| return jsobj.toString(); | | return _jsobj.toString(); | |
| } | | } | |
| | | | |
|
| void addOrConstraint( const shared_ptr< FieldRangeVector > &frv ) { | | void addOrDedupConstraint( const shared_ptr< FieldRangeVector > &fr
v ) { | |
| _orConstraints.push_back( frv ); | | _orDedupConstraints.push_back( frv ); | |
| } | | } | |
| | | | |
| void popOrClause() { | | void popOrClause() { | |
| _orMatchers.pop_front(); | | _orMatchers.pop_front(); | |
| } | | } | |
| | | | |
|
| bool sameCriteriaCount( const Matcher &other ) const; | | /** | |
| | | * @return true if this key matcher will return the same true/false | |
| | | * value as the provided doc matcher. | |
| | | */ | |
| | | bool keyMatch( const Matcher &docMatcher ) const; | |
| | | | |
| private: | | private: | |
|
| // Only specify constrainIndexKey if matches() will be called with | | /** | |
| // index keys having empty string field names. | | * Generate a matcher for the provided index key format using the | |
| Matcher( const Matcher &other, const BSONObj &constrainIndexKey ); | | * provided full doc matcher. | |
| | | */ | |
| | | Matcher( const Matcher &docMatcher, const BSONObj &constrainIndexKe
y ); | |
| | | | |
| void addBasic(const BSONElement &e, int c, bool isNot) { | | void addBasic(const BSONElement &e, int c, bool isNot) { | |
| // TODO May want to selectively ignore these element types base
d on op type. | | // TODO May want to selectively ignore these element types base
d on op type. | |
| if ( e.type() == MinKey || e.type() == MaxKey ) | | if ( e.type() == MinKey || e.type() == MaxKey ) | |
| return; | | return; | |
|
| basics.push_back( ElementMatcher( e , c, isNot ) ); | | _basics.push_back( ElementMatcher( e , c, isNot ) ); | |
| } | | } | |
| | | | |
| void addRegex(const char *fieldName, const char *regex, const char
*flags, bool isNot = false); | | void addRegex(const char *fieldName, const char *regex, const char
*flags, bool isNot = false); | |
| bool addOp( const BSONElement &e, const BSONElement &fe, bool isNot
, const char *& regex, const char *&flags ); | | bool addOp( const BSONElement &e, const BSONElement &fe, bool isNot
, const char *& regex, const char *&flags ); | |
| | | | |
| int valuesMatch(const BSONElement& l, const BSONElement& r, int op,
const ElementMatcher& bm); | | int valuesMatch(const BSONElement& l, const BSONElement& r, int op,
const ElementMatcher& bm); | |
| | | | |
|
| bool parseOrNor( const BSONElement &e, bool subMatcher ); | | bool parseClause( const BSONElement &e ); | |
| void parseOr( const BSONElement &e, bool subMatcher, list< shared_p
tr< Matcher > > &matchers ); | | void parseExtractedClause( const BSONElement &e, list< shared_ptr<
Matcher > > &matchers ); | |
| | | | |
|
| Where *where; // set if query uses $where | | void parseMatchExpressionElement( const BSONElement &e, bool nested
); | |
| BSONObj jsobj; // the query pattern. e.g., { name
: "joe" } | | | |
| BSONObj constrainIndexKey_; | | Where *_where; // set if query uses $where | |
| vector<ElementMatcher> basics; | | BSONObj _jsobj; // the query pattern. e.g., { nam
e: "joe" } | |
| bool haveSize; | | BSONObj _constrainIndexKey; | |
| bool all; | | vector<ElementMatcher> _basics; | |
| bool hasArray; | | bool _haveSize; | |
| bool haveNeg; | | bool _all; | |
| | | bool _hasArray; | |
| | | bool _haveNeg; | |
| | | | |
| /* $atomic - if true, a multi document operation (some removes, upd
ates) | | /* $atomic - if true, a multi document operation (some removes, upd
ates) | |
| should be done atomically. in that case, we do not yi
eld - | | should be done atomically. in that case, we do not yi
eld - | |
| i.e. we stay locked the whole time. | | i.e. we stay locked the whole time. | |
| http://www.mongodb.org/display/DOCS/Removing[ | | http://www.mongodb.org/display/DOCS/Removing[ | |
| */ | | */ | |
| bool _atomic; | | bool _atomic; | |
| | | | |
|
| RegexMatcher regexs[4]; | | RegexMatcher _regexs[4]; | |
| int nRegex; | | int _nRegex; | |
| | | | |
| // so we delete the mem when we're done: | | // so we delete the mem when we're done: | |
| vector< shared_ptr< BSONObjBuilder > > _builders; | | vector< shared_ptr< BSONObjBuilder > > _builders; | |
|
| | | list< shared_ptr< Matcher > > _andMatchers; | |
| list< shared_ptr< Matcher > > _orMatchers; | | list< shared_ptr< Matcher > > _orMatchers; | |
| list< shared_ptr< Matcher > > _norMatchers; | | list< shared_ptr< Matcher > > _norMatchers; | |
|
| vector< shared_ptr< FieldRangeVector > > _orConstraints; | | vector< shared_ptr< FieldRangeVector > > _orDedupConstraints; | |
| | | | |
| friend class CoveredIndexMatcher; | | friend class CoveredIndexMatcher; | |
| }; | | }; | |
| | | | |
| // If match succeeds on index key, then attempt to match full document. | | // If match succeeds on index key, then attempt to match full document. | |
| class CoveredIndexMatcher : boost::noncopyable { | | class CoveredIndexMatcher : boost::noncopyable { | |
| public: | | public: | |
| CoveredIndexMatcher(const BSONObj &pattern, const BSONObj &indexKey
Pattern , bool alwaysUseRecord=false ); | | CoveredIndexMatcher(const BSONObj &pattern, const BSONObj &indexKey
Pattern , bool alwaysUseRecord=false ); | |
|
| bool matches(const BSONObj &o){ return _docMatcher->matches( o ); } | | bool matches(const BSONObj &o) { return _docMatcher->matches( o );
} | |
| bool matches(const BSONObj &key, const DiskLoc &recLoc , MatchDetai
ls * details = 0 ); | | bool matchesWithSingleKeyIndex(const BSONObj &key, const DiskLoc &r
ecLoc , MatchDetails * details = 0 ) { | |
| | | return matches( key, recLoc, details, true ); | |
| | | } | |
| | | /** | |
| | | * This is the preferred method for matching against a cursor, as i
t | |
| | | * can handle both multi and single key cursors. | |
| | | */ | |
| bool matchesCurrent( Cursor * cursor , MatchDetails * details = 0 )
; | | bool matchesCurrent( Cursor * cursor , MatchDetails * details = 0 )
; | |
|
| bool needRecord(){ return _needRecord; } | | bool needRecord() { return _needRecord; } | |
| | | | |
| Matcher& docMatcher() { return *_docMatcher; } | | Matcher& docMatcher() { return *_docMatcher; } | |
| | | | |
| // once this is called, shouldn't use this matcher for matching any
more | | // once this is called, shouldn't use this matcher for matching any
more | |
| void advanceOrClause( const shared_ptr< FieldRangeVector > &frv ) { | | void advanceOrClause( const shared_ptr< FieldRangeVector > &frv ) { | |
|
| _docMatcher->addOrConstraint( frv ); | | _docMatcher->addOrDedupConstraint( frv ); | |
| // TODO this is not an optimal optimization, since we could ski
p an entire | | // TODO this is not yet optimal. Since we could skip an entire | |
| // or clause (if a match is impossible) between calls to advanc
eOrClause() | | // or clause (if a match is impossible) between calls to advanc
eOrClause() | |
|
| | | // we may not pop all the clauses we can. | |
| _docMatcher->popOrClause(); | | _docMatcher->popOrClause(); | |
| } | | } | |
| | | | |
| CoveredIndexMatcher *nextClauseMatcher( const BSONObj &indexKeyPatt
ern, bool alwaysUseRecord=false ) { | | CoveredIndexMatcher *nextClauseMatcher( const BSONObj &indexKeyPatt
ern, bool alwaysUseRecord=false ) { | |
| return new CoveredIndexMatcher( _docMatcher, indexKeyPattern, a
lwaysUseRecord ); | | return new CoveredIndexMatcher( _docMatcher, indexKeyPattern, a
lwaysUseRecord ); | |
| } | | } | |
|
| | | | |
| | | string toString() const; | |
| | | | |
| private: | | private: | |
|
| | | bool matches(const BSONObj &key, const DiskLoc &recLoc , MatchDetai
ls * details = 0 , bool keyUsable = true ); | |
| CoveredIndexMatcher(const shared_ptr< Matcher > &docMatcher, const
BSONObj &indexKeyPattern , bool alwaysUseRecord=false ); | | CoveredIndexMatcher(const shared_ptr< Matcher > &docMatcher, const
BSONObj &indexKeyPattern , bool alwaysUseRecord=false ); | |
| void init( bool alwaysUseRecord ); | | void init( bool alwaysUseRecord ); | |
| shared_ptr< Matcher > _docMatcher; | | shared_ptr< Matcher > _docMatcher; | |
| Matcher _keyMatcher; | | Matcher _keyMatcher; | |
|
| bool _needRecord; | | | |
| bool _useRecordOnly; | | bool _needRecord; // if the key itself isn't good enough to determi
ne a positive match | |
| }; | | }; | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 32 change blocks. |
| 67 lines changed or deleted | | 79 lines changed or added | |
|
| message.h | | message.h | |
|
| // Message.h | | // message.h | |
| | | | |
| /* Copyright 2009 10gen Inc. | | /* Copyright 2009 10gen Inc. | |
| * | | * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | | * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | | * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | | * You may obtain a copy of the License at | |
| * | | * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | | * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | | * | |
| * Unless required by applicable law or agreed to in writing, software | | * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | | * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | |
| * See the License for the specific language governing permissions and | | * See the License for the specific language governing permissions and | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
|
| #include "../util/sock.h" | | #include "sock.h" | |
| #include "../bson/util/atomic_int.h" | | #include "../../bson/util/atomic_int.h" | |
| #include "hostandport.h" | | #include "hostandport.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| extern bool noUnixSocket; | | | |
| | | | |
| class Message; | | class Message; | |
| class MessagingPort; | | class MessagingPort; | |
| class PiggyBackData; | | class PiggyBackData; | |
|
| typedef AtomicUInt MSGID; | | | |
| | | | |
| class Listener : boost::noncopyable { | | | |
| public: | | | |
| Listener(const string &ip, int p, bool logConnect=true ) : _port(p)
, _ip(ip), _logConnect(logConnect), _elapsedTime(0){ } | | | |
| virtual ~Listener() { | | | |
| if ( _timeTracker == this ) | | | |
| _timeTracker = 0; | | | |
| } | | | |
| void initAndListen(); // never returns unless error (start a thread
) | | | |
| | | | |
| /* spawn a thread, etc., then return */ | | | |
| virtual void accepted(int sock, const SockAddr& from); | | | |
| virtual void accepted(MessagingPort *mp){ | | | |
| assert(!"You must overwrite one of the accepted methods"); | | | |
| } | | | |
| | | | |
| const int _port; | | | |
| | | | |
| /** | | | |
| * @return a rough estimate of elepased time since the server start
ed | | | |
| */ | | | |
| long long getMyElapsedTimeMillis() const { return _elapsedTime; } | | | |
| | | | |
| void setAsTimeTracker(){ | | | |
| _timeTracker = this; | | | |
| } | | | |
| | | | |
| static const Listener* getTimeTracker(){ | | | |
| return _timeTracker; | | | |
| } | | | |
| | | | |
| static long long getElapsedTimeMillis() { | | | |
| if ( _timeTracker ) | | | |
| return _timeTracker->getMyElapsedTimeMillis(); | | | |
| return 0; | | | |
| } | | | |
| | | | |
| private: | | | |
| string _ip; | | | |
| bool _logConnect; | | | |
| long long _elapsedTime; | | | |
| | | | |
| static const Listener* _timeTracker; | | | |
| }; | | | |
| | | | |
| class AbstractMessagingPort : boost::noncopyable { | | | |
| public: | | | |
| virtual ~AbstractMessagingPort() { } | | | |
| virtual void reply(Message& received, Message& response, MSGID resp
onseTo) = 0; // like the reply below, but doesn't rely on received.data sti
ll being available | | | |
| virtual void reply(Message& received, Message& response) = 0; | | | |
| | | | |
| virtual HostAndPort remote() const = 0; | | | |
| virtual unsigned remotePort() const = 0; | | | |
| | | | |
| virtual int getClientId(){ | | | |
| int x = remotePort(); | | | |
| x = x << 16; | | | |
| return x; | | | |
| } | | | |
| }; | | | |
| | | | |
| class MessagingPort : public AbstractMessagingPort { | | | |
| public: | | | |
| MessagingPort(int sock, const SockAddr& farEnd); | | | |
| | | | |
| // in some cases the timeout will actually be 2x this value - eg we
do a partial send, | | | |
| // then the timeout fires, then we try to send again, then the time
out fires again with | | | |
| // no data sent, then we detect that the other side is down | | | |
| MessagingPort(int timeout = 0, int logLevel = 0 ); | | | |
| | | | |
| virtual ~MessagingPort(); | | | |
| | | | |
| void shutdown(); | | | |
| | | | |
| bool connect(SockAddr& farEnd); | | | |
| | | | |
| /* it's assumed if you reuse a message object, that it doesn't cros
s MessagingPort's. | | | |
| also, the Message data will go out of scope on the subsequent re
cv call. | | | |
| */ | | | |
| bool recv(Message& m); | | | |
| void reply(Message& received, Message& response, MSGID responseTo); | | | |
| void reply(Message& received, Message& response); | | | |
| bool call(Message& toSend, Message& response); | | | |
| void say(Message& toSend, int responseTo = -1); | | | |
| | | | |
| void piggyBack( Message& toSend , int responseTo = -1 ); | | | |
| | | | |
|
| virtual unsigned remotePort() const; | | typedef AtomicUInt MSGID; | |
| virtual HostAndPort remote() const; | | | |
| | | | |
| // send len or throw SocketException | | | |
| void send( const char * data , int len, const char *context ); | | | |
| void send( const vector< pair< char *, int > > &data, const char *c
ontext ); | | | |
| | | | |
| // recv len or throw SocketException | | | |
| void recv( char * data , int len ); | | | |
| | | | |
| int unsafe_recv( char *buf, int max ); | | | |
| private: | | | |
| int sock; | | | |
| PiggyBackData * piggyBackData; | | | |
| public: | | | |
| SockAddr farEnd; | | | |
| int _timeout; | | | |
| int _logLevel; // passed to log() when logging errors | | | |
| | | | |
| static void closeAllSockets(unsigned tagMask = 0xffffffff); | | | |
| | | | |
| /* ports can be tagged with various classes. see closeAllSockets(t
ag). defaults to 0. */ | | | |
| unsigned tag; | | | |
| | | | |
| friend class PiggyBackData; | | | |
| }; | | | |
| | | | |
| enum Operations { | | enum Operations { | |
| opReply = 1, /* reply. responseTo is set. */ | | opReply = 1, /* reply. responseTo is set. */ | |
| dbMsg = 1000, /* generic msg command followed by a string */ | | dbMsg = 1000, /* generic msg command followed by a string */ | |
| dbUpdate = 2001, /* update object */ | | dbUpdate = 2001, /* update object */ | |
| dbInsert = 2002, | | dbInsert = 2002, | |
| //dbGetByOID = 2003, | | //dbGetByOID = 2003, | |
| dbQuery = 2004, | | dbQuery = 2004, | |
| dbGetMore = 2005, | | dbGetMore = 2005, | |
| dbDelete = 2006, | | dbDelete = 2006, | |
| dbKillCursors = 2007 | | dbKillCursors = 2007 | |
| }; | | }; | |
| | | | |
| bool doesOpGetAResponse( int op ); | | bool doesOpGetAResponse( int op ); | |
| | | | |
|
| inline const char * opToString( int op ){ | | inline const char * opToString( int op ) { | |
| switch ( op ){ | | switch ( op ) { | |
| case 0: return "none"; | | case 0: return "none"; | |
| case opReply: return "reply"; | | case opReply: return "reply"; | |
| case dbMsg: return "msg"; | | case dbMsg: return "msg"; | |
| case dbUpdate: return "update"; | | case dbUpdate: return "update"; | |
| case dbInsert: return "insert"; | | case dbInsert: return "insert"; | |
| case dbQuery: return "query"; | | case dbQuery: return "query"; | |
| case dbGetMore: return "getmore"; | | case dbGetMore: return "getmore"; | |
| case dbDelete: return "remove"; | | case dbDelete: return "remove"; | |
| case dbKillCursors: return "killcursors"; | | case dbKillCursors: return "killcursors"; | |
| default: | | default: | |
| PRINT(op); | | PRINT(op); | |
| assert(0); | | assert(0); | |
| return ""; | | return ""; | |
| } | | } | |
| } | | } | |
| | | | |
|
| inline bool opIsWrite( int op ){ | | inline bool opIsWrite( int op ) { | |
| switch ( op ){ | | switch ( op ) { | |
| | | | |
| case 0: | | case 0: | |
| case opReply: | | case opReply: | |
| case dbMsg: | | case dbMsg: | |
| case dbQuery: | | case dbQuery: | |
| case dbGetMore: | | case dbGetMore: | |
| case dbKillCursors: | | case dbKillCursors: | |
| return false; | | return false; | |
| | | | |
| case dbUpdate: | | case dbUpdate: | |
| | | | |
| skipping to change at line 203 | | skipping to change at line 89 | |
| | | | |
| default: | | default: | |
| PRINT(op); | | PRINT(op); | |
| assert(0); | | assert(0); | |
| return ""; | | return ""; | |
| } | | } | |
| | | | |
| } | | } | |
| | | | |
| #pragma pack(1) | | #pragma pack(1) | |
|
| /* see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol | | /* see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol | |
| */ | | */ | |
| struct MSGHEADER { | | struct MSGHEADER { | |
| int messageLength; // total message size, including this | | int messageLength; // total message size, including this | |
| int requestID; // identifier for this message | | int requestID; // identifier for this message | |
| int responseTo; // requestID from the original request | | int responseTo; // requestID from the original request | |
| // (used in reponses from db) | | // (used in reponses from db) | |
| int opCode; | | int opCode; | |
| }; | | }; | |
| struct OP_GETMORE : public MSGHEADER { | | struct OP_GETMORE : public MSGHEADER { | |
| MSGHEADER header; // standard message header | | MSGHEADER header; // standard message header | |
| int ZERO_or_flags; // 0 - reserved for future use | | int ZERO_or_flags; // 0 - reserved for future use | |
| //cstring fullCollectionName; // "dbname.collectionname" | | //cstring fullCollectionName; // "dbname.collectionname" | |
| //int32 numberToReturn; // number of documents to return | | //int32 numberToReturn; // number of documents to return | |
| //int64 cursorID; // cursorID from the OP_REPLY | | //int64 cursorID; // cursorID from the OP_REPLY | |
| }; | | }; | |
| #pragma pack() | | #pragma pack() | |
| | | | |
| #pragma pack(1) | | #pragma pack(1) | |
| /* todo merge this with MSGHEADER (or inherit from it). */ | | /* todo merge this with MSGHEADER (or inherit from it). */ | |
| struct MsgData { | | struct MsgData { | |
| int len; /* len of the msg, including this field */ | | int len; /* len of the msg, including this field */ | |
| MSGID id; /* request/reply id's match... */ | | MSGID id; /* request/reply id's match... */ | |
| MSGID responseTo; /* id of the message we are responding to */ | | MSGID responseTo; /* id of the message we are responding to */ | |
|
| int _operation; | | short _operation; | |
| | | char _flags; | |
| | | char _version; | |
| int operation() const { | | int operation() const { | |
| return _operation; | | return _operation; | |
| } | | } | |
| void setOperation(int o) { | | void setOperation(int o) { | |
|
| | | _flags = 0; | |
| | | _version = 0; | |
| _operation = o; | | _operation = o; | |
| } | | } | |
| char _data[4]; | | char _data[4]; | |
| | | | |
| int& dataAsInt() { | | int& dataAsInt() { | |
| return *((int *) _data); | | return *((int *) _data); | |
| } | | } | |
| | | | |
|
| bool valid(){ | | bool valid() { | |
| if ( len <= 0 || len > ( 1024 * 1024 * 10 ) ) | | if ( len <= 0 || len > ( 4 * BSONObjMaxInternalSize ) ) | |
| return false; | | return false; | |
|
| if ( _operation < 0 || _operation > 100000 ) | | if ( _operation < 0 || _operation > 30000 ) | |
| return false; | | return false; | |
| return true; | | return true; | |
| } | | } | |
| | | | |
|
| long long getCursor(){ | | long long getCursor() { | |
| assert( responseTo > 0 ); | | assert( responseTo > 0 ); | |
| assert( _operation == opReply ); | | assert( _operation == opReply ); | |
| long long * l = (long long *)(_data + 4); | | long long * l = (long long *)(_data + 4); | |
| return l[0]; | | return l[0]; | |
| } | | } | |
| | | | |
| int dataLen(); // len without header | | int dataLen(); // len without header | |
| }; | | }; | |
| const int MsgDataHeaderSize = sizeof(MsgData) - 4; | | const int MsgDataHeaderSize = sizeof(MsgData) - 4; | |
| inline int MsgData::dataLen() { | | inline int MsgData::dataLen() { | |
| | | | |
| skipping to change at line 293 | | skipping to change at line 183 | |
| } | | } | |
| int operation() const { return header()->operation(); } | | int operation() const { return header()->operation(); } | |
| | | | |
| MsgData *singleData() const { | | MsgData *singleData() const { | |
| massert( 13273, "single data buffer expected", _buf ); | | massert( 13273, "single data buffer expected", _buf ); | |
| return header(); | | return header(); | |
| } | | } | |
| | | | |
| bool empty() const { return !_buf && _data.empty(); } | | bool empty() const { return !_buf && _data.empty(); } | |
| | | | |
|
| int size() const{ | | int size() const { | |
| int res = 0; | | int res = 0; | |
|
| if ( _buf ){ | | if ( _buf ) { | |
| res = _buf->len; | | res = _buf->len; | |
|
| } else { | | } | |
| for (MsgVec::const_iterator it = _data.begin(); it != _data
.end(); ++it){ | | else { | |
| | | for (MsgVec::const_iterator it = _data.begin(); it != _data
.end(); ++it) { | |
| res += it->second; | | res += it->second; | |
| } | | } | |
| } | | } | |
| return res; | | return res; | |
| } | | } | |
| | | | |
|
| | | int dataSize() const { return size() - sizeof(MSGHEADER); } | |
| | | | |
| // concat multiple buffers - noop if <2 buffers already, otherwise
can be expensive copy | | // concat multiple buffers - noop if <2 buffers already, otherwise
can be expensive copy | |
| // can get rid of this if we make response handling smarter | | // can get rid of this if we make response handling smarter | |
| void concat() { | | void concat() { | |
| if ( _buf || empty() ) { | | if ( _buf || empty() ) { | |
| return; | | return; | |
| } | | } | |
| | | | |
| assert( _freeIt ); | | assert( _freeIt ); | |
| int totalSize = 0; | | int totalSize = 0; | |
| for( vector< pair< char *, int > >::const_iterator i = _data.be
gin(); i != _data.end(); ++i ) { | | for( vector< pair< char *, int > >::const_iterator i = _data.be
gin(); i != _data.end(); ++i ) { | |
| | | | |
| skipping to change at line 398 | | skipping to change at line 291 | |
| memcpy(d->_data, msgdata, len); | | memcpy(d->_data, msgdata, len); | |
| d->len = fixEndian(dataLen); | | d->len = fixEndian(dataLen); | |
| d->setOperation(operation); | | d->setOperation(operation); | |
| _setData( d, true ); | | _setData( d, true ); | |
| } | | } | |
| | | | |
| bool doIFreeIt() { | | bool doIFreeIt() { | |
| return _freeIt; | | return _freeIt; | |
| } | | } | |
| | | | |
|
| void send( MessagingPort &p, const char *context ) { | | void send( MessagingPort &p, const char *context ); | |
| if ( empty() ) { | | | |
| return; | | string toString() const; | |
| } | | | |
| if ( _buf != 0 ) { | | | |
| p.send( (char*)_buf, _buf->len, context ); | | | |
| } else { | | | |
| p.send( _data, context ); | | | |
| } | | | |
| } | | | |
| | | | |
| private: | | private: | |
| void _setData( MsgData *d, bool freeIt ) { | | void _setData( MsgData *d, bool freeIt ) { | |
| _freeIt = freeIt; | | _freeIt = freeIt; | |
| _buf = d; | | _buf = d; | |
| } | | } | |
| // if just one buffer, keep it in _buf, otherwise keep a sequence o
f buffers in _data | | // if just one buffer, keep it in _buf, otherwise keep a sequence o
f buffers in _data | |
| MsgData * _buf; | | MsgData * _buf; | |
| // byte buffer(s) - the first must contain at least a full MsgData
unless using _buf for storage instead | | // byte buffer(s) - the first must contain at least a full MsgData
unless using _buf for storage instead | |
| typedef vector< pair< char*, int > > MsgVec; | | typedef vector< pair< char*, int > > MsgVec; | |
| MsgVec _data; | | MsgVec _data; | |
| bool _freeIt; | | bool _freeIt; | |
| }; | | }; | |
| | | | |
|
| class SocketException : public DBException { | | | |
| public: | | | |
| enum Type { CLOSED , RECV_ERROR , SEND_ERROR } type; | | | |
| SocketException( Type t ) : DBException( "socket exception" , 9001
) , type(t){} | | | |
| | | | |
| bool shouldPrint() const { | | | |
| return type != CLOSED; | | | |
| } | | | |
| | | | |
| }; | | | |
| | | | |
| MSGID nextMessageId(); | | MSGID nextMessageId(); | |
| | | | |
|
| void setClientId( int id ); | | | |
| int getClientId(); | | | |
| | | | |
| extern TicketHolder connTicketHolder; | | | |
| | | | |
| class ElapsedTracker { | | | |
| public: | | | |
| ElapsedTracker( int hitsBetweenMarks , int msBetweenMarks ) | | | |
| : _h( hitsBetweenMarks ) , _ms( msBetweenMarks ) , _pings(0){ | | | |
| _last = Listener::getElapsedTimeMillis(); | | | |
| } | | | |
| | | | |
| /** | | | |
| * call this for every iteration | | | |
| * returns true if one of the triggers has gone off | | | |
| */ | | | |
| bool ping(){ | | | |
| if ( ( ++_pings % _h ) == 0 ){ | | | |
| _last = Listener::getElapsedTimeMillis(); | | | |
| return true; | | | |
| } | | | |
| | | | |
| long long now = Listener::getElapsedTimeMillis(); | | | |
| if ( now - _last > _ms ){ | | | |
| _last = now; | | | |
| return true; | | | |
| } | | | |
| | | | |
| return false; | | | |
| } | | | |
| | | | |
| private: | | | |
| int _h; | | | |
| int _ms; | | | |
| | | | |
| unsigned long long _pings; | | | |
| | | | |
| long long _last; | | | |
| | | | |
| }; | | | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 20 change blocks. |
| 209 lines changed or deleted | | 43 lines changed or added | |
|
| mmap.h | | mmap.h | |
| | | | |
| skipping to change at line 19 | | skipping to change at line 19 | |
| * http://www.apache.org/licenses/LICENSE-2.0 | | * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | | * | |
| * Unless required by applicable law or agreed to in writing, software | | * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | | * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | |
| * See the License for the specific language governing permissions and | | * See the License for the specific language governing permissions and | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
|
| | | #include <boost/thread/xtime.hpp> | |
| | | #include "concurrency/rwlock.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| | | class MAdvise { | |
| | | void *_p; | |
| | | unsigned _len; | |
| | | public: | |
| | | enum Advice { Sequential=1 }; | |
| | | MAdvise(void *p, unsigned len, Advice a); | |
| | | ~MAdvise(); // destructor resets the range to MADV_NORMAL | |
| | | }; | |
| | | | |
| /* the administrative-ish stuff here */ | | /* the administrative-ish stuff here */ | |
| class MongoFile : boost::noncopyable { | | class MongoFile : boost::noncopyable { | |
|
| | | | |
| public: | | public: | |
| /** Flushable has to fail nicely if the underlying object gets kill
ed */ | | /** Flushable has to fail nicely if the underlying object gets kill
ed */ | |
| class Flushable { | | class Flushable { | |
| public: | | public: | |
|
| virtual ~Flushable(){} | | virtual ~Flushable() {} | |
| virtual void flush() = 0; | | virtual void flush() = 0; | |
| }; | | }; | |
| | | | |
|
| | | virtual ~MongoFile() {} | |
| | | | |
| | | enum Options { | |
| | | SEQUENTIAL = 1, // hint - e.g. FILE_FLAG_SEQUENTIAL_SCAN on win
dows | |
| | | READONLY = 2 // not contractually guaranteed, but if specifi
ed the impl has option to fault writes | |
| | | }; | |
| | | | |
| | | /** @param fun is called for each MongoFile. | |
| | | calledl from within a mutex that MongoFile uses. so be careful
not to deadlock. | |
| | | */ | |
| | | template < class F > | |
| | | static void forEach( F fun ); | |
| | | | |
| | | /** note: you need to be in mmmutex when using this. forEach (above
) handles that for you automatically. | |
| | | */ | |
| | | static set<MongoFile*>& getAllFiles() { return mmfiles; } | |
| | | | |
| | | // callbacks if you need them | |
| | | static void (*notifyPreFlush)(); | |
| | | static void (*notifyPostFlush)(); | |
| | | | |
| | | static int flushAll( bool sync ); // returns n flushed | |
| | | static long long totalMappedLength(); | |
| | | static void closeAllFiles( stringstream &message ); | |
| | | | |
| | | #if defined(_DEBUG) | |
| | | static void markAllWritable(); | |
| | | static void unmarkAllWritable(); | |
| | | #else | |
| | | static void markAllWritable() { } | |
| | | static void unmarkAllWritable() { } | |
| | | #endif | |
| | | | |
| | | static bool exists(boost::filesystem::path p) { return boost::files
ystem::exists(p); } | |
| | | | |
| | | virtual bool isMongoMMF() { return false; } | |
| | | | |
| | | string filename() const { return _filename; } | |
| | | void setFilename(string fn); | |
| | | | |
| | | private: | |
| | | string _filename; | |
| | | static int _flushAll( bool sync ); // returns n flushed | |
| protected: | | protected: | |
| virtual void close() = 0; | | virtual void close() = 0; | |
| virtual void flush(bool sync) = 0; | | virtual void flush(bool sync) = 0; | |
| /** | | /** | |
| * returns a thread safe object that you can call flush on | | * returns a thread safe object that you can call flush on | |
| * Flushable has to fail nicely if the underlying object gets kille
d | | * Flushable has to fail nicely if the underlying object gets kille
d | |
| */ | | */ | |
| virtual Flushable * prepareFlush() = 0; | | virtual Flushable * prepareFlush() = 0; | |
| | | | |
| void created(); /* subclass must call after create */ | | void created(); /* subclass must call after create */ | |
|
| void destroyed(); /* subclass must call in destructor */ | | | |
| | | /* subclass must call in destructor (or at close). | |
| | | removes this from pathToFile and other maps | |
| | | safe to call more than once, albeit might be wasted work | |
| | | ideal to call close to the close, if the close is well before ob
ject destruction | |
| | | */ | |
| | | void destroyed(); | |
| | | | |
| | | virtual unsigned long long length() const = 0; | |
| | | | |
| // only supporting on posix mmap | | // only supporting on posix mmap | |
| virtual void _lock() {} | | virtual void _lock() {} | |
| virtual void _unlock() {} | | virtual void _unlock() {} | |
| | | | |
|
| | | static set<MongoFile*> mmfiles; | |
| public: | | public: | |
|
| virtual ~MongoFile() {} | | static map<string,MongoFile*> pathToFile; | |
| virtual long length() = 0; | | | |
| | | | |
| enum Options { | | | |
| SEQUENTIAL = 1 // hint - e.g. FILE_FLAG_SEQUENTIAL_SCAN on wind
ows | | | |
| }; | | | |
| | | | |
|
| static int flushAll( bool sync ); // returns n flushed | | // lock order: lock dbMutex before this if you lock both | |
| static long long totalMappedLength(); | | static RWLockRecursive mmmutex; | |
| static void closeAllFiles( stringstream &message ); | | }; | |
| | | | |
|
| // Locking allows writes. Reads are always allowed | | /** look up a MMF by filename. scoped mutex locking convention. | |
| static void lockAll(); | | example: | |
| static void unlockAll(); | | MMFFinderByName finder; | |
| | | MongoMMF *a = finder.find("file_name_a"); | |
| | | MongoMMF *b = finder.find("file_name_b"); | |
| | | */ | |
| | | class MongoFileFinder : boost::noncopyable { | |
| | | public: | |
| | | MongoFileFinder() : _lk(MongoFile::mmmutex) { } | |
| | | | |
|
| /* can be "overriden" if necessary */ | | /** @return The MongoFile object associated with the specified file
name. If no file is open | |
| static bool exists(boost::filesystem::path p) { | | with the specified name, returns null. | |
| return boost::filesystem::exists(p); | | */ | |
| | | MongoFile* findByPath(string path) { | |
| | | map<string,MongoFile*>::iterator i = MongoFile::pathToFile.find
(path); | |
| | | return i == MongoFile::pathToFile.end() ? NULL : i->second; | |
| } | | } | |
|
| }; | | | |
| | | | |
|
| #ifndef _DEBUG | | private: | |
| // no-ops in production | | RWLockRecursive::Shared _lk; | |
| inline void MongoFile::lockAll() {} | | }; | |
| inline void MongoFile::unlockAll() {} | | | |
| | | | |
| #endif | | | |
| | | | |
| struct MongoFileAllowWrites { | | struct MongoFileAllowWrites { | |
|
| MongoFileAllowWrites(){ | | MongoFileAllowWrites() { | |
| MongoFile::lockAll(); | | MongoFile::markAllWritable(); | |
| } | | } | |
|
| ~MongoFileAllowWrites(){ | | ~MongoFileAllowWrites() { | |
| MongoFile::unlockAll(); | | MongoFile::unmarkAllWritable(); | |
| } | | } | |
| }; | | }; | |
| | | | |
|
| /** template for what a new storage engine's class definition must impl
ement | | | |
| PRELIMINARY - subject to change. | | | |
| */ | | | |
| class StorageContainerTemplate : public MongoFile { | | | |
| protected: | | | |
| virtual void close(); | | | |
| virtual void flush(bool sync); | | | |
| public: | | | |
| virtual long length(); | | | |
| | | | |
| /** pointer to a range of space in this storage unit */ | | | |
| class Pointer { | | | |
| public: | | | |
| /** retried address of buffer at offset 'offset' withing the st
orage unit. returned range is a contiguous | | | |
| buffer reflecting what is in storage. caller will not read
or write past 'len'. | | | |
| | | | |
| note calls may be received that are at different points in
a range and different lengths. however | | | |
| for now assume that on writes, if a call is made, previousl
y returned addresses are no longer valid. i.e. | | | |
| p = at(10000, 500); | | | |
| q = at(10000, 600); | | | |
| after the second call it is ok if p is invalid. | | | |
| */ | | | |
| void* at(int offset, int len); | | | |
| | | | |
| /** indicate that we wrote to the range (from a previous at() c
all) and that it needs | | | |
| flushing to disk. | | | |
| */ | | | |
| void written(int offset, int len); | | | |
| | | | |
| bool isNull() const; | | | |
| }; | | | |
| | | | |
| /** commit written() calls from above. */ | | | |
| void commit(); | | | |
| | | | |
| Pointer open(const char *filename); | | | |
| Pointer open(const char *_filename, long &length, int options=0); | | | |
| }; | | | |
| | | | |
| class MemoryMappedFile : public MongoFile { | | class MemoryMappedFile : public MongoFile { | |
|
| | | protected: | |
| | | virtual void* viewForFlushing() { | |
| | | if( views.size() == 0 ) | |
| | | return 0; | |
| | | assert( views.size() == 1 ); | |
| | | return views[0]; | |
| | | } | |
| public: | | public: | |
|
| class Pointer { | | | |
| char *_base; | | | |
| public: | | | |
| Pointer() : _base(0) { } | | | |
| Pointer(void *p) : _base((char*) p) { } | | | |
| void* at(int offset, int maxLen) { return _base + offset; } | | | |
| void grow(int offset, int len) { /* no action requir
ed with mem mapped file */ } | | | |
| bool isNull() const { return _base == 0; } | | | |
| }; | | | |
| | | | |
| MemoryMappedFile(); | | MemoryMappedFile(); | |
|
| ~MemoryMappedFile() { | | | |
| destroyed(); | | virtual ~MemoryMappedFile() { | |
| | | RWLockRecursive::Exclusive lk(mmmutex); | |
| close(); | | close(); | |
| } | | } | |
|
| void close(); | | | |
| | | | |
|
| // Throws exception if file doesn't exist. (dm may2010: not sure if
this is always true?) | | virtual void close(); | |
| void* map( const char *filename ); | | | |
| | | | |
|
| /*To replace map(): | | // Throws exception if file doesn't exist. (dm may2010: not sure if
this is always true?) | |
| | | void* map(const char *filename); | |
| | | | |
|
| Pointer open( const char *filename ) { | | /** @param options see MongoFile::Options */ | |
| void *p = map(filename); | | void* mapWithOptions(const char *filename, int options); | |
| uassert(13077, "couldn't open/map file", p); | | | |
| return Pointer(p); | | | |
| }*/ | | | |
| | | | |
| /* Creates with length if DNE, otherwise uses existing file length, | | /* Creates with length if DNE, otherwise uses existing file length, | |
| passed length. | | passed length. | |
|
| | | @param options MongoFile::Options bits | |
| */ | | */ | |
|
| void* map(const char *filename, long &length, int options = 0 ); | | void* map(const char *filename, unsigned long long &length, int opt
ions = 0 ); | |
| | | | |
| | | /* Create. Must not exist. | |
| | | @param zero fill file with zeros when true | |
| | | */ | |
| | | void* create(string filename, unsigned long long len, bool zero); | |
| | | | |
| void flush(bool sync); | | void flush(bool sync); | |
| virtual Flushable * prepareFlush(); | | virtual Flushable * prepareFlush(); | |
| | | | |
|
| /*void* viewOfs() { | | long shortLength() const { return (long) len; } | |
| return view; | | unsigned long long length() const { return len; } | |
| }*/ | | | |
| | | | |
|
| long length() { | | /** create a new view with the specified properties. | |
| return len; | | automatically cleaned up upon close/destruction of the MemoryMa
ppedFile object. | |
| } | | */ | |
| | | void* createReadOnlyMap(); | |
| | | void* createPrivateMap(); | |
| | | | |
|
| string filename() const { return _filename; } | | /** make the private map range writable (necessary for our windows
implementation) */ | |
| | | static void makeWritable(void *, unsigned len) | |
| | | #if defined(_WIN32) | |
| | | ; | |
| | | #else | |
| | | { } | |
| | | #endif | |
| | | | |
| private: | | private: | |
|
| static void updateLength( const char *filename, long &length ); | | static void updateLength( const char *filename, unsigned long long
&length ); | |
| | | | |
| HANDLE fd; | | HANDLE fd; | |
| HANDLE maphandle; | | HANDLE maphandle; | |
|
| void *view; | | vector<void *> views; | |
| long len; | | unsigned long long len; | |
| string _filename; | | | |
| | | | |
| #ifdef _WIN32 | | #ifdef _WIN32 | |
| boost::shared_ptr<mutex> _flushMutex; | | boost::shared_ptr<mutex> _flushMutex; | |
|
| | | void clearWritableBits(void *privateView); | |
| | | public: | |
| | | static const unsigned ChunkSize = 64 * 1024 * 1024; | |
| | | static const unsigned NChunks = 1024 * 1024; | |
| | | #else | |
| | | void clearWritableBits(void *privateView) { } | |
| #endif | | #endif | |
| | | | |
| protected: | | protected: | |
| // only posix mmap implementations will support this | | // only posix mmap implementations will support this | |
| virtual void _lock(); | | virtual void _lock(); | |
| virtual void _unlock(); | | virtual void _unlock(); | |
| | | | |
|
| | | /** close the current private view and open a new replacement */ | |
| | | void* remapPrivateView(void *oldPrivateAddr); | |
| }; | | }; | |
| | | | |
|
| void printMemInfo( const char * where ); | | typedef MemoryMappedFile MMF; | |
| | | | |
|
| #include "ramstore.h" | | /** p is called from within a mutex that MongoFile uses. so be careful
not to deadlock. */ | |
| | | template < class F > | |
| | | inline void MongoFile::forEach( F p ) { | |
| | | RWLockRecursive::Shared lklk(mmmutex); | |
| | | for ( set<MongoFile*>::iterator i = mmfiles.begin(); i != mmfiles.e
nd(); i++ ) | |
| | | p(*i); | |
| | | } | |
| | | | |
| | | #if defined(_WIN32) | |
| | | class ourbitset { | |
| | | volatile unsigned bits[MemoryMappedFile::NChunks]; // volatile as w
e are doing double check locking | |
| | | public: | |
| | | ourbitset() { | |
| | | memset((void*) bits, 0, sizeof(bits)); | |
| | | } | |
| | | bool get(unsigned i) const { | |
| | | unsigned x = i / 32; | |
| | | assert( x < MemoryMappedFile::NChunks ); | |
| | | return (bits[x] & (1 << (i%32))) != 0; | |
| | | } | |
| | | void set(unsigned i) { | |
| | | unsigned x = i / 32; | |
| | | wassert( x < (MemoryMappedFile::NChunks*2/3) ); // warn if gett
ing close to limit | |
| | | assert( x < MemoryMappedFile::NChunks ); | |
| | | bits[x] |= (1 << (i%32)); | |
| | | } | |
| | | void clear(unsigned i) { | |
| | | unsigned x = i / 32; | |
| | | assert( x < MemoryMappedFile::NChunks ); | |
| | | bits[x] &= ~(1 << (i%32)); | |
| | | } | |
| | | }; | |
| | | extern ourbitset writable; | |
| | | void makeChunkWritable(size_t chunkno); | |
| | | inline void MemoryMappedFile::makeWritable(void *_p, unsigned len) { | |
| | | size_t p = (size_t) _p; | |
| | | unsigned a = p/ChunkSize; | |
| | | unsigned b = (p+len)/ChunkSize; | |
| | | for( unsigned i = a; i <= b; i++ ) { | |
| | | if( !writable.get(i) ) { | |
| | | makeChunkWritable(i); | |
| | | } | |
| | | } | |
| | | } | |
| | | | |
|
| //#define _RAMSTORE | | | |
| #if defined(_RAMSTORE) | | | |
| typedef RamStoreFile MMF; | | | |
| #else | | | |
| typedef MemoryMappedFile MMF; | | | |
| #endif | | #endif | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 35 change blocks. |
| 108 lines changed or deleted | | 183 lines changed or added | |
|
| namespace.h | | namespace.h | |
| | | | |
| skipping to change at line 23 | | skipping to change at line 23 | |
| * GNU Affero General Public License for more details. | | * GNU Affero General Public License for more details. | |
| * | | * | |
| * You should have received a copy of the GNU Affero General Public Licen
se | | * You should have received a copy of the GNU Affero General Public Licen
se | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../pch.h" | | #include "../pch.h" | |
| #include "jsobj.h" | | #include "jsobj.h" | |
|
| #include "queryutil.h" | | #include "querypattern.h" | |
| #include "diskloc.h" | | #include "diskloc.h" | |
| #include "../util/hashtab.h" | | #include "../util/hashtab.h" | |
|
| #include "../util/mmap.h" | | #include "mongommf.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| /* in the mongo source code, "client" means "database". */ | | /* in the mongo source code, "client" means "database". */ | |
| | | | |
| const int MaxDatabaseLen = 256; // max str len for the db name, includi
ng null char | | | |
| | | | |
|
| // "database.a.b.c" -> "database" | | const int MaxDatabaseNameLen = 256; // max str len for the db name, inc
luding null char | |
| inline void nsToDatabase(const char *ns, char *database) { | | | |
| const char *p = ns; | | | |
| char *q = database; | | | |
| while ( *p != '.' ) { | | | |
| if ( *p == 0 ) | | | |
| break; | | | |
| *q++ = *p++; | | | |
| } | | | |
| *q = 0; | | | |
| if (q-database>=MaxDatabaseLen) { | | | |
| log() << "nsToDatabase: ns too long. terminating, buf overrun c
ondition" << endl; | | | |
| dbexit( EXIT_POSSIBLE_CORRUPTION ); | | | |
| } | | | |
| } | | | |
| inline string nsToDatabase(const char *ns) { | | | |
| char buf[MaxDatabaseLen]; | | | |
| nsToDatabase(ns, buf); | | | |
| return buf; | | | |
| } | | | |
| inline string nsToDatabase(const string& ns) { | | | |
| size_t i = ns.find( '.' ); | | | |
| if ( i == string::npos ) | | | |
| return ns; | | | |
| return ns.substr( 0 , i ); | | | |
| } | | | |
| | | | |
|
| /* e.g. | | /* e.g. | |
| NamespaceString ns("acme.orders"); | | NamespaceString ns("acme.orders"); | |
| cout << ns.coll; // "orders" | | cout << ns.coll; // "orders" | |
| */ | | */ | |
| class NamespaceString { | | class NamespaceString { | |
| public: | | public: | |
| string db; | | string db; | |
| string coll; // note collection names can have periods in them for
organizing purposes (e.g. "system.indexes") | | string coll; // note collection names can have periods in them for
organizing purposes (e.g. "system.indexes") | |
|
| | | | |
| | | NamespaceString( const char * ns ) { init(ns); } | |
| | | NamespaceString( const string& ns ) { init(ns.c_str()); } | |
| | | string ns() const { return db + '.' + coll; } | |
| | | bool isSystem() const { return strncmp(coll.c_str(), "system.", 7)
== 0; } | |
| | | | |
| | | /** | |
| | | * @return true if ns is 'normal'. $ used for collections holding
index data, which do not contain BSON objects in their records. | |
| | | * special case for the local.oplog.$main ns -- naming it as such w
as a mistake. | |
| | | */ | |
| | | static bool normal(const char* ns) { | |
| | | const char *p = strchr(ns, '$'); | |
| | | if( p == 0 ) | |
| | | return true; | |
| | | return strcmp( ns, "local.oplog.$main" ) == 0; | |
| | | } | |
| | | | |
| | | static bool special(const char *ns) { | |
| | | return !normal(ns) || strstr(ns, ".system."); | |
| | | } | |
| private: | | private: | |
| void init(const char *ns) { | | void init(const char *ns) { | |
| const char *p = strchr(ns, '.'); | | const char *p = strchr(ns, '.'); | |
| if( p == 0 ) return; | | if( p == 0 ) return; | |
| db = string(ns, p - ns); | | db = string(ns, p - ns); | |
| coll = p + 1; | | coll = p + 1; | |
| } | | } | |
|
| public: | | | |
| NamespaceString( const char * ns ) { init(ns); } | | | |
| NamespaceString( const string& ns ) { init(ns.c_str()); } | | | |
| | | | |
| string ns() const { | | | |
| return db + '.' + coll; | | | |
| } | | | |
| | | | |
| bool isSystem() { | | | |
| return strncmp(coll.c_str(), "system.", 7) == 0; | | | |
| } | | | |
| }; | | }; | |
| | | | |
| #pragma pack(1) | | #pragma pack(1) | |
|
| /* This helper class is used to make the HashMap below in NamespaceD
etails */ | | /* This helper class is used to make the HashMap below in NamespaceDeta
ils e.g. see line: | |
| | | HashTable<Namespace,NamespaceDetails> *ht; | |
| | | */ | |
| class Namespace { | | class Namespace { | |
| public: | | public: | |
|
| enum MaxNsLenValue { MaxNsLen = 128 }; | | explicit Namespace(const char *ns) { *this = ns; } | |
| Namespace(const char *ns) { | | Namespace& operator=(const char *ns); | |
| *this = ns; | | | |
| } | | | |
| Namespace& operator=(const char *ns) { | | | |
| uassert( 10080 , "ns name too long, max size is 128", strlen(ns
) < MaxNsLen); | | | |
| //memset(buf, 0, MaxNsLen); /* this is just to keep stuff clean
in the files for easy dumping and reading */ | | | |
| strcpy_s(buf, MaxNsLen, ns); | | | |
| return *this; | | | |
| } | | | |
| | | | |
|
| /* for more than 10 indexes -- see NamespaceDetails::Extra */ | | | |
| string extraName(int i) { | | | |
| char ex[] = "$extra"; | | | |
| ex[5] += i; | | | |
| string s = string(buf) + ex; | | | |
| massert( 10348 , "$extra: ns name too long", s.size() < MaxNsLe
n); | | | |
| return s; | | | |
| } | | | |
| bool isExtra() const { | | | |
| const char *p = strstr(buf, "$extr"); | | | |
| return p && p[5] && p[6] == 0; //==0 important in case an index
uses name "$extra_1" for example | | | |
| } | | | |
| bool hasDollarSign() const { return strchr( buf , '$' ) > 0; } | | bool hasDollarSign() const { return strchr( buf , '$' ) > 0; } | |
| void kill() { buf[0] = 0x7f; } | | void kill() { buf[0] = 0x7f; } | |
| bool operator==(const char *r) const { return strcmp(buf, r) == 0;
} | | bool operator==(const char *r) const { return strcmp(buf, r) == 0;
} | |
| bool operator==(const Namespace& r) const { return strcmp(buf, r.bu
f) == 0; } | | bool operator==(const Namespace& r) const { return strcmp(buf, r.bu
f) == 0; } | |
|
| int hash() const { | | int hash() const; // value returned is always > 0 | |
| unsigned x = 0; | | | |
| const char *p = buf; | | | |
| while ( *p ) { | | | |
| x = x * 131 + *p; | | | |
| p++; | | | |
| } | | | |
| return (x & 0x7fffffff) | 0x8000000; // must be > 0 | | | |
| } | | | |
| | | | |
|
| /** | | size_t size() const { return strlen( buf ); } | |
| ( foo.bar ).getSisterNS( "blah" ) == foo.blah | | | |
| perhaps this should move to the NamespaceString helper? | | | |
| */ | | | |
| string getSisterNS( const char * local ) { | | | |
| assert( local && local[0] != '.' ); | | | |
| string old(buf); | | | |
| if ( old.find( "." ) != string::npos ) | | | |
| old = old.substr( 0 , old.find( "." ) ); | | | |
| return old + "." + local; | | | |
| } | | | |
| | | | |
|
| string toString() const { | | string toString() const { return (string) buf; } | |
| return (string)buf; | | operator string() const { return (string) buf; } | |
| } | | | |
| | | | |
|
| operator string() const { | | /* NamespaceDetails::Extra was added after fact to allow chaining o
f data blocks to support more than 10 indexes | |
| return (string)buf; | | (more than 10 IndexDetails). It's a bit hacky because of this l
ate addition with backward | |
| } | | file support. */ | |
| | | string extraName(int i) const; | |
| | | bool isExtra() const; /* ends with $extr... -- when true an extra b
lock not a normal NamespaceDetails block */ | |
| | | | |
|
| | | /** ( foo.bar ).getSisterNS( "blah" ) == foo.blah | |
| | | perhaps this should move to the NamespaceString helper? | |
| | | */ | |
| | | string getSisterNS( const char * local ) const; | |
| | | | |
| | | enum MaxNsLenValue { MaxNsLen = 128 }; | |
| | | private: | |
| char buf[MaxNsLen]; | | char buf[MaxNsLen]; | |
| }; | | }; | |
| #pragma pack() | | #pragma pack() | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
| #include "index.h" | | #include "index.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| /** @return true if a client can modify this namespace | | /** @return true if a client can modify this namespace even though it i
s under ".system." | |
| things like *.system.users */ | | For example <dbname>.system.users is ok for regular clients to upda
te. | |
| | | @param write used when .system.js | |
| | | */ | |
| bool legalClientSystemNS( const string& ns , bool write ); | | bool legalClientSystemNS( const string& ns , bool write ); | |
| | | | |
| /* deleted lists -- linked lists of deleted records -- are placed in 'b
uckets' of various sizes | | /* deleted lists -- linked lists of deleted records -- are placed in 'b
uckets' of various sizes | |
| so you can look for a deleterecord about the right size. | | so you can look for a deleterecord about the right size. | |
| */ | | */ | |
| const int Buckets = 19; | | const int Buckets = 19; | |
| const int MaxBucket = 18; | | const int MaxBucket = 18; | |
| | | | |
| extern int bucketSizes[]; | | extern int bucketSizes[]; | |
| | | | |
| #pragma pack(1) | | #pragma pack(1) | |
|
| /* this is the "header" for a collection that has all its details. in
the .ns file. | | /* NamespaceDetails : this is the "header" for a collection that has al
l its details. | |
| | | It's in the .ns file and this is a memory mapped region (thus the pa
ck pragma above). | |
| */ | | */ | |
| class NamespaceDetails { | | class NamespaceDetails { | |
|
| friend class NamespaceIndex; | | | |
| enum { NIndexesExtra = 30, | | | |
| NIndexesBase = 10 | | | |
| }; | | | |
| public: | | public: | |
|
| struct ExtraOld { | | enum { NIndexesMax = 64, NIndexesExtra = 30, NIndexesBase = 10 }; | |
| // note we could use this field for more chaining later, so don
't waste it: | | | |
| unsigned long long reserved1; | | /*-------- data fields, as present on disk : */ | |
| IndexDetails details[NIndexesExtra]; | | DiskLoc firstExtent; | |
| unsigned reserved2; | | DiskLoc lastExtent; | |
| unsigned reserved3; | | /* NOTE: capped collections v1 override the meaning of deletedList. | |
| }; | | deletedList[0] points to a list of free records (DeletedRe
cord's) for all extents in | |
| | | the capped namespace. | |
| | | deletedList[1] points to the last record in the prev exten
t. When the "current extent" | |
| | | changes, this value is updated. !deletedList[1].isValid()
when this value is not | |
| | | yet computed. | |
| | | */ | |
| | | DiskLoc deletedList[Buckets]; | |
| | | // ofs 168 (8 byte aligned) | |
| | | struct Stats { | |
| | | // datasize and nrecords MUST Be adjacent code assumes! | |
| | | long long datasize; // this includes padding, but not record he
aders | |
| | | long long nrecords; | |
| | | } stats; | |
| | | int lastExtentSize; | |
| | | int nIndexes; | |
| | | private: | |
| | | // ofs 192 | |
| | | IndexDetails _indexes[NIndexesBase]; | |
| | | public: | |
| | | // ofs 352 (16 byte aligned) | |
| | | int capped; | |
| | | int max; // max # of objects for a cap
ped table. TODO: should this be 64 bit? | |
| | | double paddingFactor; // 1.0 = no padding. | |
| | | // ofs 386 (16) | |
| | | int flags; | |
| | | DiskLoc capExtent; | |
| | | DiskLoc capFirstNewRecord; | |
| | | unsigned short dataFileVersion; // NamespaceDetails version.
So we can do backward compatibility in the future. See filever.h | |
| | | unsigned short indexFileVersion; | |
| | | unsigned long long multiKeyIndexBits; | |
| | | private: | |
| | | // ofs 400 (16) | |
| | | unsigned long long reservedA; | |
| | | long long extraOffset; // where the $extra info is l
ocated (bytes relative to this) | |
| | | public: | |
| | | int indexBuildInProgress; // 1 if in prog | |
| | | unsigned reservedB; | |
| | | // ofs 424 (8) | |
| | | struct Capped2 { | |
| | | unsigned long long cc2_ptr; // see capped.cpp | |
| | | unsigned fileNumber; | |
| | | } capped2; | |
| | | char reserved[60]; | |
| | | /*-------- end data 496 bytes */ | |
| | | | |
| | | explicit NamespaceDetails( const DiskLoc &loc, bool _capped ); | |
| | | | |
| class Extra { | | class Extra { | |
| long long _next; | | long long _next; | |
|
| public: | | public: | |
| IndexDetails details[NIndexesExtra]; | | IndexDetails details[NIndexesExtra]; | |
|
| private: | | private: | |
| unsigned reserved2; | | unsigned reserved2; | |
| unsigned reserved3; | | unsigned reserved3; | |
|
| Extra(const Extra&) { assert(false); } | | Extra(const Extra&) { assert(false); } | |
| Extra& operator=(const Extra& r) { assert(false); re
turn *this; } | | Extra& operator=(const Extra& r) { assert(false); return *this;
} | |
| public: | | public: | |
| Extra() { } | | Extra() { } | |
| long ofsFrom(NamespaceDetails *d) { | | long ofsFrom(NamespaceDetails *d) { | |
| return ((char *) this) - ((char *) d); | | return ((char *) this) - ((char *) d); | |
| } | | } | |
| void init() { memset(this, 0, sizeof(Extra)); } | | void init() { memset(this, 0, sizeof(Extra)); } | |
| Extra* next(NamespaceDetails *d) { | | Extra* next(NamespaceDetails *d) { | |
| if( _next == 0 ) return 0; | | if( _next == 0 ) return 0; | |
| return (Extra*) (((char *) d) + _next); | | return (Extra*) (((char *) d) + _next); | |
| } | | } | |
|
| void setNext(long ofs) { _next = ofs; } | | void setNext(long ofs) { *getDur().writing(&_next) = ofs; } | |
| void copy(NamespaceDetails *d, const Extra& e) { | | void copy(NamespaceDetails *d, const Extra& e) { | |
| memcpy(this, &e, sizeof(Extra)); | | memcpy(this, &e, sizeof(Extra)); | |
| _next = 0; | | _next = 0; | |
| } | | } | |
|
| }; // Extra | | }; | |
| | | | |
| Extra* extra() { | | Extra* extra() { | |
| if( extraOffset == 0 ) return 0; | | if( extraOffset == 0 ) return 0; | |
| return (Extra *) (((char *) this) + extraOffset); | | return (Extra *) (((char *) this) + extraOffset); | |
| } | | } | |
|
| | | | |
| public: | | | |
| /* add extra space for indexes when more than 10 */ | | /* add extra space for indexes when more than 10 */ | |
| Extra* allocExtra(const char *ns, int nindexessofar); | | Extra* allocExtra(const char *ns, int nindexessofar); | |
|
| | | | |
| void copyingFrom(const char *thisns, NamespaceDetails *src); // mus
t be called when renaming a NS to fix up extra | | void copyingFrom(const char *thisns, NamespaceDetails *src); // mus
t be called when renaming a NS to fix up extra | |
| | | | |
|
| enum { NIndexesMax = 64 }; | | | |
| | | | |
| BOOST_STATIC_ASSERT( NIndexesMax <= NIndexesBase + NIndexesExtra*2
); | | | |
| BOOST_STATIC_ASSERT( NIndexesMax <= 64 ); // multiKey bits | | | |
| BOOST_STATIC_ASSERT( sizeof(NamespaceDetails::ExtraOld) == 4
96 ); | | | |
| BOOST_STATIC_ASSERT( sizeof(NamespaceDetails::Extra) == 496
); | | | |
| | | | |
| /* called when loaded from disk */ | | /* called when loaded from disk */ | |
| void onLoad(const Namespace& k); | | void onLoad(const Namespace& k); | |
| | | | |
|
| NamespaceDetails( const DiskLoc &loc, bool _capped ); | | /* dump info on this namespace. for debugging. */ | |
| | | void dump(const Namespace& k); | |
| DiskLoc firstExtent; | | | |
| DiskLoc lastExtent; | | | |
| | | | |
| /* NOTE: capped collections override the meaning of deleted list. | | | |
| deletedList[0] points to a list of free records (DeletedRe
cord's) for all extents in | | | |
| the capped namespace. | | | |
| deletedList[1] points to the last record in the prev exten
t. When the "current extent" | | | |
| changes, this value is updated. !deletedList[1].isValid()
when this value is not | | | |
| yet computed. | | | |
| */ | | | |
| DiskLoc deletedList[Buckets]; | | | |
| | | | |
|
| | | /* dump info on all extents for this namespace. for debugging. */ | |
| void dumpExtents(); | | void dumpExtents(); | |
| | | | |
|
| long long datasize; | | | |
| long long nrecords; | | | |
| int lastExtentSize; | | | |
| int nIndexes; | | | |
| | | | |
| private: | | | |
| IndexDetails _indexes[NIndexesBase]; | | | |
| | | | |
| private: | | private: | |
| Extent *theCapExtent() const { return capExtent.ext(); } | | Extent *theCapExtent() const { return capExtent.ext(); } | |
| void advanceCapExtent( const char *ns ); | | void advanceCapExtent( const char *ns ); | |
| DiskLoc __capAlloc(int len); | | DiskLoc __capAlloc(int len); | |
| DiskLoc cappedAlloc(const char *ns, int len); | | DiskLoc cappedAlloc(const char *ns, int len); | |
| DiskLoc &cappedFirstDeletedInCurExtent(); | | DiskLoc &cappedFirstDeletedInCurExtent(); | |
| bool nextIsInCapExtent( const DiskLoc &dl ) const; | | bool nextIsInCapExtent( const DiskLoc &dl ) const; | |
|
| | | | |
| public: | | public: | |
| DiskLoc& cappedListOfAllDeletedRecords() { return deletedList[0]; } | | DiskLoc& cappedListOfAllDeletedRecords() { return deletedList[0]; } | |
| DiskLoc& cappedLastDelRecLastExtent() { return deletedList[1]; } | | DiskLoc& cappedLastDelRecLastExtent() { return deletedList[1]; } | |
| void cappedDumpDelInfo(); | | void cappedDumpDelInfo(); | |
| bool capLooped() const { return capped && capFirstNewRecord.isValid
(); } | | bool capLooped() const { return capped && capFirstNewRecord.isValid
(); } | |
| bool inCapExtent( const DiskLoc &dl ) const; | | bool inCapExtent( const DiskLoc &dl ) const; | |
| void cappedCheckMigrate(); | | void cappedCheckMigrate(); | |
|
| void cappedTruncateAfter(const char *ns, DiskLoc after, bool inclus
ive); /** remove rest of the capped collection from this point onward */ | | /** | |
| | | * Truncate documents newer than the document at 'end' from the cap
ped | |
| | | * collection. The collection cannot be completely emptied using t
his | |
| | | * function. An assertion will be thrown if that is attempted. | |
| | | * @param inclusive - Truncate 'end' as well iff true | |
| | | */ | |
| | | void cappedTruncateAfter(const char *ns, DiskLoc end, bool inclusiv
e); | |
| | | /** Remove all documents from the capped collection */ | |
| void emptyCappedCollection(const char *ns); | | void emptyCappedCollection(const char *ns); | |
| | | | |
|
| int capped; | | | |
| | | | |
| int max; // max # of objects for a capped table. TODO: should this
be 64 bit? | | | |
| double paddingFactor; // 1.0 = no padding. | | | |
| int flags; | | | |
| | | | |
| DiskLoc capExtent; | | | |
| DiskLoc capFirstNewRecord; | | | |
| | | | |
| /* NamespaceDetails version. So we can do backward compatibility i
n the future. | | | |
| See filever.h | | | |
| */ | | | |
| unsigned short dataFileVersion; | | | |
| unsigned short indexFileVersion; | | | |
| | | | |
| unsigned long long multiKeyIndexBits; | | | |
| private: | | | |
| unsigned long long reservedA; | | | |
| long long extraOffset; // where the $extra info is located (bytes r
elative to this) | | | |
| public: | | | |
| int backgroundIndexBuildInProgress; // 1 if in prog | | | |
| char reserved[76]; | | | |
| | | | |
| /* when a background index build is in progress, we don't count the
index in nIndexes until | | /* when a background index build is in progress, we don't count the
index in nIndexes until | |
| complete, yet need to still use it in _indexRecord() - thus we u
se this function for that. | | complete, yet need to still use it in _indexRecord() - thus we u
se this function for that. | |
| */ | | */ | |
|
| int nIndexesBeingBuilt() const { return nIndexes + backgroundIndexB
uildInProgress; } | | int nIndexesBeingBuilt() const { return nIndexes + indexBuildInProg
ress; } | |
| | | | |
| /* NOTE: be careful with flags. are we manipulating them in read l
ocks? if so, | | /* NOTE: be careful with flags. are we manipulating them in read l
ocks? if so, | |
| this isn't thread safe. TODO | | this isn't thread safe. TODO | |
| */ | | */ | |
| enum NamespaceFlags { | | enum NamespaceFlags { | |
| Flag_HaveIdIndex = 1 << 0 // set when we have _id index (ONLY i
f ensureIdIndex was called -- 0 if that has never been called) | | Flag_HaveIdIndex = 1 << 0 // set when we have _id index (ONLY i
f ensureIdIndex was called -- 0 if that has never been called) | |
| }; | | }; | |
| | | | |
|
| IndexDetails& idx(int idxNo, bool missingExpected = false ) { | | IndexDetails& idx(int idxNo, bool missingExpected = false ); | |
| if( idxNo < NIndexesBase ) | | | |
| return _indexes[idxNo]; | | /** get the IndexDetails for the index currently being built in the
background. (there is at most one) */ | |
| Extra *e = extra(); | | IndexDetails& inProgIdx() { | |
| if ( ! e ){ | | DEV assert(indexBuildInProgress); | |
| if ( missingExpected ) | | | |
| throw MsgAssertionException( 13283 , "Missing Extra" ); | | | |
| massert(13282, "missing Extra", e); | | | |
| } | | | |
| int i = idxNo - NIndexesBase; | | | |
| if( i >= NIndexesExtra ) { | | | |
| e = e->next(this); | | | |
| if ( ! e ){ | | | |
| if ( missingExpected ) | | | |
| throw MsgAssertionException( 13283 , "missing extra
" ); | | | |
| massert(13283, "missing Extra", e); | | | |
| } | | | |
| i -= NIndexesExtra; | | | |
| } | | | |
| return e->details[i]; | | | |
| } | | | |
| IndexDetails& backgroundIdx() { | | | |
| DEV assert(backgroundIndexBuildInProgress); | | | |
| return idx(nIndexes); | | return idx(nIndexes); | |
| } | | } | |
| | | | |
| class IndexIterator { | | class IndexIterator { | |
|
| friend class NamespaceDetails; | | | |
| int i; | | | |
| int n; | | | |
| NamespaceDetails *d; | | | |
| IndexIterator(NamespaceDetails *_d) { | | | |
| d = _d; | | | |
| i = 0; | | | |
| n = d->nIndexes; | | | |
| } | | | |
| public: | | public: | |
| int pos() { return i; } // note this is the next one to come | | int pos() { return i; } // note this is the next one to come | |
| bool more() { return i < n; } | | bool more() { return i < n; } | |
| IndexDetails& next() { return d->idx(i++); } | | IndexDetails& next() { return d->idx(i++); } | |
|
| }; // IndexIterator | | private: | |
| | | friend class NamespaceDetails; | |
| | | int i, n; | |
| | | NamespaceDetails *d; | |
| | | IndexIterator(NamespaceDetails *_d); | |
| | | }; | |
| | | | |
| IndexIterator ii() { return IndexIterator(this); } | | IndexIterator ii() { return IndexIterator(this); } | |
| | | | |
|
| /* hackish - find our index # in the indexes array | | /* hackish - find our index # in the indexes array */ | |
| */ | | int idxNo(IndexDetails& idx); | |
| int idxNo(IndexDetails& idx) { | | | |
| IndexIterator i = ii(); | | | |
| while( i.more() ) { | | | |
| if( &i.next() == &idx ) | | | |
| return i.pos()-1; | | | |
| } | | | |
| massert( 10349 , "E12000 idxNo fails", false); | | | |
| return -1; | | | |
| } | | | |
| | | | |
| /* multikey indexes are indexes where there are more than one key i
n the index | | /* multikey indexes are indexes where there are more than one key i
n the index | |
| for a single document. see multikey in wiki. | | for a single document. see multikey in wiki. | |
| for these, we have to do some dedup work on queries. | | for these, we have to do some dedup work on queries. | |
| */ | | */ | |
|
| bool isMultikey(int i) { | | bool isMultikey(int i) const { return (multiKeyIndexBits & (((unsig
ned long long) 1) << i)) != 0; } | |
| return (multiKeyIndexBits & (((unsigned long long) 1) << i)) !=
0; | | | |
| } | | | |
| void setIndexIsMultikey(int i) { | | void setIndexIsMultikey(int i) { | |
| dassert( i < NIndexesMax ); | | dassert( i < NIndexesMax ); | |
|
| multiKeyIndexBits |= (((unsigned long long) 1) << i); | | unsigned long long x = ((unsigned long long) 1) << i; | |
| | | if( multiKeyIndexBits & x ) return; | |
| | | *getDur().writing(&multiKeyIndexBits) |= x; | |
| } | | } | |
| void clearIndexIsMultikey(int i) { | | void clearIndexIsMultikey(int i) { | |
| dassert( i < NIndexesMax ); | | dassert( i < NIndexesMax ); | |
|
| multiKeyIndexBits &= ~(((unsigned long long) 1) << i); | | unsigned long long x = ((unsigned long long) 1) << i; | |
| | | if( (multiKeyIndexBits & x) == 0 ) return; | |
| | | *getDur().writing(&multiKeyIndexBits) &= ~x; | |
| } | | } | |
| | | | |
| /* add a new index. does not add to system.indexes etc. - just to
NamespaceDetails. | | /* add a new index. does not add to system.indexes etc. - just to
NamespaceDetails. | |
| caller must populate returned object. | | caller must populate returned object. | |
| */ | | */ | |
| IndexDetails& addIndex(const char *thisns, bool resetTransient=true
); | | IndexDetails& addIndex(const char *thisns, bool resetTransient=true
); | |
| | | | |
|
| void aboutToDeleteAnIndex() { flags &= ~Flag_HaveIdIndex; } | | void aboutToDeleteAnIndex() { | |
| | | *getDur().writing(&flags) = flags & ~Flag_HaveIdIndex; | |
| | | } | |
| | | | |
| /* returns index of the first index in which the field is present.
-1 if not present. */ | | /* returns index of the first index in which the field is present.
-1 if not present. */ | |
| int fieldIsIndexed(const char *fieldName); | | int fieldIsIndexed(const char *fieldName); | |
| | | | |
| void paddingFits() { | | void paddingFits() { | |
| double x = paddingFactor - 0.01; | | double x = paddingFactor - 0.01; | |
|
| if ( x >= 1.0 ) | | if ( x >= 1.0 ) { | |
| paddingFactor = x; | | *getDur().writing(&paddingFactor) = x; | |
| | | //getDur().setNoJournal(&paddingFactor, &x, sizeof(x)); | |
| | | } | |
| } | | } | |
| void paddingTooSmall() { | | void paddingTooSmall() { | |
| double x = paddingFactor + 0.6; | | double x = paddingFactor + 0.6; | |
|
| if ( x <= 2.0 ) | | if ( x <= 2.0 ) { | |
| paddingFactor = x; | | *getDur().writing(&paddingFactor) = x; | |
| } | | //getDur().setNoJournal(&paddingFactor, &x, sizeof(x)); | |
| | | | |
| //returns offset in indexes[] | | | |
| int findIndexByName(const char *name) { | | | |
| IndexIterator i = ii(); | | | |
| while( i.more() ) { | | | |
| if ( strcmp(i.next().info.obj().getStringField("name"),name
) == 0 ) | | | |
| return i.pos()-1; | | | |
| } | | } | |
|
| return -1; | | | |
| } | | } | |
| | | | |
|
| //returns offset in indexes[] | | // @return offset in indexes[] | |
| int findIndexByKeyPattern(const BSONObj& keyPattern) { | | int findIndexByName(const char *name); | |
| IndexIterator i = ii(); | | | |
| while( i.more() ) { | | // @return offset in indexes[] | |
| if( i.next().keyPattern() == keyPattern ) | | int findIndexByKeyPattern(const BSONObj& keyPattern); | |
| return i.pos()-1; | | | |
| } | | | |
| return -1; | | | |
| } | | | |
| | | | |
| void findIndexByType( const string& name , vector<int>& matches ) { | | void findIndexByType( const string& name , vector<int>& matches ) { | |
| IndexIterator i = ii(); | | IndexIterator i = ii(); | |
|
| while ( i.more() ){ | | while ( i.more() ) { | |
| if ( i.next().getSpec().getTypeName() == name ) | | if ( i.next().getSpec().getTypeName() == name ) | |
| matches.push_back( i.pos() - 1 ); | | matches.push_back( i.pos() - 1 ); | |
| } | | } | |
| } | | } | |
| | | | |
| /* @return -1 = not found | | /* @return -1 = not found | |
| generally id is first index, so not that expensive an operation
(assuming present). | | generally id is first index, so not that expensive an operation
(assuming present). | |
| */ | | */ | |
| int findIdIndex() { | | int findIdIndex() { | |
| IndexIterator i = ii(); | | IndexIterator i = ii(); | |
| while( i.more() ) { | | while( i.more() ) { | |
| if( i.next().isIdIndex() ) | | if( i.next().isIdIndex() ) | |
| return i.pos()-1; | | return i.pos()-1; | |
| } | | } | |
| return -1; | | return -1; | |
| } | | } | |
| | | | |
|
| | | bool haveIdIndex() { | |
| | | return (flags & NamespaceDetails::Flag_HaveIdIndex) || findIdIn
dex() >= 0; | |
| | | } | |
| | | | |
| /* return which "deleted bucket" for this size object */ | | /* return which "deleted bucket" for this size object */ | |
| static int bucket(int n) { | | static int bucket(int n) { | |
| for ( int i = 0; i < Buckets; i++ ) | | for ( int i = 0; i < Buckets; i++ ) | |
| if ( bucketSizes[i] > n ) | | if ( bucketSizes[i] > n ) | |
| return i; | | return i; | |
| return Buckets-1; | | return Buckets-1; | |
| } | | } | |
| | | | |
| /* allocate a new record. lenToAlloc includes headers. */ | | /* allocate a new record. lenToAlloc includes headers. */ | |
| DiskLoc alloc(const char *ns, int lenToAlloc, DiskLoc& extentLoc); | | DiskLoc alloc(const char *ns, int lenToAlloc, DiskLoc& extentLoc); | |
| | | | |
| skipping to change at line 454 | | skipping to change at line 376 | |
| /* return which "deleted bucket" for this size object */ | | /* return which "deleted bucket" for this size object */ | |
| static int bucket(int n) { | | static int bucket(int n) { | |
| for ( int i = 0; i < Buckets; i++ ) | | for ( int i = 0; i < Buckets; i++ ) | |
| if ( bucketSizes[i] > n ) | | if ( bucketSizes[i] > n ) | |
| return i; | | return i; | |
| return Buckets-1; | | return Buckets-1; | |
| } | | } | |
| | | | |
| /* allocate a new record. lenToAlloc includes headers. */ | | /* allocate a new record. lenToAlloc includes headers. */ | |
| DiskLoc alloc(const char *ns, int lenToAlloc, DiskLoc& extentLoc); | | DiskLoc alloc(const char *ns, int lenToAlloc, DiskLoc& extentLoc); | |
|
| | | | |
| /* add a given record to the deleted chains for this NS */ | | /* add a given record to the deleted chains for this NS */ | |
| void addDeletedRec(DeletedRecord *d, DiskLoc dloc); | | void addDeletedRec(DeletedRecord *d, DiskLoc dloc); | |
|
| | | | |
| void dumpDeleted(set<DiskLoc> *extents = 0); | | void dumpDeleted(set<DiskLoc> *extents = 0); | |
|
| | | | |
| // Start from firstExtent by default. | | // Start from firstExtent by default. | |
| DiskLoc firstRecord( const DiskLoc &startExtent = DiskLoc() ) const
; | | DiskLoc firstRecord( const DiskLoc &startExtent = DiskLoc() ) const
; | |
|
| | | | |
| // Start from lastExtent by default. | | // Start from lastExtent by default. | |
| DiskLoc lastRecord( const DiskLoc &startExtent = DiskLoc() ) const; | | DiskLoc lastRecord( const DiskLoc &startExtent = DiskLoc() ) const; | |
|
| | | long long storageSize( int * numExtents = 0 , BSONArrayBuilder * ex
tentInfo = 0 ) const; | |
| | | | |
|
| long long storageSize( int * numExtents = 0 ); | | int averageObjectSize() { | |
| | | if ( stats.nrecords == 0 ) | |
| | | return 5; | |
| | | return (int) (stats.datasize / stats.nrecords); | |
| | | } | |
| | | | |
| | | NamespaceDetails *writingWithoutExtra() { | |
| | | return ( NamespaceDetails* ) getDur().writingPtr( this, sizeof(
NamespaceDetails ) ); | |
| | | } | |
| | | /** Make all linked Extra objects writeable as well */ | |
| | | NamespaceDetails *writingWithExtra(); | |
| | | | |
| private: | | private: | |
| DiskLoc _alloc(const char *ns, int len); | | DiskLoc _alloc(const char *ns, int len); | |
| void maybeComplain( const char *ns, int len ) const; | | void maybeComplain( const char *ns, int len ) const; | |
| DiskLoc __stdAlloc(int len); | | DiskLoc __stdAlloc(int len); | |
| void compact(); // combine adjacent deleted records | | void compact(); // combine adjacent deleted records | |
|
| | | friend class NamespaceIndex; | |
| | | struct ExtraOld { | |
| | | // note we could use this field for more chaining later, so don
't waste it: | |
| | | unsigned long long reserved1; | |
| | | IndexDetails details[NIndexesExtra]; | |
| | | unsigned reserved2; | |
| | | unsigned reserved3; | |
| | | }; | |
| | | /** Update cappedLastDelRecLastExtent() after capExtent changed in
cappedTruncateAfter() */ | |
| | | void cappedTruncateLastDelUpdate(); | |
| | | BOOST_STATIC_ASSERT( NIndexesMax <= NIndexesBase + NIndexesExtra*2
); | |
| | | BOOST_STATIC_ASSERT( NIndexesMax <= 64 ); // multiKey bits | |
| | | BOOST_STATIC_ASSERT( sizeof(NamespaceDetails::ExtraOld) == 496 ); | |
| | | BOOST_STATIC_ASSERT( sizeof(NamespaceDetails::Extra) == 496 ); | |
| }; // NamespaceDetails | | }; // NamespaceDetails | |
| #pragma pack() | | #pragma pack() | |
| | | | |
| /* NamespaceDetailsTransient | | /* NamespaceDetailsTransient | |
| | | | |
| these are things we know / compute about a namespace that are transi
ent -- things | | these are things we know / compute about a namespace that are transi
ent -- things | |
| we don't actually store in the .ns file. so mainly caching of frequ
ently used | | we don't actually store in the .ns file. so mainly caching of frequ
ently used | |
| information. | | information. | |
| | | | |
| CAUTION: Are you maintaining this properly on a collection drop()?
A dropdatabase()? Be careful. | | CAUTION: Are you maintaining this properly on a collection drop()?
A dropdatabase()? Be careful. | |
| The current field "allIndexKeys" may have too many keys in
it on such an occurrence; | | The current field "allIndexKeys" may have too many keys in
it on such an occurrence; | |
| as currently used that does not cause anything terrible to
happen. | | as currently used that does not cause anything terrible to
happen. | |
| | | | |
| todo: cleanup code, need abstractions and separation | | todo: cleanup code, need abstractions and separation | |
| */ | | */ | |
| class NamespaceDetailsTransient : boost::noncopyable { | | class NamespaceDetailsTransient : boost::noncopyable { | |
|
| BOOST_STATIC_ASSERT( sizeof(NamespaceDetails) == 496 ); | | BOOST_STATIC_ASSERT( sizeof(NamespaceDetails) == 496 ); | |
| | | | |
| /* general --------------------------------------------------------
----- */ | | /* general --------------------------------------------------------
----- */ | |
| private: | | private: | |
| string _ns; | | string _ns; | |
| void reset(); | | void reset(); | |
| static std::map< string, shared_ptr< NamespaceDetailsTransient > >
_map; | | static std::map< string, shared_ptr< NamespaceDetailsTransient > >
_map; | |
| public: | | public: | |
|
| NamespaceDetailsTransient(const char *ns) : _ns(ns), _keysComputed(
false), _qcWriteCount(){ } | | NamespaceDetailsTransient(const char *ns) : _ns(ns), _keysComputed(
false), _qcWriteCount() { } | |
| | | private: | |
| /* _get() is not threadsafe -- see get_inlock() comments */ | | /* _get() is not threadsafe -- see get_inlock() comments */ | |
| static NamespaceDetailsTransient& _get(const char *ns); | | static NamespaceDetailsTransient& _get(const char *ns); | |
|
| /* use get_w() when doing write operations */ | | public: | |
| | | /* use get_w() when doing write operations. this is safe as there i
s only 1 write op and it's exclusive to everything else. | |
| | | for reads you must lock and then use get_inlock() instead. */ | |
| static NamespaceDetailsTransient& get_w(const char *ns) { | | static NamespaceDetailsTransient& get_w(const char *ns) { | |
| DEV assertInWriteLock(); | | DEV assertInWriteLock(); | |
| return _get(ns); | | return _get(ns); | |
| } | | } | |
| void addedIndex() { reset(); } | | void addedIndex() { reset(); } | |
| void deletedIndex() { reset(); } | | void deletedIndex() { reset(); } | |
| /* Drop cached information on all namespaces beginning with the spe
cified prefix. | | /* Drop cached information on all namespaces beginning with the spe
cified prefix. | |
| Can be useful as index namespaces share the same start as the re
gular collection. | | Can be useful as index namespaces share the same start as the re
gular collection. | |
| SLOW - sequential scan of all NamespaceDetailsTransient objects
*/ | | SLOW - sequential scan of all NamespaceDetailsTransient objects
*/ | |
| static void clearForPrefix(const char *prefix); | | static void clearForPrefix(const char *prefix); | |
|
| | | static void eraseForPrefix(const char *prefix); | |
| | | | |
| | | /** | |
| | | * @return a cursor interface to the query optimizer. The implemen
tation may | |
| | | * utilize a single query plan or interleave results from multiple
query | |
| | | * plans before settling on a single query plan. Note that the sch
ema of | |
| | | * currKey() documents, the matcher(), and the isMultiKey() nature
of the | |
| | | * cursor may change over the course of iteration. | |
| | | * | |
| | | * @param order - If no index exists that satisfies this sort order
, an | |
| | | * empty shared_ptr will be returned. | |
| | | * | |
| | | * The returned cursor may @throw inside of advance() or recoverFro
mYield() in | |
| | | * certain error cases, for example if a capped overrun occurred du
ring a yield. | |
| | | * This indicates that the cursor was unable to perform a complete
scan. | |
| | | * | |
| | | * This is a work in progress. Partial list of features not yet im
plemented: | |
| | | * - modification of scanned documents | |
| | | * - covered indexes | |
| | | */ | |
| | | static shared_ptr<Cursor> getCursor( const char *ns, const BSONObj
&query, const BSONObj &order = BSONObj() ); | |
| | | | |
| /* indexKeys() cache ----------------------------------------------
------ */ | | /* indexKeys() cache ----------------------------------------------
------ */ | |
| /* assumed to be in write lock for this */ | | /* assumed to be in write lock for this */ | |
| private: | | private: | |
| bool _keysComputed; | | bool _keysComputed; | |
| set<string> _indexKeys; | | set<string> _indexKeys; | |
| void computeIndexKeys(); | | void computeIndexKeys(); | |
| public: | | public: | |
| /* get set of index keys for this namespace. handy to quickly chec
k if a given | | /* get set of index keys for this namespace. handy to quickly chec
k if a given | |
| field is indexed (Note it might be a secondary component of a co
mpound index.) | | field is indexed (Note it might be a secondary component of a co
mpound index.) | |
| | | | |
| skipping to change at line 532 | | skipping to change at line 499 | |
| set<string>& indexKeys() { | | set<string>& indexKeys() { | |
| DEV assertInWriteLock(); | | DEV assertInWriteLock(); | |
| if ( !_keysComputed ) | | if ( !_keysComputed ) | |
| computeIndexKeys(); | | computeIndexKeys(); | |
| return _indexKeys; | | return _indexKeys; | |
| } | | } | |
| | | | |
| /* IndexSpec caching */ | | /* IndexSpec caching */ | |
| private: | | private: | |
| map<const IndexDetails*,IndexSpec> _indexSpecs; | | map<const IndexDetails*,IndexSpec> _indexSpecs; | |
|
| static mongo::mutex _isMutex; | | static SimpleMutex _isMutex; | |
| public: | | public: | |
|
| const IndexSpec& getIndexSpec( const IndexDetails * details ){ | | const IndexSpec& getIndexSpec( const IndexDetails * details ) { | |
| IndexSpec& spec = _indexSpecs[details]; | | IndexSpec& spec = _indexSpecs[details]; | |
|
| if ( ! spec._finishedInit ){ | | if ( ! spec._finishedInit ) { | |
| scoped_lock lk(_isMutex); | | SimpleMutex::scoped_lock lk(_isMutex); | |
| if ( ! spec._finishedInit ){ | | if ( ! spec._finishedInit ) { | |
| spec.reset( details ); | | spec.reset( details ); | |
| assert( spec._finishedInit ); | | assert( spec._finishedInit ); | |
| } | | } | |
| } | | } | |
| return spec; | | return spec; | |
| } | | } | |
| | | | |
| /* query cache (for query optimizer) ------------------------------
------- */ | | /* query cache (for query optimizer) ------------------------------
------- */ | |
| private: | | private: | |
| int _qcWriteCount; | | int _qcWriteCount; | |
| map< QueryPattern, pair< BSONObj, long long > > _qcCache; | | map< QueryPattern, pair< BSONObj, long long > > _qcCache; | |
| public: | | public: | |
|
| static mongo::mutex _qcMutex; | | static SimpleMutex _qcMutex; | |
| /* you must be in the qcMutex when calling this (and using the retu
rned val): */ | | /* you must be in the qcMutex when calling this (and using the retu
rned val): */ | |
| static NamespaceDetailsTransient& get_inlock(const char *ns) { | | static NamespaceDetailsTransient& get_inlock(const char *ns) { | |
| return _get(ns); | | return _get(ns); | |
| } | | } | |
| void clearQueryCache() { // public for unit tests | | void clearQueryCache() { // public for unit tests | |
| _qcCache.clear(); | | _qcCache.clear(); | |
| _qcWriteCount = 0; | | _qcWriteCount = 0; | |
| } | | } | |
| /* you must notify the cache if you are doing writes, as query plan
optimality will change */ | | /* you must notify the cache if you are doing writes, as query plan
optimality will change */ | |
| void notifyOfWriteOp() { | | void notifyOfWriteOp() { | |
| if ( _qcCache.empty() ) | | if ( _qcCache.empty() ) | |
| return; | | return; | |
|
| if ( ++_qcWriteCount >= 100 ) | | if ( ++_qcWriteCount >= 1000 ) | |
| clearQueryCache(); | | clearQueryCache(); | |
| } | | } | |
| BSONObj indexForPattern( const QueryPattern &pattern ) { | | BSONObj indexForPattern( const QueryPattern &pattern ) { | |
| return _qcCache[ pattern ].first; | | return _qcCache[ pattern ].first; | |
| } | | } | |
| long long nScannedForPattern( const QueryPattern &pattern ) { | | long long nScannedForPattern( const QueryPattern &pattern ) { | |
| return _qcCache[ pattern ].second; | | return _qcCache[ pattern ].second; | |
| } | | } | |
| void registerIndexForPattern( const QueryPattern &pattern, const BS
ONObj &indexKey, long long nScanned ) { | | void registerIndexForPattern( const QueryPattern &pattern, const BS
ONObj &indexKey, long long nScanned ) { | |
| _qcCache[ pattern ] = make_pair( indexKey, nScanned ); | | _qcCache[ pattern ] = make_pair( indexKey, nScanned ); | |
| | | | |
| skipping to change at line 594 | | skipping to change at line 561 | |
| } | | } | |
| | | | |
| /* NamespaceIndex is the ".ns" file you see in the data directory. It
is the "system catalog" | | /* NamespaceIndex is the ".ns" file you see in the data directory. It
is the "system catalog" | |
| if you will: at least the core parts. (Additional info in system.*
collections.) | | if you will: at least the core parts. (Additional info in system.*
collections.) | |
| */ | | */ | |
| class NamespaceIndex { | | class NamespaceIndex { | |
| friend class NamespaceCursor; | | friend class NamespaceCursor; | |
| | | | |
| public: | | public: | |
| NamespaceIndex(const string &dir, const string &database) : | | NamespaceIndex(const string &dir, const string &database) : | |
|
| ht( 0 ), dir_( dir ), database_( database ) {} | | ht( 0 ), dir_( dir ), database_( database ) {} | |
| | | | |
| /* returns true if new db will be created if we init lazily */ | | /* returns true if new db will be created if we init lazily */ | |
| bool exists() const; | | bool exists() const; | |
| | | | |
| void init(); | | void init(); | |
| | | | |
| void add_ns(const char *ns, DiskLoc& loc, bool capped) { | | void add_ns(const char *ns, DiskLoc& loc, bool capped) { | |
| NamespaceDetails details( loc, capped ); | | NamespaceDetails details( loc, capped ); | |
|
| add_ns( ns, details ); | | add_ns( ns, details ); | |
| } | | } | |
|
| void add_ns( const char *ns, const NamespaceDetails &details
) { | | void add_ns( const char *ns, const NamespaceDetails &details ) { | |
| init(); | | init(); | |
| Namespace n(ns); | | Namespace n(ns); | |
| uassert( 10081 , "too many namespaces/collections", ht->put(n,
details)); | | uassert( 10081 , "too many namespaces/collections", ht->put(n,
details)); | |
|
| } | | } | |
| | | | |
| /* just for diagnostics */ | | /* just for diagnostics */ | |
| /*size_t detailsOffset(NamespaceDetails *d) { | | /*size_t detailsOffset(NamespaceDetails *d) { | |
| if ( !ht ) | | if ( !ht ) | |
| return -1; | | return -1; | |
| return ((char *) d) - (char *) ht->nodes; | | return ((char *) d) - (char *) ht->nodes; | |
| }*/ | | }*/ | |
| | | | |
| NamespaceDetails* details(const char *ns) { | | NamespaceDetails* details(const char *ns) { | |
| if ( !ht ) | | if ( !ht ) | |
| return 0; | | return 0; | |
| Namespace n(ns); | | Namespace n(ns); | |
| NamespaceDetails *d = ht->get(n); | | NamespaceDetails *d = ht->get(n); | |
| if ( d && d->capped ) | | if ( d && d->capped ) | |
| d->cappedCheckMigrate(); | | d->cappedCheckMigrate(); | |
| return d; | | return d; | |
| } | | } | |
| | | | |
|
| void kill_ns(const char *ns) { | | void kill_ns(const char *ns); | |
| if ( !ht ) | | | |
| return; | | | |
| Namespace n(ns); | | | |
| ht->kill(n); | | | |
| | | | |
| for( int i = 0; i<=1; i++ ) { | | | |
| try { | | | |
| Namespace extra(n.extraName(i).c_str()); | | | |
| ht->kill(extra); | | | |
| } | | | |
| catch(DBException&) { } | | | |
| } | | | |
| } | | | |
| | | | |
| bool find(const char *ns, DiskLoc& loc) { | | bool find(const char *ns, DiskLoc& loc) { | |
| NamespaceDetails *l = details(ns); | | NamespaceDetails *l = details(ns); | |
| if ( l ) { | | if ( l ) { | |
| loc = l->firstExtent; | | loc = l->firstExtent; | |
| return true; | | return true; | |
| } | | } | |
| return false; | | return false; | |
| } | | } | |
| | | | |
| bool allocated() const { | | bool allocated() const { | |
| return ht != 0; | | return ht != 0; | |
| } | | } | |
| | | | |
| void getNamespaces( list<string>& tofill , bool onlyCollections = t
rue ) const; | | void getNamespaces( list<string>& tofill , bool onlyCollections = t
rue ) const; | |
| | | | |
| NamespaceDetails::Extra* newExtra(const char *ns, int n, NamespaceD
etails *d); | | NamespaceDetails::Extra* newExtra(const char *ns, int n, NamespaceD
etails *d); | |
| | | | |
| boost::filesystem::path path() const; | | boost::filesystem::path path() const; | |
|
| private: | | | |
| | | | |
|
| | | unsigned long long fileLength() const { return f.length(); } | |
| | | | |
| | | private: | |
| void maybeMkdir() const; | | void maybeMkdir() const; | |
| | | | |
|
| MMF f; | | MongoMMF f; | |
| HashTable<Namespace,NamespaceDetails,MMF::Pointer> *ht; | | HashTable<Namespace,NamespaceDetails> *ht; | |
| string dir_; | | string dir_; | |
| string database_; | | string database_; | |
| }; | | }; | |
| | | | |
| extern string dbpath; // --dbpath parm | | extern string dbpath; // --dbpath parm | |
| extern bool directoryperdb; | | extern bool directoryperdb; | |
| | | | |
| // Rename a namespace within current 'client' db. | | // Rename a namespace within current 'client' db. | |
| // (Arguments should include db name) | | // (Arguments should include db name) | |
| void renameNamespace( const char *from, const char *to ); | | void renameNamespace( const char *from, const char *to ); | |
| | | | |
|
| | | // "database.a.b.c" -> "database" | |
| | | inline void nsToDatabase(const char *ns, char *database) { | |
| | | const char *p = ns; | |
| | | char *q = database; | |
| | | while ( *p != '.' ) { | |
| | | if ( *p == 0 ) | |
| | | break; | |
| | | *q++ = *p++; | |
| | | } | |
| | | *q = 0; | |
| | | if (q-database>=MaxDatabaseNameLen) { | |
| | | log() << "nsToDatabase: ns too long. terminating, buf overrun c
ondition" << endl; | |
| | | dbexit( EXIT_POSSIBLE_CORRUPTION ); | |
| | | } | |
| | | } | |
| | | inline string nsToDatabase(const char *ns) { | |
| | | char buf[MaxDatabaseNameLen]; | |
| | | nsToDatabase(ns, buf); | |
| | | return buf; | |
| | | } | |
| | | inline string nsToDatabase(const string& ns) { | |
| | | size_t i = ns.find( '.' ); | |
| | | if ( i == string::npos ) | |
| | | return ns; | |
| | | return ns.substr( 0 , i ); | |
| | | } | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 73 change blocks. |
| 281 lines changed or deleted | | 264 lines changed or added | |
|
| pdfile.h | | pdfile.h | |
| | | | |
| skipping to change at line 32 | | skipping to change at line 32 | |
| database.2 | | database.2 | |
| ... | | ... | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../pch.h" | | #include "../pch.h" | |
| #include "../util/mmap.h" | | #include "../util/mmap.h" | |
| #include "diskloc.h" | | #include "diskloc.h" | |
| #include "jsobjmanipulator.h" | | #include "jsobjmanipulator.h" | |
|
| #include "namespace.h" | | #include "namespace-inl.h" | |
| #include "client.h" | | #include "client.h" | |
|
| | | #include "mongommf.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| class DataFileHeader; | | class DataFileHeader; | |
| class Extent; | | class Extent; | |
| class Record; | | class Record; | |
| class Cursor; | | class Cursor; | |
| class OpDebug; | | class OpDebug; | |
| | | | |
| void dropDatabase(string db); | | void dropDatabase(string db); | |
| bool repairDatabase(string db, string &errmsg, bool preserveClonedFiles
OnFailure = false, bool backupOriginalFiles = false); | | bool repairDatabase(string db, string &errmsg, bool preserveClonedFiles
OnFailure = false, bool backupOriginalFiles = false); | |
| | | | |
| /* low level - only drops this ns */ | | /* low level - only drops this ns */ | |
| void dropNS(const string& dropNs); | | void dropNS(const string& dropNs); | |
| | | | |
| /* deletes this ns, indexes and cursors */ | | /* deletes this ns, indexes and cursors */ | |
| void dropCollection( const string &name, string &errmsg, BSONObjBuilder
&result ); | | void dropCollection( const string &name, string &errmsg, BSONObjBuilder
&result ); | |
| bool userCreateNS(const char *ns, BSONObj j, string& err, bool logForRe
plication, bool *deferIdIndex = 0); | | bool userCreateNS(const char *ns, BSONObj j, string& err, bool logForRe
plication, bool *deferIdIndex = 0); | |
| shared_ptr<Cursor> findTableScan(const char *ns, const BSONObj& order,
const DiskLoc &startLoc=DiskLoc()); | | shared_ptr<Cursor> findTableScan(const char *ns, const BSONObj& order,
const DiskLoc &startLoc=DiskLoc()); | |
| | | | |
|
| // -1 if library unavailable. | | bool isValidNS( const StringData& ns ); | |
| boost::intmax_t freeSpace( const string &path = dbpath ); | | | |
| | | | |
| /*---------------------------------------------------------------------
*/ | | /*---------------------------------------------------------------------
*/ | |
| | | | |
| class MongoDataFile { | | class MongoDataFile { | |
| friend class DataFileMgr; | | friend class DataFileMgr; | |
| friend class BasicCursor; | | friend class BasicCursor; | |
| public: | | public: | |
|
| MongoDataFile(int fn) : fileNo(fn) { } | | MongoDataFile(int fn) : _mb(0), fileNo(fn) { } | |
| void open(const char *filename, int requestedDataSize = 0, bool pre
allocateOnly = false); | | void open(const char *filename, int requestedDataSize = 0, bool pre
allocateOnly = false); | |
| | | | |
| /* allocate a new extent from this datafile. | | /* allocate a new extent from this datafile. | |
| @param capped - true if capped collection | | @param capped - true if capped collection | |
| @param loops is our recursion check variable - you want to pass
in zero | | @param loops is our recursion check variable - you want to pass
in zero | |
| */ | | */ | |
| Extent* createExtent(const char *ns, int approxSize, bool capped =
false, int loops = 0); | | Extent* createExtent(const char *ns, int approxSize, bool capped =
false, int loops = 0); | |
| | | | |
|
| DataFileHeader *getHeader() { | | DataFileHeader *getHeader() { return header(); } | |
| return header; | | | |
| } | | unsigned long long length() const { return mmf.length(); } | |
| | | | |
| /* return max size an extent may be */ | | /* return max size an extent may be */ | |
| static int maxSize(); | | static int maxSize(); | |
| | | | |
|
| | | /** fsync */ | |
| void flush( bool sync ); | | void flush( bool sync ); | |
| | | | |
|
| | | /** only use fore debugging */ | |
| | | Extent* debug_getExtent(DiskLoc loc) { return _getExtent( loc ); } | |
| private: | | private: | |
| void badOfs(int) const; | | void badOfs(int) const; | |
| void badOfs2(int) const; | | void badOfs2(int) const; | |
|
| | | | |
| int defaultSize( const char *filename ) const; | | int defaultSize( const char *filename ) const; | |
| | | | |
|
| Extent* getExtent(DiskLoc loc); | | Extent* getExtent(DiskLoc loc) const; | |
| Extent* _getExtent(DiskLoc loc); | | Extent* _getExtent(DiskLoc loc) const; | |
| Record* recordAt(DiskLoc dl); | | Record* recordAt(DiskLoc dl); | |
| Record* makeRecord(DiskLoc dl, int size); | | Record* makeRecord(DiskLoc dl, int size); | |
|
| void grow(DiskLoc dl, int size); | | void grow(DiskLoc dl, int size); | |
| | | | |
|
| MMF mmf; | | char* p() const { return (char *) _mb; } | |
| MMF::Pointer _p; | | DataFileHeader* header() { return (DataFileHeader*) _mb; } | |
| DataFileHeader *header; | | | |
| | | MongoMMF mmf; | |
| | | void *_mb; // the memory mapped view | |
| int fileNo; | | int fileNo; | |
| }; | | }; | |
| | | | |
| class DataFileMgr { | | class DataFileMgr { | |
| friend class BasicCursor; | | friend class BasicCursor; | |
| public: | | public: | |
| void init(const string& path ); | | void init(const string& path ); | |
| | | | |
| /* see if we can find an extent of the right size in the freelist.
*/ | | /* see if we can find an extent of the right size in the freelist.
*/ | |
| static Extent* allocFromFreeList(const char *ns, int approxSize, bo
ol capped = false); | | static Extent* allocFromFreeList(const char *ns, int approxSize, bo
ol capped = false); | |
| | | | |
| /** @return DiskLoc where item ends up */ | | /** @return DiskLoc where item ends up */ | |
| // changedId should be initialized to false | | // changedId should be initialized to false | |
| const DiskLoc updateRecord( | | const DiskLoc updateRecord( | |
| const char *ns, | | const char *ns, | |
| NamespaceDetails *d, | | NamespaceDetails *d, | |
| NamespaceDetailsTransient *nsdt, | | NamespaceDetailsTransient *nsdt, | |
| Record *toupdate, const DiskLoc& dl, | | Record *toupdate, const DiskLoc& dl, | |
|
| const char *buf, int len, OpDebug& debug, bool &changedId, bool
god=false); | | const char *buf, int len, OpDebug& debug, bool god=false); | |
| | | | |
| // The object o may be updated if modified on insert. | | // The object o may be updated if modified on insert. | |
| void insertAndLog( const char *ns, const BSONObj &o, bool god = fal
se ); | | void insertAndLog( const char *ns, const BSONObj &o, bool god = fal
se ); | |
| | | | |
|
| /** @param obj both and in and out param -- insert can sometimes mo
dify an object (such as add _id). */ | | /** insert will add an _id to the object if not present. if you wo
uld like to see the final object | |
| DiskLoc insertWithObjMod(const char *ns, BSONObj &o, bool god = fal
se); | | after such an addition, use this method. | |
| | | @param o both and in and out param | |
| | | */ | |
| | | DiskLoc insertWithObjMod(const char *ns, BSONObj & /*out*/o, bool g
od = false); | |
| | | | |
| /** @param obj in value only for this version. */ | | /** @param obj in value only for this version. */ | |
| void insertNoReturnVal(const char *ns, BSONObj o, bool god = false)
; | | void insertNoReturnVal(const char *ns, BSONObj o, bool god = false)
; | |
| | | | |
|
| DiskLoc insert(const char *ns, const void *buf, int len, bool god =
false, const BSONElement &writeId = BSONElement(), bool mayAddIndex = true
); | | DiskLoc insert(const char *ns, const void *buf, int len, bool god =
false, bool mayAddIndex = true, bool *addedID = 0); | |
| void deleteRecord(const char *ns, Record *todelete, const DiskLoc&
dl, bool cappedOK = false, bool noWarn = false); | | | |
| static shared_ptr<Cursor> findAll(const char *ns, const DiskLoc &st
artLoc = DiskLoc()); | | static shared_ptr<Cursor> findAll(const char *ns, const DiskLoc &st
artLoc = DiskLoc()); | |
| | | | |
| /* special version of insert for transaction logging -- streamlined
a bit. | | /* special version of insert for transaction logging -- streamlined
a bit. | |
| assumes ns is capped and no indexes | | assumes ns is capped and no indexes | |
| no _id field check | | no _id field check | |
| */ | | */ | |
| Record* fast_oplog_insert(NamespaceDetails *d, const char *ns, int
len); | | Record* fast_oplog_insert(NamespaceDetails *d, const char *ns, int
len); | |
| | | | |
| static Extent* getExtent(const DiskLoc& dl); | | static Extent* getExtent(const DiskLoc& dl); | |
| static Record* getRecord(const DiskLoc& dl); | | static Record* getRecord(const DiskLoc& dl); | |
| static DeletedRecord* makeDeletedRecord(const DiskLoc& dl, int len)
; | | static DeletedRecord* makeDeletedRecord(const DiskLoc& dl, int len)
; | |
|
| static void grow(const DiskLoc& dl, int len); | | | |
| | | | |
|
| /* does not clean up indexes, etc. : just deletes the record in the
pdfile. */ | | void deleteRecord(const char *ns, Record *todelete, const DiskLoc&
dl, bool cappedOK = false, bool noWarn = false, bool logOp=false); | |
| | | | |
| | | /* does not clean up indexes, etc. : just deletes the record in the
pdfile. use deleteRecord() to unindex */ | |
| void _deleteRecord(NamespaceDetails *d, const char *ns, Record *tod
elete, const DiskLoc& dl); | | void _deleteRecord(NamespaceDetails *d, const char *ns, Record *tod
elete, const DiskLoc& dl); | |
| | | | |
| private: | | private: | |
| vector<MongoDataFile *> files; | | vector<MongoDataFile *> files; | |
| }; | | }; | |
| | | | |
| extern DataFileMgr theDataFileMgr; | | extern DataFileMgr theDataFileMgr; | |
| | | | |
| #pragma pack(1) | | #pragma pack(1) | |
| | | | |
| class DeletedRecord { | | class DeletedRecord { | |
| public: | | public: | |
| int lengthWithHeaders; | | int lengthWithHeaders; | |
| int extentOfs; | | int extentOfs; | |
| DiskLoc nextDeleted; | | DiskLoc nextDeleted; | |
|
| | | DiskLoc myExtentLoc(const DiskLoc& myLoc) const { | |
| | | return DiskLoc(myLoc.a(), extentOfs); | |
| | | } | |
| Extent* myExtent(const DiskLoc& myLoc) { | | Extent* myExtent(const DiskLoc& myLoc) { | |
| return DataFileMgr::getExtent(DiskLoc(myLoc.a(), extentOfs)); | | return DataFileMgr::getExtent(DiskLoc(myLoc.a(), extentOfs)); | |
| } | | } | |
| }; | | }; | |
| | | | |
| /* Record is a record in a datafile. DeletedRecord is similar but for
deleted space. | | /* Record is a record in a datafile. DeletedRecord is similar but for
deleted space. | |
| | | | |
| *11:03:20 AM) dm10gen: regarding extentOfs... | | *11:03:20 AM) dm10gen: regarding extentOfs... | |
| (11:03:42 AM) dm10gen: an extent is a continugous disk area, which cont
ains many Records and DeleteRecords | | (11:03:42 AM) dm10gen: an extent is a continugous disk area, which cont
ains many Records and DeleteRecords | |
| (11:03:56 AM) dm10gen: a DiskLoc has two pieces, the fileno and ofs. (
64 bit total) | | (11:03:56 AM) dm10gen: a DiskLoc has two pieces, the fileno and ofs. (
64 bit total) | |
| (11:04:16 AM) dm10gen: to keep the headesr small, instead of storing a
64 bit ptr to the full extent address, we keep just the offset | | (11:04:16 AM) dm10gen: to keep the headesr small, instead of storing a
64 bit ptr to the full extent address, we keep just the offset | |
| (11:04:29 AM) dm10gen: we can do this as we know the record's address,
and it has the same fileNo | | (11:04:29 AM) dm10gen: we can do this as we know the record's address,
and it has the same fileNo | |
| (11:04:33 AM) dm10gen: see class DiskLoc for more info | | (11:04:33 AM) dm10gen: see class DiskLoc for more info | |
| (11:04:43 AM) dm10gen: so that is how Record::myExtent() works | | (11:04:43 AM) dm10gen: so that is how Record::myExtent() works | |
|
| (11:04:53 AM) dm10gen: on an alloc(), when we build a new Record, we mu
st popular its extentOfs then | | (11:04:53 AM) dm10gen: on an alloc(), when we build a new Record, we mu
st populate its extentOfs then | |
| */ | | */ | |
| class Record { | | class Record { | |
| public: | | public: | |
| enum HeaderSizeValue { HeaderSize = 16 }; | | enum HeaderSizeValue { HeaderSize = 16 }; | |
| int lengthWithHeaders; | | int lengthWithHeaders; | |
| int extentOfs; | | int extentOfs; | |
| int nextOfs; | | int nextOfs; | |
| int prevOfs; | | int prevOfs; | |
|
| | | | |
| | | /** be careful when referencing this that your write intent was cor
rect */ | |
| char data[4]; | | char data[4]; | |
|
| | | | |
| int netLength() { | | int netLength() { | |
| return lengthWithHeaders - HeaderSize; | | return lengthWithHeaders - HeaderSize; | |
| } | | } | |
| //void setNewLength(int netlen) { lengthWithHeaders = netlen + Head
erSize; } | | //void setNewLength(int netlen) { lengthWithHeaders = netlen + Head
erSize; } | |
| | | | |
| /* use this when a record is deleted. basically a union with next/p
rev fields */ | | /* use this when a record is deleted. basically a union with next/p
rev fields */ | |
| DeletedRecord& asDeleted() { | | DeletedRecord& asDeleted() { | |
| return *((DeletedRecord*) this); | | return *((DeletedRecord*) this); | |
| } | | } | |
| | | | |
| | | | |
| skipping to change at line 196 | | skipping to change at line 209 | |
| DeletedRecord& asDeleted() { | | DeletedRecord& asDeleted() { | |
| return *((DeletedRecord*) this); | | return *((DeletedRecord*) this); | |
| } | | } | |
| | | | |
| Extent* myExtent(const DiskLoc& myLoc) { | | Extent* myExtent(const DiskLoc& myLoc) { | |
| return DataFileMgr::getExtent(DiskLoc(myLoc.a(), extentOfs)); | | return DataFileMgr::getExtent(DiskLoc(myLoc.a(), extentOfs)); | |
| } | | } | |
| /* get the next record in the namespace, traversing extents as nece
ssary */ | | /* get the next record in the namespace, traversing extents as nece
ssary */ | |
| DiskLoc getNext(const DiskLoc& myLoc); | | DiskLoc getNext(const DiskLoc& myLoc); | |
| DiskLoc getPrev(const DiskLoc& myLoc); | | DiskLoc getPrev(const DiskLoc& myLoc); | |
|
| | | | |
| | | DiskLoc nextInExtent(const DiskLoc& myLoc) { | |
| | | if ( nextOfs == DiskLoc::NullOfs ) | |
| | | return DiskLoc(); | |
| | | assert( nextOfs ); | |
| | | return DiskLoc(myLoc.a(), nextOfs); | |
| | | } | |
| | | | |
| | | struct NP { | |
| | | int nextOfs; | |
| | | int prevOfs; | |
| | | }; | |
| | | NP* np() { return (NP*) &nextOfs; } | |
| | | | |
| | | // --------------------- | |
| | | // memory cache | |
| | | // --------------------- | |
| | | | |
| | | /** | |
| | | * touches the data so that is in physical memory | |
| | | * @param entireRecrd if false, only the header and first byte is t
ouched | |
| | | * if true, the entire record is touched | |
| | | * */ | |
| | | void touch( bool entireRecrd = false ); | |
| | | | |
| | | /** | |
| | | * @return if this record is likely in physical memory | |
| | | * its not guaranteed because its possible it gets swapped
out in a very unlucky windows | |
| | | */ | |
| | | bool likelyInPhysicalMemory(); | |
| | | | |
| | | /** | |
| | | * tell the cache this Record was accessed | |
| | | * @return this, for simple chaining | |
| | | */ | |
| | | Record* accessed(); | |
| | | | |
| | | static bool MemoryTrackingEnabled; | |
| }; | | }; | |
| | | | |
| /* extents are datafile regions where all the records within the region | | /* extents are datafile regions where all the records within the region | |
| belong to the same namespace. | | belong to the same namespace. | |
| | | | |
| (11:12:35 AM) dm10gen: when the extent is allocated, all its empty spac
e is stuck into one big DeletedRecord | | (11:12:35 AM) dm10gen: when the extent is allocated, all its empty spac
e is stuck into one big DeletedRecord | |
| (11:12:55 AM) dm10gen: and that is placed on the free list | | (11:12:55 AM) dm10gen: and that is placed on the free list | |
| */ | | */ | |
| class Extent { | | class Extent { | |
| public: | | public: | |
| unsigned magic; | | unsigned magic; | |
| DiskLoc myLoc; | | DiskLoc myLoc; | |
| DiskLoc xnext, xprev; /* next/prev extent for this namespace */ | | DiskLoc xnext, xprev; /* next/prev extent for this namespace */ | |
| | | | |
| /* which namespace this extent is for. this is just for troublesho
oting really | | /* which namespace this extent is for. this is just for troublesho
oting really | |
| and won't even be correct if the collection were renamed! | | and won't even be correct if the collection were renamed! | |
| */ | | */ | |
| Namespace nsDiagnostic; | | Namespace nsDiagnostic; | |
| | | | |
| int length; /* size of the extent, including these fields */ | | int length; /* size of the extent, including these fields */ | |
|
| DiskLoc firstRecord, lastRecord; | | DiskLoc firstRecord; | |
| | | DiskLoc lastRecord; | |
| char _extentData[4]; | | char _extentData[4]; | |
| | | | |
| static int HeaderSize() { return sizeof(Extent)-4; } | | static int HeaderSize() { return sizeof(Extent)-4; } | |
| | | | |
| bool validates() { | | bool validates() { | |
| return !(firstRecord.isNull() ^ lastRecord.isNull()) && | | return !(firstRecord.isNull() ^ lastRecord.isNull()) && | |
| length >= 0 && !myLoc.isNull(); | | length >= 0 && !myLoc.isNull(); | |
| } | | } | |
| | | | |
|
| | | BSONObj dump() { | |
| | | return BSON( "loc" << myLoc.toString() << "xnext" << xnext.toSt
ring() << "xprev" << xprev.toString() | |
| | | << "nsdiag" << nsDiagnostic.toString() | |
| | | << "size" << length << "firstRecord" << firstRecord.t
oString() << "lastRecord" << lastRecord.toString()); | |
| | | } | |
| | | | |
| void dump(iostream& s) { | | void dump(iostream& s) { | |
| s << " loc:" << myLoc.toString() << " xnext:" << xnext.toStr
ing() << " xprev:" << xprev.toString() << '\n'; | | s << " loc:" << myLoc.toString() << " xnext:" << xnext.toStr
ing() << " xprev:" << xprev.toString() << '\n'; | |
|
| s << " nsdiag:" << nsDiagnostic.buf << '\n'; | | s << " nsdiag:" << nsDiagnostic.toString() << '\n'; | |
| s << " size:" << length << " firstRecord:" << firstRecord.to
String() << " lastRecord:" << lastRecord.toString() << '\n'; | | s << " size:" << length << " firstRecord:" << firstRecord.to
String() << " lastRecord:" << lastRecord.toString() << '\n'; | |
| } | | } | |
| | | | |
| /* assumes already zeroed -- insufficient for block 'reuse' perhaps | | /* assumes already zeroed -- insufficient for block 'reuse' perhaps | |
| Returns a DeletedRecord location which is the data in the extent re
ady for us. | | Returns a DeletedRecord location which is the data in the extent re
ady for us. | |
| Caller will need to add that to the freelist structure in namespace
detail. | | Caller will need to add that to the freelist structure in namespace
detail. | |
| */ | | */ | |
|
| DiskLoc init(const char *nsname, int _length, int _fileNo, int _off
set); | | DiskLoc init(const char *nsname, int _length, int _fileNo, int _off
set, bool capped); | |
| | | | |
| /* like init(), but for a reuse case */ | | /* like init(), but for a reuse case */ | |
|
| DiskLoc reuse(const char *nsname); | | DiskLoc reuse(const char *nsname, bool newUseIsAsCapped); | |
| | | | |
|
| void assertOk() { | | bool isOk() const { return magic == 0x41424344; } | |
| assert(magic == 0x41424344); | | void assertOk() const { assert(isOk()); } | |
| } | | | |
| | | | |
| Record* newRecord(int len); | | Record* newRecord(int len); | |
| | | | |
| Record* getRecord(DiskLoc dl) { | | Record* getRecord(DiskLoc dl) { | |
| assert( !dl.isNull() ); | | assert( !dl.isNull() ); | |
| assert( dl.sameFile(myLoc) ); | | assert( dl.sameFile(myLoc) ); | |
| int x = dl.getOfs() - myLoc.getOfs(); | | int x = dl.getOfs() - myLoc.getOfs(); | |
| assert( x > 0 ); | | assert( x > 0 ); | |
| return (Record *) (((char *) this) + x); | | return (Record *) (((char *) this) + x); | |
| } | | } | |
| | | | |
|
| Extent* getNextExtent() { | | Extent* getNextExtent() { return xnext.isNull() ? 0 : DataFileMgr::
getExtent(xnext); } | |
| return xnext.isNull() ? 0 : DataFileMgr::getExtent(xnext); | | Extent* getPrevExtent() { return xprev.isNull() ? 0 : DataFileMgr::
getExtent(xprev); } | |
| } | | | |
| Extent* getPrevExtent() { | | | |
| return xprev.isNull() ? 0 : DataFileMgr::getExtent(xprev); | | | |
| } | | | |
| | | | |
| static int maxSize(); | | static int maxSize(); | |
|
| | | static int minSize() { return 0x100; } | |
| | | /** | |
| | | * @param len lengt of record we need | |
| | | * @param lastRecord size of last extent which is a factor in next
extent size | |
| | | */ | |
| | | static int followupSize(int len, int lastExtentLen); | |
| | | | |
| | | /** get a suggested size for the first extent in a namespace | |
| | | * @param len length of record we need to insert | |
| | | */ | |
| | | static int initialSize(int len); | |
| | | | |
| | | struct FL { | |
| | | DiskLoc firstRecord; | |
| | | DiskLoc lastRecord; | |
| | | }; | |
| | | /** often we want to update just the firstRecord and lastRecord fie
lds. | |
| | | this helper is for that -- for use with getDur().writing() meth
od | |
| | | */ | |
| | | FL* fl() { return (FL*) &firstRecord; } | |
| | | | |
| | | /** caller must declare write intent first */ | |
| | | void markEmpty(); | |
| | | private: | |
| | | DiskLoc _reuse(const char *nsname, bool newUseIsAsCapped); // recyc
le an extent and reuse it for a different ns | |
| }; | | }; | |
| | | | |
|
| /* | | /* a datafile - i.e. the "dbname.<#>" files : | |
| | | | |
| ---------------------- | | ---------------------- | |
|
| Header | | DataFileHeader | |
| ---------------------- | | ---------------------- | |
| Extent (for a particular namespace) | | Extent (for a particular namespace) | |
| Record | | Record | |
| ... | | ... | |
| Record (some chained for unused space) | | Record (some chained for unused space) | |
| ---------------------- | | ---------------------- | |
| more Extents... | | more Extents... | |
| ---------------------- | | ---------------------- | |
| */ | | */ | |
|
| | | | |
| class DataFileHeader { | | class DataFileHeader { | |
| public: | | public: | |
| int version; | | int version; | |
| int versionMinor; | | int versionMinor; | |
| int fileLength; | | int fileLength; | |
| DiskLoc unused; /* unused is the portion of the file that doesn't b
elong to any allocated extents. -1 = no more */ | | DiskLoc unused; /* unused is the portion of the file that doesn't b
elong to any allocated extents. -1 = no more */ | |
| int unusedLength; | | int unusedLength; | |
| char reserved[8192 - 4*4 - 8]; | | char reserved[8192 - 4*4 - 8]; | |
| | | | |
|
| char data[4]; | | char data[4]; // first extent starts here | |
| | | | |
| enum { HeaderSize = 8192 }; | | enum { HeaderSize = 8192 }; | |
| | | | |
|
| bool currentVersion() const { | | bool isCurrentVersion() const { return ( version == VERSION ) && (
versionMinor == VERSION_MINOR ); } | |
| return ( version == VERSION ) && ( versionMinor == VERSION_MINO
R ); | | | |
| } | | | |
| | | | |
| bool uninitialized() const { | | | |
| if ( version == 0 ) return true; | | | |
| return false; | | | |
| } | | | |
| | | | |
|
| /*Record* __getRecord(DiskLoc dl) { | | bool uninitialized() const { return version == 0; } | |
| int ofs = dl.getOfs(); | | | |
| assert( ofs >= HeaderSize ); | | | |
| return (Record*) (((char *) this) + ofs); | | | |
| }*/ | | | |
| | | | |
|
| void init(int fileno, int filelength) { | | void init(int fileno, int filelength, const char* filename) { | |
| if ( uninitialized() ) { | | if ( uninitialized() ) { | |
|
| assert(filelength > 32768 ); | | if( !(filelength > 32768 ) ) { | |
| | | massert(13640, str::stream() << "DataFileHeader looks c
orrupt at file open filelength:" << filelength << " fileno:" << fileno, fal
se); | |
| | | } | |
| | | getDur().createdFile(filename, filelength); | |
| assert( HeaderSize == 8192 ); | | assert( HeaderSize == 8192 ); | |
|
| fileLength = filelength; | | DataFileHeader *h = getDur().writing(this); | |
| version = VERSION; | | h->fileLength = filelength; | |
| versionMinor = VERSION_MINOR; | | h->version = VERSION; | |
| unused.setOfs( fileno, HeaderSize ); | | h->versionMinor = VERSION_MINOR; | |
| | | h->unused.set( fileno, HeaderSize ); | |
| assert( (data-(char*)this) == HeaderSize ); | | assert( (data-(char*)this) == HeaderSize ); | |
|
| unusedLength = fileLength - HeaderSize - 16; | | h->unusedLength = fileLength - HeaderSize - 16; | |
| //memcpy(data+unusedLength, " \nthe end\n", 16); | | | |
| } | | } | |
| } | | } | |
| | | | |
| bool isEmpty() const { | | bool isEmpty() const { | |
| return uninitialized() || ( unusedLength == fileLength - Header
Size - 16 ); | | return uninitialized() || ( unusedLength == fileLength - Header
Size - 16 ); | |
| } | | } | |
| }; | | }; | |
| | | | |
| #pragma pack() | | #pragma pack() | |
| | | | |
|
| inline Extent* MongoDataFile::_getExtent(DiskLoc loc) { | | inline Extent* MongoDataFile::_getExtent(DiskLoc loc) const { | |
| loc.assertOk(); | | loc.assertOk(); | |
|
| Extent *e = (Extent *) _p.at(loc.getOfs(), Extent::HeaderSize()); | | Extent *e = (Extent *) (p()+loc.getOfs()); | |
| return e; | | return e; | |
| } | | } | |
| | | | |
|
| inline Extent* MongoDataFile::getExtent(DiskLoc loc) { | | inline Extent* MongoDataFile::getExtent(DiskLoc loc) const { | |
| Extent *e = _getExtent(loc); | | Extent *e = _getExtent(loc); | |
| e->assertOk(); | | e->assertOk(); | |
| return e; | | return e; | |
| } | | } | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
| #include "cursor.h" | | #include "cursor.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| inline Record* MongoDataFile::recordAt(DiskLoc dl) { | | inline Record* MongoDataFile::recordAt(DiskLoc dl) { | |
| int ofs = dl.getOfs(); | | int ofs = dl.getOfs(); | |
| if( ofs < DataFileHeader::HeaderSize ) badOfs(ofs); // will uassert
- external call to keep out of the normal code path | | if( ofs < DataFileHeader::HeaderSize ) badOfs(ofs); // will uassert
- external call to keep out of the normal code path | |
|
| return (Record*) _p.at(ofs, -1); | | return (Record*) (p()+ofs); | |
| } | | } | |
| | | | |
|
| inline void MongoDataFile::grow(DiskLoc dl, int size) { | | | |
| int ofs = dl.getOfs(); | | | |
| _p.grow(ofs, size); | | | |
| } | | | |
| | | | |
| inline Record* MongoDataFile::makeRecord(DiskLoc dl, int size) { | | inline Record* MongoDataFile::makeRecord(DiskLoc dl, int size) { | |
| int ofs = dl.getOfs(); | | int ofs = dl.getOfs(); | |
| if( ofs < DataFileHeader::HeaderSize ) badOfs(ofs); // will uassert
- external call to keep out of the normal code path | | if( ofs < DataFileHeader::HeaderSize ) badOfs(ofs); // will uassert
- external call to keep out of the normal code path | |
|
| return (Record*) _p.at(ofs, size); | | return (Record*) (p()+ofs); | |
| } | | } | |
| | | | |
| inline DiskLoc Record::getNext(const DiskLoc& myLoc) { | | inline DiskLoc Record::getNext(const DiskLoc& myLoc) { | |
| if ( nextOfs != DiskLoc::NullOfs ) { | | if ( nextOfs != DiskLoc::NullOfs ) { | |
| /* defensive */ | | /* defensive */ | |
| if ( nextOfs >= 0 && nextOfs < 10 ) { | | if ( nextOfs >= 0 && nextOfs < 10 ) { | |
| sayDbContext("Assertion failure - Record::getNext() referen
cing a deleted record?"); | | sayDbContext("Assertion failure - Record::getNext() referen
cing a deleted record?"); | |
| return DiskLoc(); | | return DiskLoc(); | |
| } | | } | |
| | | | |
| | | | |
| skipping to change at line 396 | | skipping to change at line 461 | |
| Extent *e = myExtent(myLoc); | | Extent *e = myExtent(myLoc); | |
| if ( e->xprev.isNull() ) | | if ( e->xprev.isNull() ) | |
| return DiskLoc(); | | return DiskLoc(); | |
| return e->xprev.ext()->lastRecord; | | return e->xprev.ext()->lastRecord; | |
| } | | } | |
| | | | |
| inline Record* DiskLoc::rec() const { | | inline Record* DiskLoc::rec() const { | |
| return DataFileMgr::getRecord(*this); | | return DataFileMgr::getRecord(*this); | |
| } | | } | |
| inline BSONObj DiskLoc::obj() const { | | inline BSONObj DiskLoc::obj() const { | |
|
| return BSONObj(rec()); | | return BSONObj(rec()->accessed()); | |
| } | | } | |
| inline DeletedRecord* DiskLoc::drec() const { | | inline DeletedRecord* DiskLoc::drec() const { | |
|
| assert( fileNo != -1 ); | | assert( _a != -1 ); | |
| return (DeletedRecord*) rec(); | | return (DeletedRecord*) rec(); | |
| } | | } | |
| inline Extent* DiskLoc::ext() const { | | inline Extent* DiskLoc::ext() const { | |
| return DataFileMgr::getExtent(*this); | | return DataFileMgr::getExtent(*this); | |
| } | | } | |
| | | | |
|
| /*---------------------------------------------------------------------
*/ | | template< class V > | |
| | | inline | |
| | | const BtreeBucket<V> * DiskLoc::btree() const { | |
| | | assert( _a != -1 ); | |
| | | return (const BtreeBucket<V> *) rec()->data; | |
| | | } | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
|
| #include "rec.h" | | | |
| #include "database.h" | | #include "database.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| // Heritable class to implement an operation that may be applied to all | | | |
| // files in a database using _applyOpToDataFiles() | | | |
| class FileOp { | | | |
| public: | | | |
| virtual ~FileOp() {} | | | |
| // Return true if file exists and operation successful | | | |
| virtual bool apply( const boost::filesystem::path &p ) = 0; | | | |
| virtual const char * op() const = 0; | | | |
| }; | | | |
| | | | |
| void _applyOpToDataFiles( const char *database, FileOp &fo, bool afterA
llocator = false, const string& path = dbpath ); | | | |
| | | | |
| inline void _deleteDataFiles(const char *database) { | | | |
| if ( directoryperdb ) { | | | |
| BOOST_CHECK_EXCEPTION( boost::filesystem::remove_all( boost::fi
lesystem::path( dbpath ) / database ) ); | | | |
| return; | | | |
| } | | | |
| class : public FileOp { | | | |
| virtual bool apply( const boost::filesystem::path &p ) { | | | |
| return boost::filesystem::remove( p ); | | | |
| } | | | |
| virtual const char * op() const { | | | |
| return "remove"; | | | |
| } | | | |
| } deleter; | | | |
| _applyOpToDataFiles( database, deleter, true ); | | | |
| } | | | |
| | | | |
| boost::intmax_t dbSize( const char *database ); | | boost::intmax_t dbSize( const char *database ); | |
| | | | |
| inline NamespaceIndex* nsindex(const char *ns) { | | inline NamespaceIndex* nsindex(const char *ns) { | |
| Database *database = cc().database(); | | Database *database = cc().database(); | |
| assert( database ); | | assert( database ); | |
| DEV { | | DEV { | |
| char buf[256]; | | char buf[256]; | |
| nsToDatabase(ns, buf); | | nsToDatabase(ns, buf); | |
| if ( database->name != buf ) { | | if ( database->name != buf ) { | |
| out() << "ERROR: attempt to write to wrong database databas
e\n"; | | out() << "ERROR: attempt to write to wrong database databas
e\n"; | |
| | | | |
| skipping to change at line 466 | | skipping to change at line 507 | |
| } | | } | |
| } | | } | |
| return &database->namespaceIndex; | | return &database->namespaceIndex; | |
| } | | } | |
| | | | |
| inline NamespaceDetails* nsdetails(const char *ns) { | | inline NamespaceDetails* nsdetails(const char *ns) { | |
| // if this faults, did you set the current db first? (Client::Cont
ext + dblock) | | // if this faults, did you set the current db first? (Client::Cont
ext + dblock) | |
| return nsindex(ns)->details(ns); | | return nsindex(ns)->details(ns); | |
| } | | } | |
| | | | |
|
| inline MongoDataFile& DiskLoc::pdf() const { | | | |
| assert( fileNo != -1 ); | | | |
| return *cc().database()->getFile(fileNo); | | | |
| } | | | |
| | | | |
| inline Extent* DataFileMgr::getExtent(const DiskLoc& dl) { | | inline Extent* DataFileMgr::getExtent(const DiskLoc& dl) { | |
| assert( dl.a() != -1 ); | | assert( dl.a() != -1 ); | |
| return cc().database()->getFile(dl.a())->getExtent(dl); | | return cc().database()->getFile(dl.a())->getExtent(dl); | |
| } | | } | |
| | | | |
| inline Record* DataFileMgr::getRecord(const DiskLoc& dl) { | | inline Record* DataFileMgr::getRecord(const DiskLoc& dl) { | |
| assert( dl.a() != -1 ); | | assert( dl.a() != -1 ); | |
| return cc().database()->getFile(dl.a())->recordAt(dl); | | return cc().database()->getFile(dl.a())->recordAt(dl); | |
| } | | } | |
| | | | |
|
| BOOST_STATIC_ASSERT( 16 == sizeof(DeletedRecord) ); | | BOOST_STATIC_ASSERT( 16 == sizeof(DeletedRecord) ); | |
| | | | |
| inline void DataFileMgr::grow(const DiskLoc& dl, int len) { | | | |
| assert( dl.a() != -1 ); | | | |
| cc().database()->getFile(dl.a())->grow(dl, len); | | | |
| } | | | |
| | | | |
| inline DeletedRecord* DataFileMgr::makeDeletedRecord(const DiskLoc& dl,
int len) { | | inline DeletedRecord* DataFileMgr::makeDeletedRecord(const DiskLoc& dl,
int len) { | |
| assert( dl.a() != -1 ); | | assert( dl.a() != -1 ); | |
| return (DeletedRecord*) cc().database()->getFile(dl.a())->makeRecor
d(dl, sizeof(DeletedRecord)); | | return (DeletedRecord*) cc().database()->getFile(dl.a())->makeRecor
d(dl, sizeof(DeletedRecord)); | |
| } | | } | |
| | | | |
| void ensureHaveIdIndex(const char *ns); | | void ensureHaveIdIndex(const char *ns); | |
| | | | |
| bool dropIndexes( NamespaceDetails *d, const char *ns, const char *name
, string &errmsg, BSONObjBuilder &anObjBuilder, bool maydeleteIdIndex ); | | bool dropIndexes( NamespaceDetails *d, const char *ns, const char *name
, string &errmsg, BSONObjBuilder &anObjBuilder, bool maydeleteIdIndex ); | |
| | | | |
|
| /** | | inline BSONObj::BSONObj(const Record *r) { | |
| * @return true if ns is ok | | init(r->data); | |
| */ | | | |
| inline bool nsDollarCheck( const char* ns ){ | | | |
| if ( strchr( ns , '$' ) == 0 ) | | | |
| return true; | | | |
| | | | |
| return strcmp( ns, "local.oplog.$main" ) == 0; | | | |
| } | | } | |
|
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 54 change blocks. |
| 121 lines changed or deleted | | 147 lines changed or added | |
|
| query.h | | query.h | |
| | | | |
| skipping to change at line 21 | | skipping to change at line 21 | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Affero General Public License for more details. | | * GNU Affero General Public License for more details. | |
| * | | * | |
| * You should have received a copy of the GNU Affero General Public Licen
se | | * You should have received a copy of the GNU Affero General Public Licen
se | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
|
| #include "../pch.h" | | #include "../../pch.h" | |
| #include "../util/message.h" | | #include "../../util/net/message.h" | |
| #include "dbmessage.h" | | #include "../dbmessage.h" | |
| #include "jsobj.h" | | #include "../jsobj.h" | |
| #include "diskloc.h" | | #include "../diskloc.h" | |
| | | #include "../projection.h" | |
| /* db request message format | | | |
| | | | |
| unsigned opid; // arbitary; will be echoed back | | | |
| byte operation; | | | |
| int options; | | | |
| | | | |
| then for: | | | |
| | | | |
| dbInsert: | | | |
| string collection; | | | |
| a series of JSObjects | | | |
| dbDelete: | | | |
| string collection; | | | |
| int flags=0; // 1=DeleteSingle | | | |
| JSObject query; | | | |
| dbUpdate: | | | |
| string collection; | | | |
| int flags; // 1=upsert | | | |
| JSObject query; | | | |
| JSObject objectToUpdate; | | | |
| objectToUpdate may include { $inc: <field> } or { $set: ... }, see
struct Mod. | | | |
| dbQuery: | | | |
| string collection; | | | |
| int nToSkip; | | | |
| int nToReturn; // how many you want back as the beginning of the c
ursor data (0=no limit) | | | |
| // greater than zero is simply a hint on how many obje
cts to send back per "cursor batch". | | | |
| // a negative number indicates a hard limit. | | | |
| JSObject query; | | | |
| [JSObject fieldsToReturn] | | | |
| dbGetMore: | | | |
| string collection; // redundant, might use for security. | | | |
| int nToReturn; | | | |
| int64 cursorID; | | | |
| dbKillCursors=2007: | | | |
| int n; | | | |
| int64 cursorIDs[n]; | | | |
| | | | |
| Note that on Update, there is only one object, which is different | | | |
| from insert where you can pass a list of objects to insert in the db. | | | |
| Note that the update field layout is very similar layout to Query. | | | |
| */ | | | |
| | | | |
| // struct QueryOptions, QueryResult, QueryResultFlags in: | | // struct QueryOptions, QueryResult, QueryResultFlags in: | |
|
| #include "../client/dbclient.h" | | #include "../../client/dbclient.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| extern const int MaxBytesToReturnToClientAtOnce; | | extern const int MaxBytesToReturnToClientAtOnce; | |
| | | | |
|
| // for an existing query (ie a ClientCursor), send back additional info
rmation. | | | |
| struct GetMoreWaitException { }; | | | |
| | | | |
| QueryResult* processGetMore(const char *ns, int ntoreturn, long long cu
rsorid , CurOp& op, int pass, bool& exhaust); | | QueryResult* processGetMore(const char *ns, int ntoreturn, long long cu
rsorid , CurOp& op, int pass, bool& exhaust); | |
| | | | |
|
| struct UpdateResult { | | | |
| bool existing; // if existing objects were modified | | | |
| bool mod; // was this a $ mod | | | |
| long long num; // how many objects touched | | | |
| OID upserted; // if something was upserted, the new _id of the obj
ect | | | |
| | | | |
| UpdateResult( bool e, bool m, unsigned long long n , const BSONObj&
upsertedObject = BSONObj() ) | | | |
| : existing(e) , mod(m), num(n){ | | | |
| upserted.clear(); | | | |
| | | | |
| BSONElement id = upsertedObject["_id"]; | | | |
| if ( ! e && n == 1 && id.type() == jstOID ){ | | | |
| upserted = id.OID(); | | | |
| } | | | |
| } | | | |
| | | | |
| }; | | | |
| | | | |
| class RemoveSaver; | | | |
| | | | |
| /* returns true if an existing object was updated, false if no existing
object was found. | | | |
| multi - update multiple objects - mostly useful with things like $se
t | | | |
| god - allow access to system namespaces | | | |
| */ | | | |
| UpdateResult updateObjects(const char *ns, const BSONObj& updateobj, BS
ONObj pattern, bool upsert, bool multi , bool logop , OpDebug& debug ); | | | |
| UpdateResult _updateObjects(bool god, const char *ns, const BSONObj& up
dateobj, BSONObj pattern, | | | |
| bool upsert, bool multi , bool logop , OpDe
bug& debug , RemoveSaver * rs = 0 ); | | | |
| | | | |
| // If justOne is true, deletedId is set to the id of the deleted object
. | | | |
| long long deleteObjects(const char *ns, BSONObj pattern, bool justOne,
bool logop = false, bool god=false, RemoveSaver * rs=0); | | | |
| | | | |
| long long runCount(const char *ns, const BSONObj& cmd, string& err); | | long long runCount(const char *ns, const BSONObj& cmd, string& err); | |
| | | | |
| const char * runQuery(Message& m, QueryMessage& q, CurOp& curop, Messag
e &result); | | const char * runQuery(Message& m, QueryMessage& q, CurOp& curop, Messag
e &result); | |
| | | | |
| /* This is for languages whose "objects" are not well ordered (JSON is
well ordered). | | /* This is for languages whose "objects" are not well ordered (JSON is
well ordered). | |
| [ { a : ... } , { b : ... } ] -> { a : ..., b : ... } | | [ { a : ... } , { b : ... } ] -> { a : ..., b : ... } | |
| */ | | */ | |
| inline BSONObj transformOrderFromArrayFormat(BSONObj order) { | | inline BSONObj transformOrderFromArrayFormat(BSONObj order) { | |
| /* note: this is slow, but that is ok as order will have very few p
ieces */ | | /* note: this is slow, but that is ok as order will have very few p
ieces */ | |
| BSONObjBuilder b; | | BSONObjBuilder b; | |
| | | | |
| skipping to change at line 144 | | skipping to change at line 69 | |
| } | | } | |
| | | | |
| return b.obj(); | | return b.obj(); | |
| } | | } | |
| | | | |
| /** | | /** | |
| * this represents a total user query | | * this represents a total user query | |
| * includes fields from the query message, both possible query levels | | * includes fields from the query message, both possible query levels | |
| * parses everything up front | | * parses everything up front | |
| */ | | */ | |
|
| class ParsedQuery { | | class ParsedQuery : boost::noncopyable { | |
| public: | | public: | |
| ParsedQuery( QueryMessage& qm ) | | ParsedQuery( QueryMessage& qm ) | |
|
| : _ns( qm.ns ) , _ntoskip( qm.ntoskip ) , _ntoreturn( qm.ntoret
urn ) , _options( qm.queryOptions ){ | | : _ns( qm.ns ) , _ntoskip( qm.ntoskip ) , _ntoreturn( qm.ntoret
urn ) , _options( qm.queryOptions ) { | |
| init( qm.query ); | | init( qm.query ); | |
| initFields( qm.fields ); | | initFields( qm.fields ); | |
| } | | } | |
| ParsedQuery( const char* ns , int ntoskip , int ntoreturn , int que
ryoptions , const BSONObj& query , const BSONObj& fields ) | | ParsedQuery( const char* ns , int ntoskip , int ntoreturn , int que
ryoptions , const BSONObj& query , const BSONObj& fields ) | |
|
| : _ns( ns ) , _ntoskip( ntoskip ) , _ntoreturn( ntoreturn ) , _
options( queryoptions ){ | | : _ns( ns ) , _ntoskip( ntoskip ) , _ntoreturn( ntoreturn ) , _
options( queryoptions ) { | |
| init( query ); | | init( query ); | |
| initFields( fields ); | | initFields( fields ); | |
| } | | } | |
| | | | |
|
| ~ParsedQuery(){} | | | |
| | | | |
| const char * ns() const { return _ns; } | | const char * ns() const { return _ns; } | |
| bool isLocalDB() const { return strncmp(_ns, "local.", 6) == 0; } | | bool isLocalDB() const { return strncmp(_ns, "local.", 6) == 0; } | |
| | | | |
| const BSONObj& getFilter() const { return _filter; } | | const BSONObj& getFilter() const { return _filter; } | |
|
| FieldMatcher* getFields() const { return _fields.get(); } | | Projection* getFields() const { return _fields.get(); } | |
| shared_ptr<FieldMatcher> getFieldPtr() const { return _fields; } | | shared_ptr<Projection> getFieldPtr() const { return _fields; } | |
| | | | |
| int getSkip() const { return _ntoskip; } | | int getSkip() const { return _ntoskip; } | |
| int getNumToReturn() const { return _ntoreturn; } | | int getNumToReturn() const { return _ntoreturn; } | |
| bool wantMore() const { return _wantMore; } | | bool wantMore() const { return _wantMore; } | |
| int getOptions() const { return _options; } | | int getOptions() const { return _options; } | |
| bool hasOption( int x ) const { return x & _options; } | | bool hasOption( int x ) const { return x & _options; } | |
| | | | |
| bool isExplain() const { return _explain; } | | bool isExplain() const { return _explain; } | |
| bool isSnapshot() const { return _snapshot; } | | bool isSnapshot() const { return _snapshot; } | |
| bool returnKey() const { return _returnKey; } | | bool returnKey() const { return _returnKey; } | |
| | | | |
| skipping to change at line 212 | | skipping to change at line 135 | |
| return n >= _ntoreturn || len > MaxBytesToReturnToClientAtOnce; | | return n >= _ntoreturn || len > MaxBytesToReturnToClientAtOnce; | |
| } | | } | |
| | | | |
| bool enough( int n ) const { | | bool enough( int n ) const { | |
| if ( _ntoreturn == 0 ) | | if ( _ntoreturn == 0 ) | |
| return false; | | return false; | |
| return n >= _ntoreturn; | | return n >= _ntoreturn; | |
| } | | } | |
| | | | |
| private: | | private: | |
|
| void init( const BSONObj& q ){ | | void init( const BSONObj& q ) { | |
| _reset(); | | _reset(); | |
| uassert( 10105 , "bad skip value in query", _ntoskip >= 0); | | uassert( 10105 , "bad skip value in query", _ntoskip >= 0); | |
| | | | |
|
| if ( _ntoreturn < 0 ){ | | if ( _ntoreturn < 0 ) { | |
| /* _ntoreturn greater than zero is simply a hint on how man
y objects to send back per | | /* _ntoreturn greater than zero is simply a hint on how man
y objects to send back per | |
| "cursor batch". | | "cursor batch". | |
| A negative number indicates a hard limit. | | A negative number indicates a hard limit. | |
| */ | | */ | |
| _wantMore = false; | | _wantMore = false; | |
| _ntoreturn = -_ntoreturn; | | _ntoreturn = -_ntoreturn; | |
| } | | } | |
| | | | |
| BSONElement e = q["query"]; | | BSONElement e = q["query"]; | |
| if ( ! e.isABSONObj() ) | | if ( ! e.isABSONObj() ) | |
| e = q["$query"]; | | e = q["$query"]; | |
| | | | |
|
| if ( e.isABSONObj() ){ | | if ( e.isABSONObj() ) { | |
| _filter = e.embeddedObject(); | | _filter = e.embeddedObject(); | |
| _initTop( q ); | | _initTop( q ); | |
| } | | } | |
| else { | | else { | |
| _filter = q; | | _filter = q; | |
| } | | } | |
| } | | } | |
| | | | |
|
| void _reset(){ | | void _reset() { | |
| _wantMore = true; | | _wantMore = true; | |
| _explain = false; | | _explain = false; | |
| _snapshot = false; | | _snapshot = false; | |
| _returnKey = false; | | _returnKey = false; | |
| _showDiskLoc = false; | | _showDiskLoc = false; | |
| _maxScan = 0; | | _maxScan = 0; | |
| } | | } | |
| | | | |
|
| void _initTop( const BSONObj& top ){ | | void _initTop( const BSONObj& top ) { | |
| BSONObjIterator i( top ); | | BSONObjIterator i( top ); | |
|
| while ( i.more() ){ | | while ( i.more() ) { | |
| BSONElement e = i.next(); | | BSONElement e = i.next(); | |
| const char * name = e.fieldName(); | | const char * name = e.fieldName(); | |
| | | | |
| if ( strcmp( "$orderby" , name ) == 0 || | | if ( strcmp( "$orderby" , name ) == 0 || | |
|
| strcmp( "orderby" , name ) == 0 ){ | | strcmp( "orderby" , name ) == 0 ) { | |
| if ( e.type() == Object ) | | if ( e.type() == Object ) { | |
| _order = e.embeddedObject(); | | _order = e.embeddedObject(); | |
|
| else if ( e.type() == Array ) | | } | |
| | | else if ( e.type() == Array ) { | |
| _order = transformOrderFromArrayFormat( _order ); | | _order = transformOrderFromArrayFormat( _order ); | |
|
| else | | } | |
| assert( 0 ); | | else { | |
| | | uasserted(13513, "sort must be an object or array")
; | |
| | | } | |
| | | continue; | |
| } | | } | |
|
| else if ( strcmp( "$explain" , name ) == 0 ) | | | |
| _explain = e.trueValue(); | | | |
| else if ( strcmp( "$snapshot" , name ) == 0 ) | | | |
| _snapshot = e.trueValue(); | | | |
| else if ( strcmp( "$min" , name ) == 0 ) | | | |
| _min = e.embeddedObject(); | | | |
| else if ( strcmp( "$max" , name ) == 0 ) | | | |
| _max = e.embeddedObject(); | | | |
| else if ( strcmp( "$hint" , name ) == 0 ) | | | |
| _hint = e; | | | |
| else if ( strcmp( "$returnKey" , name ) == 0 ) | | | |
| _returnKey = e.trueValue(); | | | |
| else if ( strcmp( "$maxScan" , name ) == 0 ) | | | |
| _maxScan = e.numberInt(); | | | |
| else if ( strcmp( "$showDiskLoc" , name ) == 0 ) | | | |
| _showDiskLoc = e.trueValue(); | | | |
| | | | |
|
| | | if( *name == '$' ) { | |
| | | name++; | |
| | | if ( strcmp( "explain" , name ) == 0 ) | |
| | | _explain = e.trueValue(); | |
| | | else if ( strcmp( "snapshot" , name ) == 0 ) | |
| | | _snapshot = e.trueValue(); | |
| | | else if ( strcmp( "min" , name ) == 0 ) | |
| | | _min = e.embeddedObject(); | |
| | | else if ( strcmp( "max" , name ) == 0 ) | |
| | | _max = e.embeddedObject(); | |
| | | else if ( strcmp( "hint" , name ) == 0 ) | |
| | | _hint = e; | |
| | | else if ( strcmp( "returnKey" , name ) == 0 ) | |
| | | _returnKey = e.trueValue(); | |
| | | else if ( strcmp( "maxScan" , name ) == 0 ) | |
| | | _maxScan = e.numberInt(); | |
| | | else if ( strcmp( "showDiskLoc" , name ) == 0 ) | |
| | | _showDiskLoc = e.trueValue(); | |
| | | else if ( strcmp( "comment" , name ) == 0 ) { | |
| | | ; // no-op | |
| | | } | |
| | | } | |
| } | | } | |
| | | | |
|
| if ( _snapshot ){ | | if ( _snapshot ) { | |
| uassert( 12001 , "E12001 can't sort with $snapshot", _order
.isEmpty() ); | | uassert( 12001 , "E12001 can't sort with $snapshot", _order
.isEmpty() ); | |
| uassert( 12002 , "E12002 can't use hint with $snapshot", _h
int.eoo() ); | | uassert( 12002 , "E12002 can't use hint with $snapshot", _h
int.eoo() ); | |
| } | | } | |
| | | | |
| } | | } | |
| | | | |
|
| void initFields( const BSONObj& fields ){ | | void initFields( const BSONObj& fields ) { | |
| if ( fields.isEmpty() ) | | if ( fields.isEmpty() ) | |
| return; | | return; | |
|
| _fields.reset( new FieldMatcher() ); | | _fields.reset( new Projection() ); | |
| _fields->add( fields ); | | _fields->init( fields ); | |
| } | | | |
| | | | |
| ParsedQuery( const ParsedQuery& other ){ | | | |
| assert(0); | | | |
| } | | } | |
| | | | |
|
| const char* _ns; | | const char * const _ns; | |
| int _ntoskip; | | const int _ntoskip; | |
| int _ntoreturn; | | int _ntoreturn; | |
|
| int _options; | | | |
| | | | |
| BSONObj _filter; | | BSONObj _filter; | |
|
| shared_ptr< FieldMatcher > _fields; | | BSONObj _order; | |
| | | const int _options; | |
| | | shared_ptr< Projection > _fields; | |
| bool _wantMore; | | bool _wantMore; | |
|
| | | | |
| bool _explain; | | bool _explain; | |
| bool _snapshot; | | bool _snapshot; | |
| bool _returnKey; | | bool _returnKey; | |
| bool _showDiskLoc; | | bool _showDiskLoc; | |
| BSONObj _min; | | BSONObj _min; | |
| BSONObj _max; | | BSONObj _max; | |
| BSONElement _hint; | | BSONElement _hint; | |
|
| BSONObj _order; | | | |
| int _maxScan; | | int _maxScan; | |
| }; | | }; | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
|
| | | | |
| #include "clientcursor.h" | | | |
| | | | |
End of changes. 29 change blocks. |
| 132 lines changed or deleted | | 58 lines changed or added | |
|
| queryoptimizer.h | | queryoptimizer.h | |
|
| /* queryoptimizer.h */ | | // @file queryoptimizer.h | |
| | | | |
| /** | | /** | |
| * Copyright (C) 2008 10gen Inc. | | * Copyright (C) 2008 10gen Inc. | |
| * | | * | |
| * This program is free software: you can redistribute it and/or modify | | * 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
, | | * it under the terms of the GNU Affero General Public License, version 3
, | |
| * as published by the Free Software Foundation. | | * as published by the Free Software Foundation. | |
| * | | * | |
| * This program is distributed in the hope that it will be useful, | | * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| | | | |
| skipping to change at line 25 | | skipping to change at line 25 | |
| * You should have received a copy of the GNU Affero General Public Licen
se | | * You should have received a copy of the GNU Affero General Public Licen
se | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "cursor.h" | | #include "cursor.h" | |
| #include "jsobj.h" | | #include "jsobj.h" | |
| #include "queryutil.h" | | #include "queryutil.h" | |
| #include "matcher.h" | | #include "matcher.h" | |
|
| #include "../util/message.h" | | #include "../util/net/listen.h" | |
| | | #include <queue> | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| class IndexDetails; | | class IndexDetails; | |
| class IndexType; | | class IndexType; | |
|
| | | class ElapsedTracker; | |
| | | | |
|
| | | /** A plan for executing a query using the given index spec and FieldRa
ngeSet. */ | |
| class QueryPlan : boost::noncopyable { | | class QueryPlan : boost::noncopyable { | |
| public: | | public: | |
|
| QueryPlan(NamespaceDetails *_d, | | | |
| int _idxNo, // -1 = no index | | /** | |
| const FieldRangeSet &fbs, | | * @param originalFrsp - original constraints for this query clause
. If null, frsp will be used instead. | |
| | | */ | |
| | | QueryPlan(NamespaceDetails *d, | |
| | | int idxNo, // -1 = no index | |
| | | const FieldRangeSetPair &frsp, | |
| | | const FieldRangeSetPair *originalFrsp, | |
| const BSONObj &originalQuery, | | const BSONObj &originalQuery, | |
| const BSONObj &order, | | const BSONObj &order, | |
| const BSONObj &startKey = BSONObj(), | | const BSONObj &startKey = BSONObj(), | |
|
| const BSONObj &endKey = BSONObj() , | | const BSONObj &endKey = BSONObj(), | |
| string special="" ); | | string special="" ); | |
| | | | |
|
| /* If true, no other index can do better. */ | | /** @return true iff no other plans should be considered. */ | |
| bool optimal() const { return optimal_; } | | bool optimal() const { return _optimal; } | |
| /* ScanAndOrder processing will be required if true */ | | /* @return true iff this plan should not be considered at all. */ | |
| bool scanAndOrderRequired() const { return scanAndOrderRequired_; } | | bool unhelpful() const { return _unhelpful; } | |
| /* When true, the index we are using has keys such that it can comp
letely resolve the | | /** @return true iff ScanAndOrder processing will be required for r
esult set. */ | |
| query expression to match by itself without ever checking the main
object. | | bool scanAndOrderRequired() const { return _scanAndOrderRequired; } | |
| | | /** | |
| | | * @return true iff the index we are using has keys such that it ca
n completely resolve the | |
| | | * query expression to match by itself without ever checking the ma
in object. | |
| */ | | */ | |
|
| bool exactKeyMatch() const { return exactKeyMatch_; } | | bool exactKeyMatch() const { return _exactKeyMatch; } | |
| /* If true, the startKey and endKey are unhelpful and the index ord
er doesn't match the | | /** @return true iff this QueryPlan would perform an unindexed scan
. */ | |
| requested sort order */ | | bool willScanTable() const { return _idxNo < 0 && !_impossible; } | |
| bool unhelpful() const { return unhelpful_; } | | | |
| int direction() const { return direction_; } | | /** @return a new cursor based on this QueryPlan's index and FieldR
angeSet. */ | |
| shared_ptr<Cursor> newCursor( const DiskLoc &startLoc = DiskLoc() ,
int numWanted=0 ) const; | | shared_ptr<Cursor> newCursor( const DiskLoc &startLoc = DiskLoc() ,
int numWanted=0 ) const; | |
|
| | | /** @return a new reverse cursor if this is an unindexed plan. */ | |
| shared_ptr<Cursor> newReverseCursor() const; | | shared_ptr<Cursor> newReverseCursor() const; | |
|
| | | /** Register this plan as a winner for its QueryPattern, with speci
fied 'nscanned'. */ | |
| | | void registerSelf( long long nScanned ) const; | |
| | | | |
| | | int direction() const { return _direction; } | |
| BSONObj indexKey() const; | | BSONObj indexKey() const; | |
|
| bool willScanTable() const { return !index_ && fbs_.matchPossible()
; } | | bool indexed() const { return _index; } | |
| const char *ns() const { return fbs_.ns(); } | | int idxNo() const { return _idxNo; } | |
| NamespaceDetails *nsd() const { return d; } | | const char *ns() const { return _frs.ns(); } | |
| | | NamespaceDetails *nsd() const { return _d; } | |
| BSONObj originalQuery() const { return _originalQuery; } | | BSONObj originalQuery() const { return _originalQuery; } | |
|
| BSONObj simplifiedQuery( const BSONObj& fields = BSONObj() ) const
{ return fbs_.simplifiedQuery( fields ); } | | BSONObj simplifiedQuery( const BSONObj& fields = BSONObj() ) const
{ return _frs.simplifiedQuery( fields ); } | |
| const FieldRange &range( const char *fieldName ) const { return fbs
_.range( fieldName ); } | | const FieldRange &range( const char *fieldName ) const { return _fr
s.range( fieldName ); } | |
| void registerSelf( long long nScanned ) const; | | shared_ptr<FieldRangeVector> originalFrv() const { return _original
Frv; } | |
| shared_ptr< FieldRangeVector > frv() const { return _frv; } | | | |
| | | const FieldRangeSet &multikeyFrs() const { return _frsMulti; } | |
| | | | |
| | | /** just for testing */ | |
| | | | |
| | | shared_ptr<FieldRangeVector> frv() const { return _frv; } | |
| | | bool isMultiKey() const; | |
| | | | |
| private: | | private: | |
|
| NamespaceDetails *d; | | NamespaceDetails * _d; | |
| int idxNo; | | int _idxNo; | |
| const FieldRangeSet &fbs_; | | const FieldRangeSet &_frs; | |
| | | const FieldRangeSet &_frsMulti; | |
| const BSONObj &_originalQuery; | | const BSONObj &_originalQuery; | |
|
| const BSONObj &order_; | | const BSONObj &_order; | |
| const IndexDetails *index_; | | const IndexDetails * _index; | |
| bool optimal_; | | bool _optimal; | |
| bool scanAndOrderRequired_; | | bool _scanAndOrderRequired; | |
| bool exactKeyMatch_; | | bool _exactKeyMatch; | |
| int direction_; | | int _direction; | |
| shared_ptr< FieldRangeVector > _frv; | | shared_ptr<FieldRangeVector> _frv; | |
| | | shared_ptr<FieldRangeVector> _originalFrv; | |
| BSONObj _startKey; | | BSONObj _startKey; | |
| BSONObj _endKey; | | BSONObj _endKey; | |
|
| bool endKeyInclusive_; | | bool _endKeyInclusive; | |
| bool unhelpful_; | | bool _unhelpful; | |
| | | bool _impossible; | |
| string _special; | | string _special; | |
| IndexType * _type; | | IndexType * _type; | |
| bool _startOrEndSpec; | | bool _startOrEndSpec; | |
| }; | | }; | |
| | | | |
|
| // Inherit from this interface to implement a new query operation. | | /** | |
| // The query optimizer will clone the QueryOp that is provided, giving | | * Inherit from this interface to implement a new query operation. | |
| // each clone its own query plan. | | * The query optimizer will clone the QueryOp that is provided, giving | |
| | | * each clone its own query plan. | |
| | | * | |
| | | * Normal sequence of events: | |
| | | * 1) A new QueryOp is generated using createChild(). | |
| | | * 2) A QueryPlan is assigned to this QueryOp with setQueryPlan(). | |
| | | * 3) _init() is called on the QueryPlan. | |
| | | * 4) next() is called repeatedly, with nscanned() checked after each c
all. | |
| | | * 5) In one of these calls to next(), setComplete() is called. | |
| | | * 6) The QueryPattern for the QueryPlan may be recorded as a winner. | |
| | | */ | |
| class QueryOp { | | class QueryOp { | |
| public: | | public: | |
| QueryOp() : _complete(), _stopRequested(), _qp(), _error() {} | | QueryOp() : _complete(), _stopRequested(), _qp(), _error() {} | |
| | | | |
|
| // Used when handing off from one QueryOp type to another | | /** Used when handing off from one QueryOp to another. */ | |
| QueryOp( const QueryOp &other ) : | | QueryOp( const QueryOp &other ) : | |
|
| _complete(), _stopRequested(), _qp(), _error(), _matcher( other._ma
tcher ), | | _complete(), _stopRequested(), _qp(), _error(), _matcher( other
._matcher ), | |
| _orConstraint( other._orConstraint ) {} | | _orConstraint( other._orConstraint ) {} | |
| | | | |
| virtual ~QueryOp() {} | | virtual ~QueryOp() {} | |
| | | | |
|
| /** these gets called after a query plan is set */ | | /** @return QueryPlan assigned to this QueryOp by the query optimiz
er. */ | |
| void init() { | | const QueryPlan &qp() const { return *_qp; } | |
| if ( _oldMatcher.get() ) { | | | |
| _matcher.reset( _oldMatcher->nextClauseMatcher( qp().indexK
ey() ) ); | | | |
| } else { | | | |
| _matcher.reset( new CoveredIndexMatcher( qp().originalQuery
(), qp().indexKey(), alwaysUseRecord() ) ); | | | |
| } | | | |
| _init(); | | | |
| } | | | |
| virtual void next() = 0; | | | |
| | | | |
| virtual bool mayRecordPlan() const = 0; | | | |
| | | | |
|
| | | /** Advance to next potential matching document (eg using a cursor)
. */ | |
| | | virtual void next() = 0; | |
| | | /** | |
| | | * @return current 'nscanned' metric for this QueryOp. Used to com
pare | |
| | | * cost to other QueryOps. | |
| | | */ | |
| | | virtual long long nscanned() = 0; | |
| | | /** Take any steps necessary before the db mutex is yielded. */ | |
| virtual bool prepareToYield() { massert( 13335, "yield not supporte
d", false ); return false; } | | virtual bool prepareToYield() { massert( 13335, "yield not supporte
d", false ); return false; } | |
|
| | | /** Recover once the db mutex is regained. */ | |
| virtual void recoverFromYield() { massert( 13336, "yield not suppor
ted", false ); } | | virtual void recoverFromYield() { massert( 13336, "yield not suppor
ted", false ); } | |
| | | | |
|
| virtual long long nscanned() = 0; | | /** | |
| | | * @return true iff the QueryPlan for this QueryOp may be registere
d | |
| | | * as a winning plan. | |
| | | */ | |
| | | virtual bool mayRecordPlan() const = 0; | |
| | | | |
|
| /** @return a copy of the inheriting class, which will be run with
its own | | /** @return true iff the implementation called setComplete() or set
Stop(). */ | |
| query plan. If multiple plan sets are required for an
$or query, | | | |
| the QueryOp of the winning plan from a given set will b
e cloned | | | |
| to generate QueryOps for the subsequent plan set. This
function | | | |
| should only be called after the query op has completed
executing. | | | |
| */ | | | |
| QueryOp *createChild() { | | | |
| if( _orConstraint.get() ) { | | | |
| _matcher->advanceOrClause( _orConstraint ); | | | |
| _orConstraint.reset(); | | | |
| } | | | |
| QueryOp *ret = _createChild(); | | | |
| ret->_oldMatcher = _matcher; | | | |
| return ret; | | | |
| } | | | |
| bool complete() const { return _complete; } | | bool complete() const { return _complete; } | |
|
| bool error() const { return _error; } | | /** @return true iff the implementation called steStop(). */ | |
| bool stopRequested() const { return _stopRequested; } | | bool stopRequested() const { return _stopRequested; } | |
|
| | | /** @return true iff the implementation threw an exception. */ | |
| | | bool error() const { return _error; } | |
| | | /** @return the exception thrown by implementation if one was throw
n. */ | |
| ExceptionInfo exception() const { return _exception; } | | ExceptionInfo exception() const { return _exception; } | |
|
| const QueryPlan &qp() const { return *_qp; } | | | |
| // To be called by QueryPlanSet::Runner only. | | /** To be called by QueryPlanSet::Runner only. */ | |
| void setQueryPlan( const QueryPlan *qp ) { _qp = qp; } | | | |
| | | QueryOp *createChild(); | |
| | | void setQueryPlan( const QueryPlan *qp ) { _qp = qp; assert( _qp !=
NULL ); } | |
| | | void init(); | |
| void setException( const DBException &e ) { | | void setException( const DBException &e ) { | |
| _error = true; | | _error = true; | |
| _exception = e.getInfo(); | | _exception = e.getInfo(); | |
| } | | } | |
|
| shared_ptr< CoveredIndexMatcher > matcher() const { return _matcher
; } | | | |
| | | shared_ptr<CoveredIndexMatcher> matcher( const shared_ptr<Cursor>&
c ) const { | |
| | | return matcher( c.get() ); | |
| | | } | |
| | | shared_ptr<CoveredIndexMatcher> matcher( Cursor* c ) const { | |
| | | if( ! c ) return _matcher; | |
| | | return c->matcher() ? c->matcherPtr() : _matcher; | |
| | | } | |
| | | | |
| protected: | | protected: | |
|
| | | /** Call if all results have been found. */ | |
| void setComplete() { | | void setComplete() { | |
|
| _orConstraint = qp().frv(); | | _orConstraint = qp().originalFrv(); | |
| _complete = true; | | _complete = true; | |
| } | | } | |
|
| | | /** Call if the scan is complete even if not all results have been
found. */ | |
| void setStop() { setComplete(); _stopRequested = true; } | | void setStop() { setComplete(); _stopRequested = true; } | |
| | | | |
|
| | | /** Handle initialization after a QueryPlan has been set. */ | |
| virtual void _init() = 0; | | virtual void _init() = 0; | |
| | | | |
|
| | | /** @return a copy of the inheriting class, which will be run with
its own query plan. */ | |
| virtual QueryOp *_createChild() const = 0; | | virtual QueryOp *_createChild() const = 0; | |
| | | | |
| virtual bool alwaysUseRecord() const { return false; } | | virtual bool alwaysUseRecord() const { return false; } | |
| | | | |
| private: | | private: | |
| bool _complete; | | bool _complete; | |
| bool _stopRequested; | | bool _stopRequested; | |
| ExceptionInfo _exception; | | ExceptionInfo _exception; | |
| const QueryPlan *_qp; | | const QueryPlan *_qp; | |
| bool _error; | | bool _error; | |
|
| shared_ptr< CoveredIndexMatcher > _matcher; | | shared_ptr<CoveredIndexMatcher> _matcher; | |
| shared_ptr< CoveredIndexMatcher > _oldMatcher; | | shared_ptr<CoveredIndexMatcher> _oldMatcher; | |
| shared_ptr< FieldRangeVector > _orConstraint; | | shared_ptr<FieldRangeVector> _orConstraint; | |
| }; | | }; | |
| | | | |
|
| // Set of candidate query plans for a particular query. Used for runni
ng | | // temp. this class works if T::operator< is variant unlike a regular
stl priority queue. | |
| // a QueryOp on these plans. | | // but it's very slow. however if v.size() is always very small, it wo
uld be fine, | |
| | | // maybe even faster than a smart impl that does more memory allocation
s. | |
| | | template<class T> | |
| | | class our_priority_queue : boost::noncopyable { | |
| | | vector<T> v; | |
| | | public: | |
| | | our_priority_queue() { | |
| | | v.reserve(4); | |
| | | } | |
| | | int size() const { return v.size(); } | |
| | | bool empty() const { return v.empty(); } | |
| | | void push(const T & x) { | |
| | | v.push_back(x); | |
| | | } | |
| | | T pop() { | |
| | | size_t t = 0; | |
| | | for( size_t i = 1; i < v.size(); i++ ) { | |
| | | if( v[t] < v[i] ) | |
| | | t = i; | |
| | | } | |
| | | T ret = v[t]; | |
| | | v.erase(v.begin()+t); | |
| | | return ret; | |
| | | } | |
| | | }; | |
| | | | |
| | | /** | |
| | | * A set of candidate query plans for a query. This class can return a
best buess plan or run a | |
| | | * QueryOp on all the plans. | |
| | | */ | |
| class QueryPlanSet { | | class QueryPlanSet { | |
| public: | | public: | |
| | | | |
|
| typedef boost::shared_ptr< QueryPlan > PlanPtr; | | typedef boost::shared_ptr<QueryPlan> QueryPlanPtr; | |
| typedef vector< PlanPtr > PlanSet; | | typedef vector<QueryPlanPtr> PlanSet; | |
| | | | |
|
| | | /** | |
| | | * @param originalFrsp - original constraints for this query clause
; if null, frsp will be used. | |
| | | */ | |
| QueryPlanSet( const char *ns, | | QueryPlanSet( const char *ns, | |
|
| auto_ptr< FieldRangeSet > frs, | | auto_ptr<FieldRangeSetPair> frsp, | |
| const BSONObj &originalQuery, | | auto_ptr<FieldRangeSetPair> originalFrsp, | |
| const BSONObj &order, | | const BSONObj &originalQuery, | |
| const BSONElement *hint = 0, | | const BSONObj &order, | |
| bool honorRecordedPlan = true, | | const BSONElement *hint = 0, | |
| const BSONObj &min = BSONObj(), | | bool honorRecordedPlan = true, | |
| const BSONObj &max = BSONObj(), | | const BSONObj &min = BSONObj(), | |
| bool bestGuessOnly = false, | | const BSONObj &max = BSONObj(), | |
| bool mayYield = false); | | bool bestGuessOnly = false, | |
| int nPlans() const { return plans_.size(); } | | bool mayYield = false); | |
| shared_ptr< QueryOp > runOp( QueryOp &op ); | | | |
| template< class T > | | /** @return number of candidate plans. */ | |
| shared_ptr< T > runOp( T &op ) { | | int nPlans() const { return _plans.size(); } | |
| return dynamic_pointer_cast< T >( runOp( static_cast< QueryOp&
>( op ) ) ); | | | |
| | | /** | |
| | | * Clone op for each query plan, and @return the first cloned op to
call | |
| | | * setComplete() or setStop(). | |
| | | */ | |
| | | | |
| | | shared_ptr<QueryOp> runOp( QueryOp &op ); | |
| | | template<class T> | |
| | | shared_ptr<T> runOp( T &op ) { | |
| | | return dynamic_pointer_cast<T>( runOp( static_cast<QueryOp&>( o
p ) ) ); | |
| } | | } | |
|
| | | | |
| | | /** Initialize or iterate a runner generated from @param originalOp
. */ | |
| | | shared_ptr<QueryOp> nextOp( QueryOp &originalOp, bool retried = fal
se ); | |
| | | | |
| | | /** Yield the runner member. */ | |
| | | | |
| | | bool prepareToYield(); | |
| | | void recoverFromYield(); | |
| | | | |
| | | QueryPlanPtr firstPlan() const { return _plans[ 0 ]; } | |
| | | | |
| | | /** @return metadata about cursors and index bounds for all plans,
suitable for explain output. */ | |
| BSONObj explain() const; | | BSONObj explain() const; | |
|
| bool usingPrerecordedPlan() const { return usingPrerecordedPlan_; } | | /** @return true iff a plan is selected based on previous success o
f this plan. */ | |
| PlanPtr getBestGuess() const; | | bool usingPrerecordedPlan() const { return _usingPrerecordedPlan; } | |
| | | /** @return a single plan that may work well for the specified quer
y. */ | |
| | | QueryPlanPtr getBestGuess() const; | |
| | | | |
| //for testing | | //for testing | |
|
| const FieldRangeSet &fbs() const { return *fbs_; } | | const FieldRangeSetPair &frsp() const { return *_frsp; } | |
| | | const FieldRangeSetPair *originalFrsp() const { return _originalFrs
p.get(); } | |
| | | bool modifiedKeys() const; | |
| | | bool hasMultiKey() const; | |
| | | | |
| private: | | private: | |
| void addOtherPlans( bool checkFirst ); | | void addOtherPlans( bool checkFirst ); | |
|
| void addPlan( PlanPtr plan, bool checkFirst ) { | | void addPlan( QueryPlanPtr plan, bool checkFirst ) { | |
| if ( checkFirst && plan->indexKey().woCompare( plans_[ 0 ]->ind
exKey() ) == 0 ) | | if ( checkFirst && plan->indexKey().woCompare( _plans[ 0 ]->ind
exKey() ) == 0 ) | |
| return; | | return; | |
|
| plans_.push_back( plan ); | | _plans.push_back( plan ); | |
| } | | } | |
| void init(); | | void init(); | |
| void addHint( IndexDetails &id ); | | void addHint( IndexDetails &id ); | |
|
| struct Runner { | | class Runner { | |
| | | public: | |
| Runner( QueryPlanSet &plans, QueryOp &op ); | | Runner( QueryPlanSet &plans, QueryOp &op ); | |
|
| shared_ptr< QueryOp > run(); | | | |
| void mayYield( const vector< shared_ptr< QueryOp > > &ops ); | | /** | |
| QueryOp &op_; | | * Iterate interactively through candidate documents on all pla
ns. | |
| QueryPlanSet &plans_; | | * QueryOp objects are returned at each interleaved step. | |
| | | */ | |
| | | | |
| | | /** @return a plan that has completed, otherwise an arbitrary p
lan. */ | |
| | | shared_ptr<QueryOp> init(); | |
| | | /** | |
| | | * Move the Runner forward one iteration, and @return the plan
for | |
| | | * this iteration. | |
| | | */ | |
| | | shared_ptr<QueryOp> next(); | |
| | | /** @return next non error op if there is one, otherwise an err
or op. */ | |
| | | shared_ptr<QueryOp> nextNonError(); | |
| | | | |
| | | bool prepareToYield(); | |
| | | void recoverFromYield(); | |
| | | | |
| | | /** Run until first op completes. */ | |
| | | shared_ptr<QueryOp> runUntilFirstCompletes(); | |
| | | | |
| | | void mayYield(); | |
| | | QueryOp &_op; | |
| | | QueryPlanSet &_plans; | |
| static void initOp( QueryOp &op ); | | static void initOp( QueryOp &op ); | |
| static void nextOp( QueryOp &op ); | | static void nextOp( QueryOp &op ); | |
|
| static bool prepareToYield( QueryOp &op ); | | static bool prepareToYieldOp( QueryOp &op ); | |
| static void recoverFromYield( QueryOp &op ); | | static void recoverFromYieldOp( QueryOp &op ); | |
| | | private: | |
| | | vector<shared_ptr<QueryOp> > _ops; | |
| | | struct OpHolder { | |
| | | OpHolder( const shared_ptr<QueryOp> &op ) : _op( op ), _off
set() {} | |
| | | shared_ptr<QueryOp> _op; | |
| | | long long _offset; | |
| | | bool operator<( const OpHolder &other ) const { | |
| | | return _op->nscanned() + _offset > other._op->nscanned(
) + other._offset; | |
| | | } | |
| | | }; | |
| | | our_priority_queue<OpHolder> _queue; | |
| }; | | }; | |
|
| const char *ns; | | | |
| | | const char *_ns; | |
| BSONObj _originalQuery; | | BSONObj _originalQuery; | |
|
| auto_ptr< FieldRangeSet > fbs_; | | auto_ptr<FieldRangeSetPair> _frsp; | |
| PlanSet plans_; | | auto_ptr<FieldRangeSetPair> _originalFrsp; | |
| bool mayRecordPlan_; | | PlanSet _plans; | |
| bool usingPrerecordedPlan_; | | bool _mayRecordPlan; | |
| BSONObj hint_; | | bool _usingPrerecordedPlan; | |
| BSONObj order_; | | BSONObj _hint; | |
| long long oldNScanned_; | | BSONObj _order; | |
| bool honorRecordedPlan_; | | long long _oldNScanned; | |
| BSONObj min_; | | bool _honorRecordedPlan; | |
| BSONObj max_; | | BSONObj _min; | |
| | | BSONObj _max; | |
| string _special; | | string _special; | |
| bool _bestGuessOnly; | | bool _bestGuessOnly; | |
| bool _mayYield; | | bool _mayYield; | |
| ElapsedTracker _yieldSometimesTracker; | | ElapsedTracker _yieldSometimesTracker; | |
|
| | | shared_ptr<Runner> _runner; | |
| }; | | }; | |
| | | | |
|
| // Handles $or type queries by generating a QueryPlanSet for each $or c
lause | | /** Handles $or type queries by generating a QueryPlanSet for each $or
clause. */ | |
| // NOTE on our $or implementation: In our current qo implementation we
don't | | | |
| // keep statistics on our data, but we can conceptualize the problem of | | | |
| // selecting an index when statistics exist for all index ranges. The | | | |
| // d-hitting set problem on k sets and n elements can be reduced to the | | | |
| // problem of index selection on k $or clauses and n index ranges (wher
e | | | |
| // d is the max number of indexes, and the number of ranges n is unboun
ded). | | | |
| // In light of the fact that d-hitting set is np complete, and we don't
even | | | |
| // track statistics (so cost calculations are expensive) our first | | | |
| // implementation uses the following greedy approach: We take one $or c
lause | | | |
| // at a time and treat each as a separate query for index selection pur
poses. | | | |
| // But if an index range is scanned for a particular $or clause, we eli
minate | | | |
| // that range from all subsequent clauses. One could imagine an opposi
te | | | |
| // implementation where we select indexes based on the union of index r
anges | | | |
| // for all $or clauses, but this can have much poorer worst case behavi
or. | | | |
| // (An index range that suits one $or clause may not suit another, and
this | | | |
| // is worse than the typical case of index range choice staleness becau
se | | | |
| // with $or the clauses may likely be logically distinct.) The greedy | | | |
| // implementation won't do any worse than all the $or clauses individua
lly, | | | |
| // and it can often do better. In the first cut we are intentionally u
sing | | | |
| // QueryPattern tracking to record successful plans on $or clauses for
use by | | | |
| // subsequent $or clauses, even though there may be a significant aggre
gate | | | |
| // $nor component that would not be represented in QueryPattern. | | | |
| class MultiPlanScanner { | | class MultiPlanScanner { | |
| public: | | public: | |
| MultiPlanScanner( const char *ns, | | MultiPlanScanner( const char *ns, | |
|
| const BSONObj &query, | | const BSONObj &query, | |
| const BSONObj &order, | | const BSONObj &order, | |
| const BSONElement *hint = 0, | | const BSONElement *hint = 0, | |
| bool honorRecordedPlan = true, | | bool honorRecordedPlan = true, | |
| const BSONObj &min = BSONObj(), | | const BSONObj &min = BSONObj(), | |
| const BSONObj &max = BSONObj(), | | const BSONObj &max = BSONObj(), | |
| bool bestGuessOnly = false, | | bool bestGuessOnly = false, | |
| bool mayYield = false); | | bool mayYield = false); | |
| shared_ptr< QueryOp > runOp( QueryOp &op ); | | | |
| template< class T > | | /** | |
| shared_ptr< T > runOp( T &op ) { | | * Clone op for each query plan of a single $or clause, and @return
the first cloned op | |
| return dynamic_pointer_cast< T >( runOp( static_cast< QueryOp&
>( op ) ) ); | | * to call setComplete() or setStop(). | |
| | | */ | |
| | | | |
| | | shared_ptr<QueryOp> runOpOnce( QueryOp &op ); | |
| | | template<class T> | |
| | | shared_ptr<T> runOpOnce( T &op ) { | |
| | | return dynamic_pointer_cast<T>( runOpOnce( static_cast<QueryOp&
>( op ) ) ); | |
| } | | } | |
|
| shared_ptr< QueryOp > runOpOnce( QueryOp &op ); | | | |
| template< class T > | | /** | |
| shared_ptr< T > runOpOnce( T &op ) { | | * For each $or clause, calls runOpOnce on the child QueryOp cloned
from the winning QueryOp | |
| return dynamic_pointer_cast< T >( runOpOnce( static_cast< Query
Op& >( op ) ) ); | | * of the previous $or clause (or from the supplied 'op' for the fi
rst $or clause). | |
| | | */ | |
| | | | |
| | | shared_ptr<QueryOp> runOp( QueryOp &op ); | |
| | | template<class T> | |
| | | shared_ptr<T> runOp( T &op ) { | |
| | | return dynamic_pointer_cast<T>( runOp( static_cast<QueryOp&>( o
p ) ) ); | |
| } | | } | |
|
| bool mayRunMore() const { return _or ? ( !_tableScanned && !_fros.o
rFinished() ) : _i == 0; } | | | |
| | | /** Initialize or iterate a runner generated from @param originalOp
. */ | |
| | | | |
| | | void initialOp( const shared_ptr<QueryOp> &originalOp ) { _baseOp =
originalOp; } | |
| | | shared_ptr<QueryOp> nextOp(); | |
| | | | |
| | | /** Yield the runner member. */ | |
| | | | |
| | | bool prepareToYield(); | |
| | | void recoverFromYield(); | |
| | | | |
| | | /** | |
| | | * @return a single simple cursor if the scanner would run a single
cursor | |
| | | * for this query, otherwise return an empty shared_ptr. | |
| | | */ | |
| | | shared_ptr<Cursor> singleCursor() const; | |
| | | | |
| | | /** @return true iff more $or clauses need to be scanned. */ | |
| | | bool mayRunMore() const { return _or ? ( !_tableScanned && !_org->o
rFinished() ) : _i == 0; } | |
| | | /** @return non-$or version of explain output. */ | |
| BSONObj oldExplain() const { assertNotOr(); return _currentQps->exp
lain(); } | | BSONObj oldExplain() const { assertNotOr(); return _currentQps->exp
lain(); } | |
|
| // just report this when only one query op | | /** @return true iff this is not a $or query and a plan is selected
based on previous success of this plan. */ | |
| bool usingPrerecordedPlan() const { | | bool usingPrerecordedPlan() const { return !_or && _currentQps->usi
ngPrerecordedPlan(); } | |
| return !_or && _currentQps->usingPrerecordedPlan(); | | /** Don't attempt to scan multiple plans, just use the best guess.
*/ | |
| } | | | |
| void setBestGuessOnly() { _bestGuessOnly = true; } | | void setBestGuessOnly() { _bestGuessOnly = true; } | |
|
| | | /** Yielding is allowed while running each QueryPlan. */ | |
| void mayYield( bool val ) { _mayYield = val; } | | void mayYield( bool val ) { _mayYield = val; } | |
|
| | | bool modifiedKeys() const { return _currentQps->modifiedKeys(); } | |
| | | bool hasMultiKey() const { return _currentQps->hasMultiKey(); } | |
| | | | |
| private: | | private: | |
| void assertNotOr() const { | | void assertNotOr() const { | |
| massert( 13266, "not implemented for $or query", !_or ); | | massert( 13266, "not implemented for $or query", !_or ); | |
| } | | } | |
|
| | | void assertMayRunMore() const { | |
| | | massert( 13271, "can't run more ops", mayRunMore() ); | |
| | | } | |
| | | shared_ptr<QueryOp> nextOpBeginningClause(); | |
| | | shared_ptr<QueryOp> nextOpHandleEndOfClause(); | |
| bool uselessOr( const BSONElement &hint ) const; | | bool uselessOr( const BSONElement &hint ) const; | |
| const char * _ns; | | const char * _ns; | |
| bool _or; | | bool _or; | |
| BSONObj _query; | | BSONObj _query; | |
|
| FieldRangeOrSet _fros; | | shared_ptr<OrRangeGenerator> _org; // May be null in certain non $o
r query cases. | |
| auto_ptr< QueryPlanSet > _currentQps; | | auto_ptr<QueryPlanSet> _currentQps; | |
| int _i; | | int _i; | |
| bool _honorRecordedPlan; | | bool _honorRecordedPlan; | |
| bool _bestGuessOnly; | | bool _bestGuessOnly; | |
| BSONObj _hint; | | BSONObj _hint; | |
| bool _mayYield; | | bool _mayYield; | |
| bool _tableScanned; | | bool _tableScanned; | |
|
| | | shared_ptr<QueryOp> _baseOp; | |
| }; | | }; | |
| | | | |
|
| | | /** Provides a cursor interface for certain limited uses of a MultiPlan
Scanner. */ | |
| class MultiCursor : public Cursor { | | class MultiCursor : public Cursor { | |
| public: | | public: | |
| class CursorOp : public QueryOp { | | class CursorOp : public QueryOp { | |
| public: | | public: | |
| CursorOp() {} | | CursorOp() {} | |
| CursorOp( const QueryOp &other ) : QueryOp( other ) {} | | CursorOp( const QueryOp &other ) : QueryOp( other ) {} | |
|
| virtual shared_ptr< Cursor > newCursor() const = 0; | | virtual shared_ptr<Cursor> newCursor() const = 0; | |
| }; | | }; | |
|
| // takes ownership of 'op' | | /** takes ownership of 'op' */ | |
| MultiCursor( const char *ns, const BSONObj &pattern, const BSONObj
&order, shared_ptr< CursorOp > op = shared_ptr< CursorOp >(), bool mayYield
= false ) | | MultiCursor( const char *ns, const BSONObj &pattern, const BSONObj
&order, shared_ptr<CursorOp> op = shared_ptr<CursorOp>(), bool mayYield = f
alse ); | |
| : _mps( new MultiPlanScanner( ns, pattern, order, 0, true, BSONObj(
), BSONObj(), !op.get(), mayYield ) ), _nscanned() { | | /** | |
| if ( op.get() ) { | | * Used | |
| _op = op; | | * 1. To handoff a query to a getMore() | |
| } else { | | * 2. To handoff a QueryOptimizerCursor | |
| _op.reset( new NoOp() ); | | * @param nscanned is an optional initial value, if not supplied ns
canned() | |
| } | | * will always return -1 | |
| if ( _mps->mayRunMore() ) { | | */ | |
| nextClause(); | | MultiCursor( auto_ptr<MultiPlanScanner> mps, const shared_ptr<Curso
r> &c, const shared_ptr<CoveredIndexMatcher> &matcher, const QueryOp &op, l
ong long nscanned = -1 ); | |
| if ( !ok() ) { | | | |
| advance(); | | | |
| } | | | |
| } else { | | | |
| _c.reset( new BasicCursor( DiskLoc() ) ); | | | |
| } | | | |
| } | | | |
| // used to handoff a query to a getMore() | | | |
| MultiCursor( auto_ptr< MultiPlanScanner > mps, const shared_ptr< Cu
rsor > &c, const shared_ptr< CoveredIndexMatcher > &matcher, const QueryOp
&op ) | | | |
| : _op( new NoOp( op ) ), _c( c ), _mps( mps ), _matcher( matcher ),
_nscanned( -1 ) { | | | |
| _mps->setBestGuessOnly(); | | | |
| _mps->mayYield( false ); // with a NoOp, there's no need to yie
ld in QueryPlanSet | | | |
| if ( !ok() ) { | | | |
| // would have been advanced by UserQueryOp if possible | | | |
| advance(); | | | |
| } | | | |
| } | | | |
| virtual bool ok() { return _c->ok(); } | | virtual bool ok() { return _c->ok(); } | |
| virtual Record* _current() { return _c->_current(); } | | virtual Record* _current() { return _c->_current(); } | |
| virtual BSONObj current() { return _c->current(); } | | virtual BSONObj current() { return _c->current(); } | |
| virtual DiskLoc currLoc() { return _c->currLoc(); } | | virtual DiskLoc currLoc() { return _c->currLoc(); } | |
| virtual bool advance() { | | virtual bool advance() { | |
| _c->advance(); | | _c->advance(); | |
| while( !ok() && _mps->mayRunMore() ) { | | while( !ok() && _mps->mayRunMore() ) { | |
| nextClause(); | | nextClause(); | |
| } | | } | |
| return ok(); | | return ok(); | |
| } | | } | |
| virtual BSONObj currKey() const { return _c->currKey(); } | | virtual BSONObj currKey() const { return _c->currKey(); } | |
| virtual DiskLoc refLoc() { return _c->refLoc(); } | | virtual DiskLoc refLoc() { return _c->refLoc(); } | |
|
| virtual void noteLocation() { | | virtual void noteLocation() { _c->noteLocation(); } | |
| _c->noteLocation(); | | virtual void checkLocation() { _c->checkLocation(); } | |
| } | | | |
| virtual void checkLocation() { | | | |
| _c->checkLocation(); | | | |
| } | | | |
| virtual bool supportGetMore() { return true; } | | virtual bool supportGetMore() { return true; } | |
| virtual bool supportYields() { return _c->supportYields(); } | | virtual bool supportYields() { return _c->supportYields(); } | |
|
| // with update we could potentially get the same document on multip
le | | virtual BSONObj indexKeyPattern() { return _c->indexKeyPattern(); } | |
| // indexes, but update appears to already handle this with seenObje
cts | | | |
| // so we don't have to do anything special here. | | /** | |
| virtual bool getsetdup(DiskLoc loc) { | | * with update we could potentially get the same document on multip
le | |
| return _c->getsetdup( loc ); | | * indexes, but update appears to already handle this with seenObje
cts | |
| } | | * so we don't have to do anything special here. | |
| virtual CoveredIndexMatcher *matcher() const { return _matcher.get(
); } | | */ | |
| // return -1 if we're a getmore handoff | | virtual bool getsetdup(DiskLoc loc) { return _c->getsetdup( loc );
} | |
| | | | |
| | | virtual bool modifiedKeys() const { return _mps->modifiedKeys(); } | |
| | | | |
| | | virtual bool isMultiKey() const { return _mps->hasMultiKey(); } | |
| | | | |
| | | virtual shared_ptr< CoveredIndexMatcher > matcherPtr() const { retu
rn _matcher; } | |
| | | virtual CoveredIndexMatcher* matcher() const { return _matcher.get(
); } | |
| | | | |
| | | /** return -1 if we're a getmore handoff */ | |
| virtual long long nscanned() { return _nscanned >= 0 ? _nscanned +
_c->nscanned() : _nscanned; } | | virtual long long nscanned() { return _nscanned >= 0 ? _nscanned +
_c->nscanned() : _nscanned; } | |
|
| // just for testing | | /** just for testing */ | |
| shared_ptr< Cursor > sub_c() const { return _c; } | | shared_ptr<Cursor> sub_c() const { return _c; } | |
| private: | | private: | |
| class NoOp : public CursorOp { | | class NoOp : public CursorOp { | |
| public: | | public: | |
| NoOp() {} | | NoOp() {} | |
| NoOp( const QueryOp &other ) : CursorOp( other ) {} | | NoOp( const QueryOp &other ) : CursorOp( other ) {} | |
| virtual void _init() { setComplete(); } | | virtual void _init() { setComplete(); } | |
| virtual void next() {} | | virtual void next() {} | |
| virtual bool mayRecordPlan() const { return false; } | | virtual bool mayRecordPlan() const { return false; } | |
| virtual QueryOp *_createChild() const { return new NoOp(); } | | virtual QueryOp *_createChild() const { return new NoOp(); } | |
|
| virtual shared_ptr< Cursor > newCursor() const { return qp().ne
wCursor(); } | | virtual shared_ptr<Cursor> newCursor() const { return qp().newC
ursor(); } | |
| virtual long long nscanned() { assert( false ); return 0; } | | virtual long long nscanned() { assert( false ); return 0; } | |
| }; | | }; | |
|
| void nextClause() { | | void nextClause(); | |
| if ( _nscanned >= 0 && _c.get() ) { | | shared_ptr<CursorOp> _op; | |
| _nscanned += _c->nscanned(); | | shared_ptr<Cursor> _c; | |
| } | | auto_ptr<MultiPlanScanner> _mps; | |
| shared_ptr< CursorOp > best = _mps->runOpOnce( *_op ); | | shared_ptr<CoveredIndexMatcher> _matcher; | |
| if ( ! best->complete() ) | | | |
| throw MsgAssertionException( best->exception() ); | | | |
| _c = best->newCursor(); | | | |
| _matcher = best->matcher(); | | | |
| _op = best; | | | |
| } | | | |
| shared_ptr< CursorOp > _op; | | | |
| shared_ptr< Cursor > _c; | | | |
| auto_ptr< MultiPlanScanner > _mps; | | | |
| shared_ptr< CoveredIndexMatcher > _matcher; | | | |
| long long _nscanned; | | long long _nscanned; | |
| }; | | }; | |
| | | | |
|
| // NOTE min, max, and keyPattern will be updated to be consistent with
the selected index. | | /** NOTE min, max, and keyPattern will be updated to be consistent with
the selected index. */ | |
| IndexDetails *indexDetailsForRange( const char *ns, string &errmsg, BSO
NObj &min, BSONObj &max, BSONObj &keyPattern ); | | IndexDetails *indexDetailsForRange( const char *ns, string &errmsg, BSO
NObj &min, BSONObj &max, BSONObj &keyPattern ); | |
| | | | |
|
| inline bool isSimpleIdQuery( const BSONObj& query ){ | | bool isSimpleIdQuery( const BSONObj& query ); | |
| BSONObjIterator i(query); | | | |
| if( !i.more() ) return false; | | | |
| BSONElement e = i.next(); | | | |
| if( i.more() ) return false; | | | |
| if( strcmp("_id", e.fieldName()) != 0 ) return false; | | | |
| return e.isSimpleType(); // e.g. not something like { _id : { $gt :
... | | | |
| } | | | |
| | | | |
|
| // matcher() will always work on the returned cursor | | /** | |
| inline shared_ptr< Cursor > bestGuessCursor( const char *ns, const BSON
Obj &query, const BSONObj &sort ) { | | * @return a single cursor that may work well for the given query. | |
| if( !query.getField( "$or" ).eoo() ) { | | * It is possible no cursor is returned if the sort is not supported by
an index. Clients are responsible | |
| return shared_ptr< Cursor >( new MultiCursor( ns, query, sort )
); | | * for checking this if they are not sure an index for a sort exists, a
nd defaulting to a non-sort if | |
| } else { | | * no suitable indices exist. | |
| auto_ptr< FieldRangeSet > frs( new FieldRangeSet( ns, query ) )
; | | */ | |
| shared_ptr< Cursor > ret = QueryPlanSet( ns, frs, query, sort )
.getBestGuess()->newCursor(); | | shared_ptr<Cursor> bestGuessCursor( const char *ns, const BSONObj &quer
y, const BSONObj &sort ); | |
| if ( !query.isEmpty() ) { | | | |
| shared_ptr< CoveredIndexMatcher > matcher( new CoveredIndex
Matcher( query, ret->indexKeyPattern() ) ); | | /** | |
| ret->setMatcher( matcher ); | | * Add-on functionality for queryutil classes requiring access to index
ing | |
| } | | * functionality not currently linked to mongos. | |
| return ret; | | * TODO Clean this up a bit, possibly with separate sharded and non sha
rded | |
| } | | * implementations for the appropriate queryutil classes or by pulling
index | |
| } | | * related functionality into separate wrapper classes. | |
| | | */ | |
| | | struct QueryUtilIndexed { | |
| | | /** @return true if the index may be useful according to its KeySpe
c. */ | |
| | | static bool indexUseful( const FieldRangeSetPair &frsp, NamespaceDe
tails *d, int idxNo, const BSONObj &order ); | |
| | | /** Clear any indexes recorded as the best for either the single or
multi key pattern. */ | |
| | | static void clearIndexesForPatterns( const FieldRangeSetPair &frsp,
const BSONObj &order ); | |
| | | /** Return a recorded best index for the single or multi key patter
n. */ | |
| | | static pair< BSONObj, long long > bestIndexForPatterns( const Field
RangeSetPair &frsp, const BSONObj &order ); | |
| | | static bool uselessOr( const OrRangeGenerator& org, NamespaceDetail
s *d, int hintIdx ); | |
| | | }; | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 69 change blocks. |
| 250 lines changed or deleted | | 391 lines changed or added | |
|
| queryutil.h | | queryutil.h | |
|
| // queryutil.h | | // @file queryutil.h - Utility classes representing ranges of valid BSONEle
ment values for a query. | |
| | | | |
| /* Copyright 2009 10gen Inc. | | /* Copyright 2009 10gen Inc. | |
| * | | * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | | * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | | * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | | * You may obtain a copy of the License at | |
| * | | * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | | * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | | * | |
| * Unless required by applicable law or agreed to in writing, software | | * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | | * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | |
| * See the License for the specific language governing permissions and | | * See the License for the specific language governing permissions and | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "jsobj.h" | | #include "jsobj.h" | |
|
| | | #include "indexkey.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| | | /** | |
| | | * One side of an interval of valid BSONElements, specified by a value
and a | |
| | | * boolean indicating whether the interval includes the value. | |
| | | */ | |
| struct FieldBound { | | struct FieldBound { | |
| BSONElement _bound; | | BSONElement _bound; | |
| bool _inclusive; | | bool _inclusive; | |
| bool operator==( const FieldBound &other ) const { | | bool operator==( const FieldBound &other ) const { | |
| return _bound.woCompare( other._bound ) == 0 && | | return _bound.woCompare( other._bound ) == 0 && | |
|
| _inclusive == other._inclusive; | | _inclusive == other._inclusive; | |
| } | | } | |
| void flipInclusive() { _inclusive = !_inclusive; } | | void flipInclusive() { _inclusive = !_inclusive; } | |
| }; | | }; | |
| | | | |
|
| | | /** A closed interval composed of a lower and an upper FieldBound. */ | |
| struct FieldInterval { | | struct FieldInterval { | |
| FieldInterval() : _cachedEquality( -1 ) {} | | FieldInterval() : _cachedEquality( -1 ) {} | |
| FieldInterval( const BSONElement& e ) : _cachedEquality( -1 ) { | | FieldInterval( const BSONElement& e ) : _cachedEquality( -1 ) { | |
| _lower._bound = _upper._bound = e; | | _lower._bound = _upper._bound = e; | |
| _lower._inclusive = _upper._inclusive = true; | | _lower._inclusive = _upper._inclusive = true; | |
| } | | } | |
| FieldBound _lower; | | FieldBound _lower; | |
| FieldBound _upper; | | FieldBound _upper; | |
|
| | | /** @return true iff no single element can be contained in the inte
rval. */ | |
| bool strictValid() const { | | bool strictValid() const { | |
| int cmp = _lower._bound.woCompare( _upper._bound, false ); | | int cmp = _lower._bound.woCompare( _upper._bound, false ); | |
| return ( cmp < 0 || ( cmp == 0 && _lower._inclusive && _upper._
inclusive ) ); | | return ( cmp < 0 || ( cmp == 0 && _lower._inclusive && _upper._
inclusive ) ); | |
| } | | } | |
|
| bool equality() const { | | /** @return true iff the interval is an equality constraint. */ | |
| if ( _cachedEquality == -1 ) { | | bool equality() const; | |
| _cachedEquality = ( _lower._inclusive && _upper._inclusive
&& _lower._bound.woCompare( _upper._bound, false ) == 0 ); | | | |
| } | | | |
| return _cachedEquality; | | | |
| } | | | |
| mutable int _cachedEquality; | | mutable int _cachedEquality; | |
|
| | | | |
| | | string toString() const; | |
| }; | | }; | |
| | | | |
|
| // range of a field's value that may be determined from query -- used t
o | | /** | |
| // determine index limits | | * An ordered list of FieldIntervals expressing constraints on valid | |
| | | * BSONElement values for a field. | |
| | | */ | |
| class FieldRange { | | class FieldRange { | |
| public: | | public: | |
|
| FieldRange( const BSONElement &e = BSONObj().firstElement() , bool
isNot=false , bool optimize=true ); | | FieldRange( const BSONElement &e , bool singleKey , bool isNot=fals
e , bool optimize=true ); | |
| | | | |
| | | /** @return Range intersection with 'other'. */ | |
| const FieldRange &operator&=( const FieldRange &other ); | | const FieldRange &operator&=( const FieldRange &other ); | |
|
| | | /** @return Range union with 'other'. */ | |
| const FieldRange &operator|=( const FieldRange &other ); | | const FieldRange &operator|=( const FieldRange &other ); | |
|
| // does not remove fully contained ranges (eg [1,3] - [2,2] doesn't
remove anything) | | /** @return Range of elements elements included in 'this' but not '
other'. */ | |
| // in future we can change so that an or on $in:[3] combined with $
gt:2 doesn't scan 3 a second time | | | |
| const FieldRange &operator-=( const FieldRange &other ); | | const FieldRange &operator-=( const FieldRange &other ); | |
|
| // true iff other includes this | | /** @return true iff this range is a subset of 'other'. */ | |
| bool operator<=( const FieldRange &other ); | | bool operator<=( const FieldRange &other ) const; | |
| | | | |
| | | /** | |
| | | * If there are any valid values for this range, the extreme values
can | |
| | | * be extracted. | |
| | | */ | |
| | | | |
| BSONElement min() const { assert( !empty() ); return _intervals[ 0
]._lower._bound; } | | BSONElement min() const { assert( !empty() ); return _intervals[ 0
]._lower._bound; } | |
| BSONElement max() const { assert( !empty() ); return _intervals[ _i
ntervals.size() - 1 ]._upper._bound; } | | BSONElement max() const { assert( !empty() ); return _intervals[ _i
ntervals.size() - 1 ]._upper._bound; } | |
| bool minInclusive() const { assert( !empty() ); return _intervals[
0 ]._lower._inclusive; } | | bool minInclusive() const { assert( !empty() ); return _intervals[
0 ]._lower._inclusive; } | |
| bool maxInclusive() const { assert( !empty() ); return _intervals[
_intervals.size() - 1 ]._upper._inclusive; } | | bool maxInclusive() const { assert( !empty() ); return _intervals[
_intervals.size() - 1 ]._upper._inclusive; } | |
|
| bool equality() const { | | | |
| return | | /** @return true iff this range expresses a single equality interva
l. */ | |
| !empty() && | | bool equality() const; | |
| min().woCompare( max(), false ) == 0 && | | /** @return true if all the intervals for this range are equalities
*/ | |
| maxInclusive() && | | bool inQuery() const; | |
| minInclusive(); | | /** @return true iff this range does not include every BSONElement
*/ | |
| } | | bool nontrivial() const; | |
| bool inQuery() const { | | /** @return true iff this range matches no BSONElements. */ | |
| if ( equality() ) { | | | |
| return true; | | | |
| } | | | |
| for( vector< FieldInterval >::const_iterator i = _intervals.beg
in(); i != _intervals.end(); ++i ) { | | | |
| if ( !i->equality() ) { | | | |
| return false; | | | |
| } | | | |
| } | | | |
| return true; | | | |
| } | | | |
| bool nontrivial() const { | | | |
| return | | | |
| ! empty() && | | | |
| ( _intervals.size() != 1 || | | | |
| minKey.firstElement().woCompare( min(), false ) != 0 || | | | |
| maxKey.firstElement().woCompare( max(), false ) != 0 ); | | | |
| } | | | |
| bool empty() const { return _intervals.empty(); } | | bool empty() const { return _intervals.empty(); } | |
|
| | | | |
| | | /** Empty the range so it matches no BSONElements. */ | |
| void makeEmpty() { _intervals.clear(); } | | void makeEmpty() { _intervals.clear(); } | |
|
| const vector< FieldInterval > &intervals() const { return _i
ntervals; } | | const vector<FieldInterval> &intervals() const { return _intervals;
} | |
| string getSpecial() const { return _special; } | | string getSpecial() const { return _special; } | |
|
| void setExclusiveBounds() { | | /** Make component intervals noninclusive. */ | |
| for( vector< FieldInterval >::iterator i = _intervals.begin();
i != _intervals.end(); ++i ) { | | void setExclusiveBounds(); | |
| i->_lower._inclusive = false; | | /** | |
| i->_upper._inclusive = false; | | * Constructs a range where all FieldIntervals and FieldBounds are
in | |
| } | | * the opposite order of the current range. | |
| } | | * NOTE the resulting intervals might not be strictValid(). | |
| // constructs a range which is the reverse of the current one | | */ | |
| // note - the resulting intervals may not be strictValid() | | void reverse( FieldRange &ret ) const; | |
| void reverse( FieldRange &ret ) const { | | | |
| assert( _special.empty() ); | | string toString() const; | |
| ret._intervals.clear(); | | | |
| ret._objData = _objData; | | | |
| for( vector< FieldInterval >::const_reverse_iterator i = _inter
vals.rbegin(); i != _intervals.rend(); ++i ) { | | | |
| FieldInterval fi; | | | |
| fi._lower = i->_upper; | | | |
| fi._upper = i->_lower; | | | |
| ret._intervals.push_back( fi ); | | | |
| } | | | |
| } | | | |
| private: | | private: | |
| BSONObj addObj( const BSONObj &o ); | | BSONObj addObj( const BSONObj &o ); | |
|
| void finishOperation( const vector< FieldInterval > &newIntervals,
const FieldRange &other ); | | void finishOperation( const vector<FieldInterval> &newIntervals, co
nst FieldRange &other ); | |
| vector< FieldInterval > _intervals; | | vector<FieldInterval> _intervals; | |
| vector< BSONObj > _objData; | | // Owns memory for our BSONElements. | |
| | | vector<BSONObj> _objData; | |
| string _special; | | string _special; | |
|
| | | bool _singleKey; | |
| }; | | }; | |
| | | | |
|
| // implements query pattern matching, used to determine if a query is | | /** | |
| // similar to an earlier query and should use the same plan | | * A BoundList contains intervals specified by inclusive start | |
| class QueryPattern { | | * and end bounds. The intervals should be nonoverlapping and occur in | |
| public: | | * the specified direction of traversal. For example, given a simple i
ndex {i:1} | |
| friend class FieldRangeSet; | | * and direction +1, one valid BoundList is: (1, 2); (4, 6). The same
BoundList | |
| enum Type { | | * would be valid for index {i:-1} with direction -1. | |
| Equality, | | */ | |
| LowerBound, | | typedef vector<pair<BSONObj,BSONObj> > BoundList; | |
| UpperBound, | | | |
| UpperAndLowerBound | | | |
| }; | | | |
| // for testing only, speed unimportant | | | |
| bool operator==( const QueryPattern &other ) const { | | | |
| bool less = operator<( other ); | | | |
| bool more = other.operator<( *this ); | | | |
| assert( !( less && more ) ); | | | |
| return !( less || more ); | | | |
| } | | | |
| bool operator!=( const QueryPattern &other ) const { | | | |
| return !operator==( other ); | | | |
| } | | | |
| bool operator<( const QueryPattern &other ) const { | | | |
| map< string, Type >::const_iterator i = _fieldTypes.begin(); | | | |
| map< string, Type >::const_iterator j = other._fieldTypes.begin
(); | | | |
| while( i != _fieldTypes.end() ) { | | | |
| if ( j == other._fieldTypes.end() ) | | | |
| return false; | | | |
| if ( i->first < j->first ) | | | |
| return true; | | | |
| else if ( i->first > j->first ) | | | |
| return false; | | | |
| if ( i->second < j->second ) | | | |
| return true; | | | |
| else if ( i->second > j->second ) | | | |
| return false; | | | |
| ++i; | | | |
| ++j; | | | |
| } | | | |
| if ( j != other._fieldTypes.end() ) | | | |
| return true; | | | |
| return _sort.woCompare( other._sort ) < 0; | | | |
| } | | | |
| private: | | | |
| QueryPattern() {} | | | |
| void setSort( const BSONObj sort ) { | | | |
| _sort = normalizeSort( sort ); | | | |
| } | | | |
| BSONObj static normalizeSort( const BSONObj &spec ) { | | | |
| if ( spec.isEmpty() ) | | | |
| return spec; | | | |
| int direction = ( spec.firstElement().number() >= 0 ) ? 1 : -1; | | | |
| BSONObjIterator i( spec ); | | | |
| BSONObjBuilder b; | | | |
| while( i.moreWithEOO() ) { | | | |
| BSONElement e = i.next(); | | | |
| if ( e.eoo() ) | | | |
| break; | | | |
| b.append( e.fieldName(), direction * ( ( e.number() >= 0 )
? -1 : 1 ) ); | | | |
| } | | | |
| return b.obj(); | | | |
| } | | | |
| map< string, Type > _fieldTypes; | | | |
| BSONObj _sort; | | | |
| }; | | | |
| | | | |
|
| // a BoundList contains intervals specified by inclusive start | | class QueryPattern; | |
| // and end bounds. The intervals should be nonoverlapping and occur in | | | |
| // the specified direction of traversal. For example, given a simple i
ndex {i:1} | | | |
| // and direction +1, one valid BoundList is: (1, 2); (4, 6). The same
BoundList | | | |
| // would be valid for index {i:-1} with direction -1. | | | |
| typedef vector< pair< BSONObj, BSONObj > > BoundList; | | | |
| | | | |
|
| // ranges of fields' value that may be determined from query -- used to | | /** | |
| // determine index limits | | * A set of FieldRanges determined from constraints on the fields of a
query, | |
| | | * that may be used to determine index bounds. | |
| | | */ | |
| class FieldRangeSet { | | class FieldRangeSet { | |
| public: | | public: | |
|
| friend class FieldRangeOrSet; | | friend class OrRangeGenerator; | |
| friend class FieldRangeVector; | | friend class FieldRangeVector; | |
|
| FieldRangeSet( const char *ns, const BSONObj &query , bool optimize
=true ); | | FieldRangeSet( const char *ns, const BSONObj &query , bool singleKe
y , bool optimize=true ); | |
| | | | |
| | | /** @return true if there is a nontrivial range for the given field
. */ | |
| bool hasRange( const char *fieldName ) const { | | bool hasRange( const char *fieldName ) const { | |
|
| map< string, FieldRange >::const_iterator f = _ranges.find( fie
ldName ); | | map<string, FieldRange>::const_iterator f = _ranges.find( field
Name ); | |
| return f != _ranges.end(); | | return f != _ranges.end(); | |
| } | | } | |
|
| const FieldRange &range( const char *fieldName ) const { | | /** @return range for the given field. */ | |
| map< string, FieldRange >::const_iterator f = _ranges.find( fie
ldName ); | | const FieldRange &range( const char *fieldName ) const; | |
| if ( f == _ranges.end() ) | | /** @return range for the given field. */ | |
| return trivialRange(); | | FieldRange &range( const char *fieldName ); | |
| return f->second; | | /** @return the number of nontrivial ranges. */ | |
| } | | int nNontrivialRanges() const; | |
| FieldRange &range( const char *fieldName ) { | | /** | |
| map< string, FieldRange >::iterator f = _ranges.find( fieldName
); | | * @return true if a match could be possible on every field. Genera
lly this | |
| if ( f == _ranges.end() ) | | * is not useful information for a single key FieldRangeSet and | |
| return trivialRange(); | | * matchPossibleForIndex() should be used instead. | |
| return f->second; | | */ | |
| } | | bool matchPossible() const; | |
| int nNontrivialRanges() const { | | /** | |
| int count = 0; | | * @return true if a match could be possible given the value of _si
ngleKey | |
| for( map< string, FieldRange >::const_iterator i = _ranges.begi
n(); i != _ranges.end(); ++i ) { | | * and index key 'keyPattern'. | |
| if ( i->second.nontrivial() ) | | * @param keyPattern May be {} or {$natural:1} for a non index scan
. | |
| ++count; | | */ | |
| } | | bool matchPossibleForIndex( const BSONObj &keyPattern ) const; | |
| return count; | | | |
| } | | | |
| const char *ns() const { return _ns; } | | const char *ns() const { return _ns; } | |
|
| // if fields is specified, order fields of returned object to match
those of 'fields' | | | |
| | | /** | |
| | | * @return a simplified query from the extreme values of the nontri
vial | |
| | | * fields. | |
| | | * @param fields If specified, the fields of the returned object ar
e | |
| | | * ordered to match those of 'fields'. | |
| | | */ | |
| BSONObj simplifiedQuery( const BSONObj &fields = BSONObj() ) const; | | BSONObj simplifiedQuery( const BSONObj &fields = BSONObj() ) const; | |
|
| bool matchPossible() const { | | | |
| for( map< string, FieldRange >::const_iterator i = _ranges.begi
n(); i != _ranges.end(); ++i ) | | | |
| if ( i->second.empty() ) | | | |
| return false; | | | |
| return true; | | | |
| } | | | |
| QueryPattern pattern( const BSONObj &sort = BSONObj() ) const; | | QueryPattern pattern( const BSONObj &sort = BSONObj() ) const; | |
| string getSpecial() const; | | string getSpecial() const; | |
|
| const FieldRangeSet &operator-=( const FieldRangeSet &other ) { | | | |
| int nUnincluded = 0; | | /** | |
| string unincludedKey; | | * @return a FieldRangeSet approximation of the documents in 'this'
but | |
| map< string, FieldRange >::iterator i = _ranges.begin(); | | * not in 'other'. The approximation will be a superset of the doc
uments | |
| map< string, FieldRange >::const_iterator j = other._ranges.beg
in(); | | * in 'this' but not 'other'. | |
| while( nUnincluded < 2 && i != _ranges.end() && j != other._ran
ges.end() ) { | | */ | |
| int cmp = i->first.compare( j->first ); | | const FieldRangeSet &operator-=( const FieldRangeSet &other ); | |
| if ( cmp == 0 ) { | | /** @return intersection of 'this' with 'other'. */ | |
| if ( i->second <= j->second ) { | | const FieldRangeSet &operator&=( const FieldRangeSet &other ); | |
| // nothing | | | |
| } else { | | /** | |
| ++nUnincluded; | | * @return an ordered list of bounds generated using an index key p
attern | |
| unincludedKey = i->first; | | * and traversal direction. | |
| } | | * | |
| ++i; | | * NOTE This function is deprecated in the query optimizer and only | |
| ++j; | | * currently used by the sharding code. | |
| } else if ( cmp < 0 ) { | | */ | |
| ++i; | | | |
| } else { | | | |
| // other has a bound we don't, nothing can be done | | | |
| return *this; | | | |
| } | | | |
| } | | | |
| if ( j != other._ranges.end() ) { | | | |
| // other has a bound we don't, nothing can be done | | | |
| return *this; | | | |
| } | | | |
| if ( nUnincluded > 1 ) { | | | |
| return *this; | | | |
| } | | | |
| if ( nUnincluded == 0 ) { | | | |
| makeEmpty(); | | | |
| return *this; | | | |
| } | | | |
| // nUnincluded == 1 | | | |
| _ranges[ unincludedKey ] -= other._ranges[ unincludedKey ]; | | | |
| appendQueries( other ); | | | |
| return *this; | | | |
| } | | | |
| const FieldRangeSet &operator&=( const FieldRangeSet &other ) { | | | |
| map< string, FieldRange >::iterator i = _ranges.begin(); | | | |
| map< string, FieldRange >::const_iterator j = other._ranges.beg
in(); | | | |
| while( i != _ranges.end() && j != other._ranges.end() ) { | | | |
| int cmp = i->first.compare( j->first ); | | | |
| if ( cmp == 0 ) { | | | |
| i->second &= j->second; | | | |
| ++i; | | | |
| ++j; | | | |
| } else if ( cmp < 0 ) { | | | |
| ++i; | | | |
| } else { | | | |
| _ranges[ j->first ] = j->second; | | | |
| ++j; | | | |
| } | | | |
| } | | | |
| while( j != other._ranges.end() ) { | | | |
| _ranges[ j->first ] = j->second; | | | |
| ++j; | | | |
| } | | | |
| appendQueries( other ); | | | |
| return *this; | | | |
| } | | | |
| // TODO get rid of this | | | |
| BoundList indexBounds( const BSONObj &keyPattern, int direction ) c
onst; | | BoundList indexBounds( const BSONObj &keyPattern, int direction ) c
onst; | |
|
| | | | |
| | | /** | |
| | | * @return - A new FieldRangeSet based on this FieldRangeSet, but w
ith only | |
| | | * a subset of the fields. | |
| | | * @param fields - Only fields which are represented as field names
in this object | |
| | | * will be included in the returned FieldRangeSet. | |
| | | */ | |
| | | FieldRangeSet *subset( const BSONObj &fields ) const; | |
| | | | |
| | | bool singleKey() const { return _singleKey; } | |
| | | | |
| | | BSONObj originalQuery() const { return _queries[ 0 ]; } | |
| private: | | private: | |
|
| void appendQueries( const FieldRangeSet &other ) { | | void appendQueries( const FieldRangeSet &other ); | |
| for( vector< BSONObj >::const_iterator i = other._queries.begin
(); i != other._queries.end(); ++i ) { | | void makeEmpty(); | |
| _queries.push_back( *i ); | | | |
| } | | | |
| } | | | |
| void makeEmpty() { | | | |
| for( map< string, FieldRange >::iterator i = _ranges.begin(); i
!= _ranges.end(); ++i ) { | | | |
| i->second.makeEmpty(); | | | |
| } | | | |
| } | | | |
| void processQueryField( const BSONElement &e, bool optimize ); | | void processQueryField( const BSONElement &e, bool optimize ); | |
| void processOpElement( const char *fieldName, const BSONElement &f,
bool isNot, bool optimize ); | | void processOpElement( const char *fieldName, const BSONElement &f,
bool isNot, bool optimize ); | |
|
| static FieldRange *trivialRange_; | | static FieldRange *__singleKeyTrivialRange; | |
| static FieldRange &trivialRange(); | | static FieldRange *__multiKeyTrivialRange; | |
| mutable map< string, FieldRange > _ranges; | | const FieldRange &trivialRange() const; | |
| | | map<string,FieldRange> _ranges; | |
| const char *_ns; | | const char *_ns; | |
|
| // make sure memory for FieldRange BSONElements is owned | | // Owns memory for FieldRange BSONElements. | |
| vector< BSONObj > _queries; | | vector<BSONObj> _queries; | |
| | | bool _singleKey; | |
| }; | | }; | |
| | | | |
|
| class FieldRangeVector { | | class NamespaceDetails; | |
| | | | |
| | | /** | |
| | | * A pair of FieldRangeSets, one representing constraints for single ke
y | |
| | | * indexes and the other representing constraints for multi key indexes
and | |
| | | * unindexed scans. In several member functions the caller is asked to | |
| | | * supply an index so that the implementation may utilize the proper | |
| | | * FieldRangeSet and return results that are appropriate with respect t
o that | |
| | | * supplied index. | |
| | | */ | |
| | | class FieldRangeSetPair { | |
| public: | | public: | |
|
| FieldRangeVector( const FieldRangeSet &frs, const BSONObj &keyPatte
rn, int direction ) | | FieldRangeSetPair( const char *ns, const BSONObj &query, bool optim
ize=true ) | |
| :_keyPattern( keyPattern ), _direction( direction >= 0 ? 1 : -1 ) | | :_singleKey( ns, query, true, optimize ), _multiKey( ns, query, fal
se, optimize ) {} | |
| { | | | |
| _queries = frs._queries; | | /** | |
| BSONObjIterator i( _keyPattern ); | | * @return the appropriate single or multi key FieldRangeSet for th
e specified index. | |
| while( i.more() ) { | | * @param idxNo -1 for non index scan. | |
| BSONElement e = i.next(); | | */ | |
| int number = (int) e.number(); // returns 0.0 if not numeri
c | | const FieldRangeSet &frsForIndex( const NamespaceDetails* nsd, int
idxNo ) const; | |
| bool forward = ( ( number >= 0 ? 1 : -1 ) * ( direction >=
0 ? 1 : -1 ) > 0 ); | | | |
| if ( forward ) { | | /** @return a field range in the single key FieldRangeSet. */ | |
| _ranges.push_back( frs.range( e.fieldName() ) ); | | const FieldRange &singleKeyRange( const char *fieldName ) const { | |
| } else { | | return _singleKey.range( fieldName ); | |
| _ranges.push_back( FieldRange() ); | | | |
| frs.range( e.fieldName() ).reverse( _ranges.back() ); | | | |
| } | | | |
| assert( !_ranges.back().empty() ); | | | |
| } | | | |
| uassert( 13385, "combinatorial limit of $in partitioning of res
ult set exceeded", size() < 1000000 ); | | | |
| } | | | |
| long long size() { | | | |
| long long ret = 1; | | | |
| for( vector< FieldRange >::const_iterator i = _ranges.begin();
i != _ranges.end(); ++i ) { | | | |
| ret *= i->intervals().size(); | | | |
| } | | | |
| return ret; | | | |
| } | | | |
| BSONObj startKey() const { | | | |
| BSONObjBuilder b; | | | |
| for( vector< FieldRange >::const_iterator i = _ranges.begin();
i != _ranges.end(); ++i ) { | | | |
| const FieldInterval &fi = i->intervals().front(); | | | |
| b.appendAs( fi._lower._bound, "" ); | | | |
| } | | | |
| return b.obj(); | | | |
| } | | | |
| BSONObj endKey() const { | | | |
| BSONObjBuilder b; | | | |
| for( vector< FieldRange >::const_iterator i = _ranges.begin();
i != _ranges.end(); ++i ) { | | | |
| const FieldInterval &fi = i->intervals().back(); | | | |
| b.appendAs( fi._upper._bound, "" ); | | | |
| } | | | |
| return b.obj(); | | | |
| } | | } | |
|
| BSONObj obj() const { | | /** @return true if the range limits are equivalent to an empty que
ry. */ | |
| BSONObjBuilder b; | | bool noNontrivialRanges() const; | |
| BSONObjIterator k( _keyPattern ); | | /** @return false if a match is impossible regardless of index. */ | |
| for( int i = 0; i < (int)_ranges.size(); ++i ) { | | bool matchPossible() const { return _multiKey.matchPossible(); } | |
| BSONArrayBuilder a( b.subarrayStart( k.next().fieldName() )
); | | /** | |
| for( vector< FieldInterval >::const_iterator j = _ranges[ i
].intervals().begin(); | | * @return false if a match is impossible on the specified index. | |
| j != _ranges[ i ].intervals().end(); ++j ) { | | * @param idxNo -1 for non index scan. | |
| a << BSONArray( BSON_ARRAY( j->_lower._bound << j->_upp
er._bound ).clientReadable() ); | | */ | |
| } | | bool matchPossibleForIndex( NamespaceDetails *d, int idxNo, const B
SONObj &keyPattern ) const; | |
| a.done(); | | | |
| } | | const char *ns() const { return _singleKey.ns(); } | |
| return b.obj(); | | | |
| | | string getSpecial() const { return _singleKey.getSpecial(); } | |
| | | | |
| | | /** Intersect with another FieldRangeSetPair. */ | |
| | | FieldRangeSetPair &operator&=( const FieldRangeSetPair &other ); | |
| | | /** | |
| | | * Subtract a FieldRangeSet, generally one expressing a range that
has | |
| | | * already been scanned. | |
| | | */ | |
| | | FieldRangeSetPair &operator-=( const FieldRangeSet &scanned ); | |
| | | | |
| | | BoundList singleKeyIndexBounds( const BSONObj &keyPattern, int dire
ction ) const { | |
| | | return _singleKey.indexBounds( keyPattern, direction ); | |
| } | | } | |
|
| | | | |
| | | BSONObj originalQuery() const { return _singleKey.originalQuery();
} | |
| | | | |
| | | private: | |
| | | FieldRangeSetPair( const FieldRangeSet &singleKey, const FieldRange
Set &multiKey ) | |
| | | :_singleKey( singleKey ), _multiKey( multiKey ) {} | |
| | | void assertValidIndex( const NamespaceDetails *d, int idxNo ) const
; | |
| | | void assertValidIndexOrNoIndex( const NamespaceDetails *d, int idxN
o ) const; | |
| | | /** matchPossibleForIndex() must be true. */ | |
| | | BSONObj simplifiedQueryForIndex( NamespaceDetails *d, int idxNo, co
nst BSONObj &keyPattern ) const; | |
| | | FieldRangeSet _singleKey; | |
| | | FieldRangeSet _multiKey; | |
| | | friend class OrRangeGenerator; | |
| | | friend struct QueryUtilIndexed; | |
| | | }; | |
| | | | |
| | | class IndexSpec; | |
| | | | |
| | | /** | |
| | | * An ordered list of fields and their FieldRanges, correspoinding to v
alid | |
| | | * index keys for a given index spec. | |
| | | */ | |
| | | class FieldRangeVector { | |
| | | public: | |
| | | /** | |
| | | * @param frs The valid ranges for all fields, as defined by the qu
ery spec | |
| | | * @param indexSpec The index spec (key pattern and info) | |
| | | * @param direction The direction of index traversal | |
| | | */ | |
| | | FieldRangeVector( const FieldRangeSet &frs, const IndexSpec &indexS
pec, int direction ); | |
| | | | |
| | | /** @return the number of index ranges represented by 'this' */ | |
| | | long long size(); | |
| | | /** @return starting point for an index traversal. */ | |
| | | BSONObj startKey() const; | |
| | | /** @return end point for an index traversal. */ | |
| | | BSONObj endKey() const; | |
| | | /** @return a client readable representation of 'this' */ | |
| | | BSONObj obj() const; | |
| | | | |
| | | /** | |
| | | * @return true iff the provided document matches valid ranges on a
ll | |
| | | * of this FieldRangeVector's fields, which is the case iff this do
cument | |
| | | * would be returned while scanning the index corresponding to this | |
| | | * FieldRangeVector. This function is used for $or clause deduping
. | |
| | | */ | |
| bool matches( const BSONObj &obj ) const; | | bool matches( const BSONObj &obj ) const; | |
|
| class Iterator { | | | |
| public: | | /** | |
| Iterator( const FieldRangeVector &v ) : _v( v ), _i( _v._ranges
.size(), -1 ), _cmp( _v._ranges.size(), 0 ), _inc( _v._ranges.size(), false
), _after() { | | * @return first key of 'obj' that would be encountered by a forwar
d | |
| } | | * index scan using this FieldRangeVector, BSONObj() if no such key
. | |
| static BSONObj minObject() { | | */ | |
| BSONObjBuilder b; | | BSONObj firstMatch( const BSONObj &obj ) const; | |
| b.appendMinKey( "" ); | | | |
| return b.obj(); | | | |
| } | | | |
| static BSONObj maxObject() { | | | |
| BSONObjBuilder b; | | | |
| b.appendMaxKey( "" ); | | | |
| return b.obj(); | | | |
| } | | | |
| bool advance() { | | | |
| int i = _i.size() - 1; | | | |
| while( i >= 0 && _i[ i ] >= ( (int)_v._ranges[ i ].interval
s().size() - 1 ) ) { | | | |
| --i; | | | |
| } | | | |
| if( i >= 0 ) { | | | |
| _i[ i ]++; | | | |
| for( unsigned j = i + 1; j < _i.size(); ++j ) { | | | |
| _i[ j ] = 0; | | | |
| } | | | |
| } else { | | | |
| _i[ 0 ] = _v._ranges[ 0 ].intervals().size(); | | | |
| } | | | |
| return ok(); | | | |
| } | | | |
| // return value | | | |
| // -2 end of iteration | | | |
| // -1 no skipping | | | |
| // >= 0 skip parameter | | | |
| int advance( const BSONObj &curr ); | | | |
| const vector< const BSONElement * > &cmp() const { return _cmp;
} | | | |
| const vector< bool > &inc() const { return _inc; } | | | |
| bool after() const { return _after; } | | | |
| void prepDive(); | | | |
| void setZero( int i ) { | | | |
| for( int j = i; j < (int)_i.size(); ++j ) { | | | |
| _i[ j ] = 0; | | | |
| } | | | |
| } | | | |
| void setMinus( int i ) { | | | |
| for( int j = i; j < (int)_i.size(); ++j ) { | | | |
| _i[ j ] = -1; | | | |
| } | | | |
| } | | | |
| bool ok() { | | | |
| return _i[ 0 ] < (int)_v._ranges[ 0 ].intervals().size(); | | | |
| } | | | |
| BSONObj startKey() { | | | |
| BSONObjBuilder b; | | | |
| for( int unsigned i = 0; i < _i.size(); ++i ) { | | | |
| const FieldInterval &fi = _v._ranges[ i ].intervals()[
_i[ i ] ]; | | | |
| b.appendAs( fi._lower._bound, "" ); | | | |
| } | | | |
| return b.obj(); | | | |
| } | | | |
| // temp | | | |
| BSONObj endKey() { | | | |
| BSONObjBuilder b; | | | |
| for( int unsigned i = 0; i < _i.size(); ++i ) { | | | |
| const FieldInterval &fi = _v._ranges[ i ].intervals()[
_i[ i ] ]; | | | |
| b.appendAs( fi._upper._bound, "" ); | | | |
| } | | | |
| return b.obj(); | | | |
| } | | | |
| // check | | | |
| private: | | | |
| const FieldRangeVector &_v; | | | |
| vector< int > _i; | | | |
| vector< const BSONElement* > _cmp; | | | |
| vector< bool > _inc; | | | |
| bool _after; | | | |
| }; | | | |
| private: | | private: | |
| int matchingLowElement( const BSONElement &e, int i, bool direction
, bool &lowEquality ) const; | | int matchingLowElement( const BSONElement &e, int i, bool direction
, bool &lowEquality ) const; | |
| bool matchesElement( const BSONElement &e, int i, bool direction )
const; | | bool matchesElement( const BSONElement &e, int i, bool direction )
const; | |
|
| vector< FieldRange > _ranges; | | bool matchesKey( const BSONObj &key ) const; | |
| BSONObj _keyPattern; | | vector<FieldRange> _ranges; | |
| | | const IndexSpec &_indexSpec; | |
| int _direction; | | int _direction; | |
|
| vector< BSONObj > _queries; // make sure mem owned | | vector<BSONObj> _queries; // make sure mem owned | |
| | | friend class FieldRangeVectorIterator; | |
| }; | | }; | |
| | | | |
|
| // generages FieldRangeSet objects, accounting for or clauses | | /** | |
| class FieldRangeOrSet { | | * Helper class for iterating through an ordered representation of keys | |
| | | * to find those keys that match a specified FieldRangeVector. | |
| | | */ | |
| | | class FieldRangeVectorIterator { | |
| public: | | public: | |
|
| FieldRangeOrSet( const char *ns, const BSONObj &query , bool optimi
ze=true ); | | FieldRangeVectorIterator( const FieldRangeVector &v ) : _v( v ), _i
( _v._ranges.size(), -1 ), _cmp( _v._ranges.size(), 0 ), _inc( _v._ranges.s
ize(), false ), _after() { | |
| // if there's a useless or clause, we won't use or ranges to help w
ith scanning | | | |
| bool orFinished() const { return _orFound && _orSets.empty(); } | | | |
| // removes first or clause, and removes the field ranges it covers
from all subsequent or clauses | | | |
| // this could invalidate the result of the last topFrs() | | | |
| void popOrClause() { | | | |
| massert( 13274, "no or clause to pop", !orFinished() ); | | | |
| const FieldRangeSet &toPop = _orSets.front(); | | | |
| list< FieldRangeSet >::iterator i = _orSets.begin(); | | | |
| ++i; | | | |
| while( i != _orSets.end() ) { | | | |
| *i -= toPop; | | | |
| if( !i->matchPossible() ) { | | | |
| i = _orSets.erase( i ); | | | |
| } else { | | | |
| ++i; | | | |
| } | | | |
| } | | | |
| _oldOrSets.push_front( toPop ); | | | |
| _orSets.pop_front(); | | | |
| } | | } | |
|
| FieldRangeSet *topFrs() const { | | static BSONObj minObject() { | |
| FieldRangeSet *ret = new FieldRangeSet( _baseSet ); | | BSONObjBuilder b; b.appendMinKey( "" ); | |
| if (_orSets.size()){ | | return b.obj(); | |
| *ret &= _orSets.front(); | | | |
| } | | | |
| return ret; | | | |
| } | | } | |
|
| void allClausesSimplified( vector< BSONObj > &ret ) const { | | static BSONObj maxObject() { | |
| for( list< FieldRangeSet >::const_iterator i = _orSets.begin();
i != _orSets.end(); ++i ) { | | BSONObjBuilder b; b.appendMaxKey( "" ); | |
| if ( i->matchPossible() ) { | | return b.obj(); | |
| ret.push_back( i->simplifiedQuery() ); | | | |
| } | | | |
| } | | | |
| } | | } | |
|
| string getSpecial() const { return _baseSet.getSpecial(); } | | /** | |
| | | * @return Suggested advance method, based on current key. | |
| bool moreOrClauses() const { return !_orSets.empty(); } | | * -2 Iteration is complete, no need to advance. | |
| | | * -1 Advance to the next key, without skipping. | |
| | | * >=0 Skip parameter. If @return is r, skip to the key comprised | |
| | | * of the first r elements of curr followed by the (r+1)th and | |
| | | * remaining elements of cmp() (with inclusivity specified by | |
| | | * the (r+1)th and remaining elements of inc()). If after() i
s | |
| | | * true, skip past this key not to it. | |
| | | */ | |
| | | int advance( const BSONObj &curr ); | |
| | | const vector<const BSONElement *> &cmp() const { return _cmp; } | |
| | | const vector<bool> &inc() const { return _inc; } | |
| | | bool after() const { return _after; } | |
| | | void prepDive(); | |
| | | void setZero( int i ) { for( int j = i; j < (int)_i.size(); ++j ) _
i[ j ] = 0; } | |
| | | void setMinus( int i ) { for( int j = i; j < (int)_i.size(); ++j )
_i[ j ] = -1; } | |
| | | bool ok() { return _i[ 0 ] < (int)_v._ranges[ 0 ].intervals().size(
); } | |
| | | BSONObj startKey(); | |
| | | // temp | |
| | | BSONObj endKey(); | |
| private: | | private: | |
|
| FieldRangeSet _baseSet; | | const FieldRangeVector &_v; | |
| list< FieldRangeSet > _orSets; | | vector<int> _i; | |
| list< FieldRangeSet > _oldOrSets; // make sure memory is owned | | vector<const BSONElement*> _cmp; | |
| bool _orFound; | | vector<bool> _inc; | |
| | | bool _after; | |
| }; | | }; | |
| | | | |
| /** | | /** | |
|
| used for doing field limiting | | * As we iterate through $or clauses this class generates a FieldRangeS
etPair | |
| | | * for the current $or clause, in some cases by excluding ranges that w
ere | |
| | | * included in a previous clause. | |
| */ | | */ | |
|
| class FieldMatcher { | | class OrRangeGenerator { | |
| public: | | public: | |
|
| FieldMatcher() | | OrRangeGenerator( const char *ns, const BSONObj &query , bool optim
ize=true ); | |
| : _include(true) | | | |
| , _special(false) | | | |
| , _includeID(true) | | | |
| , _skip(0) | | | |
| , _limit(-1) | | | |
| {} | | | |
| | | | |
|
| void add( const BSONObj& o ); | | /** | |
| | | * @return true iff we are done scanning $or clauses. if there's a | |
| | | * useless or clause, we won't use or index ranges to help with sca
nning. | |
| | | */ | |
| | | bool orFinished() const { return _orFound && _orSets.empty(); } | |
| | | /** Iterates to the next $or clause by removing the current $or cla
use. */ | |
| | | void popOrClause( NamespaceDetails *nsd, int idxNo, const BSONObj &
keyPattern ); | |
| | | void popOrClauseSingleKey(); | |
| | | /** @return FieldRangeSetPair for the current $or clause. */ | |
| | | FieldRangeSetPair *topFrsp() const; | |
| | | /** | |
| | | * @return original FieldRangeSetPair for the current $or clause. W
hile the | |
| | | * original bounds are looser, they are composed of fewer ranges an
d it | |
| | | * is faster to do operations with them; when they can be used inst
ead of | |
| | | * more precise bounds, they should. | |
| | | */ | |
| | | FieldRangeSetPair *topFrspOriginal() const; | |
| | | | |
|
| void append( BSONObjBuilder& b , const BSONElement& e ) const; | | string getSpecial() const { return _baseSet.getSpecial(); } | |
| | | | |
|
| BSONObj getSpec() const; | | bool moreOrClauses() const { return !_orSets.empty(); } | |
| bool includeID() { return _includeID; } | | | |
| private: | | private: | |
|
| | | void assertMayPopOrClause(); | |
| void add( const string& field, bool include ); | | void popOrClause( const FieldRangeSet *toDiff, NamespaceDetails *d
= 0, int idxNo = -1, const BSONObj &keyPattern = BSONObj() ); | |
| void add( const string& field, int skip, int limit ); | | FieldRangeSetPair _baseSet; | |
| void appendArray( BSONObjBuilder& b , const BSONObj& a , bool neste
d=false) const; | | list<FieldRangeSetPair> _orSets; | |
| | | list<FieldRangeSetPair> _originalOrSets; | |
| bool _include; // true if default at this level is to include | | // ensure memory is owned | |
| bool _special; // true if this level can't be skipped or included w
ithout recursing | | list<FieldRangeSetPair> _oldOrSets; | |
| //TODO: benchmark vector<pair> vs map | | bool _orFound; | |
| typedef map<string, boost::shared_ptr<FieldMatcher> > FieldMap; | | friend struct QueryUtilIndexed; | |
| FieldMap _fields; | | | |
| BSONObj _source; | | | |
| bool _includeID; | | | |
| | | | |
| // used for $slice operator | | | |
| int _skip; | | | |
| int _limit; | | | |
| }; | | }; | |
| | | | |
| /** returns a string that when used as a matcher, would match a super s
et of regex() | | /** returns a string that when used as a matcher, would match a super s
et of regex() | |
| returns "" for complex regular expressions | | returns "" for complex regular expressions | |
| used to optimize queries in some simple regex cases that start with
'^' | | used to optimize queries in some simple regex cases that start with
'^' | |
| | | | |
| if purePrefix != NULL, sets it to whether the regex can be converte
d to a range query | | if purePrefix != NULL, sets it to whether the regex can be converte
d to a range query | |
| */ | | */ | |
| string simpleRegex(const char* regex, const char* flags, bool* purePref
ix=NULL); | | string simpleRegex(const char* regex, const char* flags, bool* purePref
ix=NULL); | |
| | | | |
| /** returns the upper bound of a query that matches prefix */ | | /** returns the upper bound of a query that matches prefix */ | |
| string simpleRegexEnd( string prefix ); | | string simpleRegexEnd( string prefix ); | |
| | | | |
| long long applySkipLimit( long long num , const BSONObj& cmd ); | | long long applySkipLimit( long long num , const BSONObj& cmd ); | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
|
| | | | |
| | | #include "queryutil-inl.h" | |
| | | | |
End of changes. 54 change blocks. |
| 446 lines changed or deleted | | 315 lines changed or added | |
|
| repl.h | | repl.h | |
| | | | |
| skipping to change at line 33 | | skipping to change at line 33 | |
| | | | |
| at the master: | | at the master: | |
| local.oplog.$<source> | | local.oplog.$<source> | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "pdfile.h" | | #include "pdfile.h" | |
| #include "db.h" | | #include "db.h" | |
| #include "dbhelpers.h" | | #include "dbhelpers.h" | |
|
| #include "query.h" | | | |
| #include "queryoptimizer.h" | | | |
| #include "../client/dbclient.h" | | #include "../client/dbclient.h" | |
| #include "../util/optime.h" | | #include "../util/optime.h" | |
| #include "oplog.h" | | #include "oplog.h" | |
| #include "../util/concurrency/thread_pool.h" | | #include "../util/concurrency/thread_pool.h" | |
| #include "oplogreader.h" | | #include "oplogreader.h" | |
|
| | | #include "cloner.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| /* replication slave? (possibly with slave or repl pair nonmaster) | | /* replication slave? (possibly with slave) | |
| --slave cmd line setting -> SimpleSlave | | --slave cmd line setting -> SimpleSlave | |
|
| */ | | */ | |
| typedef enum { NotSlave=0, SimpleSlave, ReplPairSlave } SlaveTypes; | | typedef enum { NotSlave=0, SimpleSlave } SlaveTypes; | |
| | | | |
| class ReplSettings { | | class ReplSettings { | |
| public: | | public: | |
| SlaveTypes slave; | | SlaveTypes slave; | |
| | | | |
|
| /* true means we are master and doing replication. if we are not w
riting to oplog (no --master or repl pairing), | | /** true means we are master and doing replication. if we are not
writing to oplog, this won't be true. */ | |
| this won't be true. | | | |
| */ | | | |
| bool master; | | bool master; | |
| | | | |
|
| int opIdMem; | | | |
| | | | |
| bool fastsync; | | bool fastsync; | |
| | | | |
| bool autoresync; | | bool autoresync; | |
| | | | |
| int slavedelay; | | int slavedelay; | |
| | | | |
|
| | | set<string> discoveredSeeds; | |
| | | BSONObj reconfig; | |
| | | | |
| ReplSettings() | | ReplSettings() | |
|
| : slave(NotSlave) , master(false) , opIdMem(100000000) , fastsy
nc() , autoresync(false), slavedelay() { | | : slave(NotSlave) , master(false) , fastsync() , autoresync(fal
se), slavedelay(), discoveredSeeds() { | |
| } | | } | |
| | | | |
| }; | | }; | |
| | | | |
| extern ReplSettings replSettings; | | extern ReplSettings replSettings; | |
| | | | |
|
| bool cloneFrom(const char *masterHost, string& errmsg, const string& fr
omdb, bool logForReplication, | | | |
| bool slaveOk, bool useReplAuth, bool snap
shot); | | | |
| | | | |
| /* A replication exception */ | | /* A replication exception */ | |
| class SyncException : public DBException { | | class SyncException : public DBException { | |
| public: | | public: | |
|
| SyncException() : DBException( "sync exception" , 10001 ){} | | SyncException() : DBException( "sync exception" , 10001 ) {} | |
| }; | | }; | |
| | | | |
| /* A Source is a source from which we can pull (replicate) data. | | /* A Source is a source from which we can pull (replicate) data. | |
| stored in collection local.sources. | | stored in collection local.sources. | |
| | | | |
| Can be a group of things to replicate for several databases. | | Can be a group of things to replicate for several databases. | |
| | | | |
|
| { host: ..., source: ..., only: ..., syncedTo: ..., localLogTs: .
.., dbsNextPass: { ... }, incompleteCloneDbs: { ... } } | | { host: ..., source: ..., only: ..., syncedTo: ..., dbsNextPass:
{ ... }, incompleteCloneDbs: { ... } } | |
| | | | |
| 'source' defaults to 'main'; support for multiple source names is | | 'source' defaults to 'main'; support for multiple source names is | |
| not done (always use main for now). | | not done (always use main for now). | |
| */ | | */ | |
| class ReplSource { | | class ReplSource { | |
|
| auto_ptr<ThreadPool> tp; | | shared_ptr<ThreadPool> tp; | |
| | | | |
|
| bool resync(string db); | | void resync(string db); | |
| | | | |
|
| /* pull some operations from the master's oplog, and apply them. */ | | /** @param alreadyLocked caller already put us in write lock if tru
e */ | |
| int sync_pullOpLog(int& nApplied); | | void sync_pullOpLog_applyOperation(BSONObj& op, bool alreadyLocked)
; | |
| | | | |
|
| void sync_pullOpLog_applyOperation(BSONObj& op, OpTime *localLogTai
l); | | /* pull some operations from the master's oplog, and apply them. | |
| | | calls sync_pullOpLog_applyOperation | |
| | | */ | |
| | | int sync_pullOpLog(int& nApplied); | |
| | | | |
| /* we only clone one database per pass, even if a lot need done. T
his helps us | | /* we only clone one database per pass, even if a lot need done. T
his helps us | |
| avoid overflowing the master's transaction log by doing too much
work before going | | avoid overflowing the master's transaction log by doing too much
work before going | |
| back to read more transactions. (Imagine a scenario of slave sta
rtup where we try to | | back to read more transactions. (Imagine a scenario of slave sta
rtup where we try to | |
| clone 100 databases in one pass.) | | clone 100 databases in one pass.) | |
| */ | | */ | |
| set<string> addDbNextPass; | | set<string> addDbNextPass; | |
| | | | |
| set<string> incompleteCloneDbs; | | set<string> incompleteCloneDbs; | |
| | | | |
| ReplSource(); | | ReplSource(); | |
| | | | |
| // returns the dummy ns used to do the drop | | // returns the dummy ns used to do the drop | |
| string resyncDrop( const char *db, const char *requester ); | | string resyncDrop( const char *db, const char *requester ); | |
|
| // returns possibly unowned id spec for the operation. | | | |
| static BSONObj idForOp( const BSONObj &op, bool &mod ); | | | |
| static void updateSetsWithOp( const BSONObj &op, bool mayUpdateStor
age ); | | | |
| // call without the db mutex | | // call without the db mutex | |
| void syncToTailOfRemoteLog(); | | void syncToTailOfRemoteLog(); | |
|
| // call with the db mutex | | | |
| OpTime nextLastSavedLocalTs() const; | | | |
| void setLastSavedLocalTs( const OpTime &nextLocalTs ); | | | |
| // call without the db mutex | | | |
| void resetSlave(); | | | |
| // call with the db mutex | | | |
| // returns false if the slave has been reset | | | |
| bool updateSetsWithLocalOps( OpTime &localLogTail, bool mayUnlock )
; | | | |
| string ns() const { return string( "local.oplog.$" ) + sourceName()
; } | | string ns() const { return string( "local.oplog.$" ) + sourceName()
; } | |
| unsigned _sleepAdviceTime; | | unsigned _sleepAdviceTime; | |
| | | | |
|
| | | /** | |
| | | * If 'db' is a new database and its name would conflict with that
of | |
| | | * an existing database, synchronize these database names with the | |
| | | * master. | |
| | | * @return true iff an op with the specified ns may be applied. | |
| | | */ | |
| | | bool handleDuplicateDbName( const BSONObj &op, const char *ns, cons
t char *db ); | |
| | | | |
| public: | | public: | |
| OplogReader oplogReader; | | OplogReader oplogReader; | |
| | | | |
| static void applyOperation(const BSONObj& op); | | static void applyOperation(const BSONObj& op); | |
|
| bool replacing; // in "replace mode" -- see CmdReplacePeer | | | |
| bool paired; // --pair in use | | | |
| string hostName; // ip addr or hostname plus optionally, ":<port
>" | | string hostName; // ip addr or hostname plus optionally, ":<port
>" | |
| string _sourceName; // a logical source name. | | string _sourceName; // a logical source name. | |
|
| string sourceName() const { | | string sourceName() const { return _sourceName.empty() ? "main" : _
sourceName; } | |
| return _sourceName.empty() ? "main" : _sourceName; | | | |
| } | | | |
| string only; // only a certain db. note that in the sources collect
ion, this may not be changed once you start replicating. | | string only; // only a certain db. note that in the sources collect
ion, this may not be changed once you start replicating. | |
| | | | |
| /* the last time point we have already synced up to (in the remote/
master's oplog). */ | | /* the last time point we have already synced up to (in the remote/
master's oplog). */ | |
| OpTime syncedTo; | | OpTime syncedTo; | |
| | | | |
|
| /* This is for repl pairs. | | | |
| _lastSavedLocalTs is the most recent point in the local log that
we know is consistent | | | |
| with the remote log ( ie say the local op log has entries ABCDE
and the remote op log | | | |
| has ABCXY, then _lastSavedLocalTs won't be greater than C until
we have reconciled | | | |
| the DE-XY difference.) | | | |
| */ | | | |
| OpTime _lastSavedLocalTs; | | | |
| | | | |
| int nClonedThisPass; | | int nClonedThisPass; | |
| | | | |
| typedef vector< shared_ptr< ReplSource > > SourceVector; | | typedef vector< shared_ptr< ReplSource > > SourceVector; | |
| static void loadAll(SourceVector&); | | static void loadAll(SourceVector&); | |
| explicit ReplSource(BSONObj); | | explicit ReplSource(BSONObj); | |
| | | | |
| /* -1 = error */ | | /* -1 = error */ | |
| int sync(int& nApplied); | | int sync(int& nApplied); | |
| | | | |
| void save(); // write ourself to local.sources | | void save(); // write ourself to local.sources | |
| | | | |
| skipping to change at line 188 | | skipping to change at line 171 | |
| return 0; | | return 0; | |
| int wait = _sleepAdviceTime - unsigned( time( 0 ) ); | | int wait = _sleepAdviceTime - unsigned( time( 0 ) ); | |
| return wait > 0 ? wait : 0; | | return wait > 0 ? wait : 0; | |
| } | | } | |
| | | | |
| static bool throttledForceResyncDead( const char *requester ); | | static bool throttledForceResyncDead( const char *requester ); | |
| static void forceResyncDead( const char *requester ); | | static void forceResyncDead( const char *requester ); | |
| void forceResync( const char *requester ); | | void forceResync( const char *requester ); | |
| }; | | }; | |
| | | | |
|
| // class for managing a set of ids in memory | | bool anyReplEnabled(); | |
| class MemIds { | | void appendReplicationInfo( BSONObjBuilder& result , bool authed , int
level = 0 ); | |
| public: | | | |
| MemIds() : size_() {} | | | |
| friend class IdTracker; | | | |
| void reset() { | | | |
| imp_.clear(); | | | |
| size_ = 0; | | | |
| } | | | |
| bool get( const char *ns, const BSONObj &id ) { return imp_[ ns ].c
ount( id ); } | | | |
| void set( const char *ns, const BSONObj &id, bool val ) { | | | |
| if ( val ) { | | | |
| if ( imp_[ ns ].insert( id.getOwned() ).second ) { | | | |
| size_ += id.objsize() + sizeof( BSONObj ); | | | |
| } | | | |
| } else { | | | |
| if ( imp_[ ns ].erase( id ) == 1 ) { | | | |
| size_ -= id.objsize() + sizeof( BSONObj ); | | | |
| } | | | |
| } | | | |
| } | | | |
| long long roughSize() const { | | | |
| return size_; | | | |
| } | | | |
| private: | | | |
| typedef map< string, BSONObjSetDefaultOrder > IdSets; | | | |
| IdSets imp_; | | | |
| long long size_; | | | |
| }; | | | |
| | | | |
| // class for managing a set of ids in a db collection | | | |
| // All functions must be called with db mutex held | | | |
| class DbIds { | | | |
| public: | | | |
| DbIds( const string & name ) : impl_( name, BSON( "ns" << 1 << "id"
<< 1 ) ) {} | | | |
| void reset() { | | | |
| impl_.reset(); | | | |
| } | | | |
| bool get( const char *ns, const BSONObj &id ) { | | | |
| return impl_.get( key( ns, id ) ); | | | |
| } | | | |
| void set( const char *ns, const BSONObj &id, bool val ) { | | | |
| impl_.set( key( ns, id ), val ); | | | |
| } | | | |
| private: | | | |
| static BSONObj key( const char *ns, const BSONObj &id ) { | | | |
| BSONObjBuilder b; | | | |
| b << "ns" << ns; | | | |
| // rename _id to id since there may be duplicates | | | |
| b.appendAs( id.firstElement(), "id" ); | | | |
| return b.obj(); | | | |
| } | | | |
| DbSet impl_; | | | |
| }; | | | |
| | | | |
|
| // class for tracking ids and mod ids, in memory or on disk | | /** | |
| // All functions must be called with db mutex held | | * Helper class used to set and query an ignore state for a named datab
ase. | |
| // Kind of sloppy class structure, for now just want to keep the in mem | | * The ignore state will expire after a specified OpTime. | |
| // version speedy. | | */ | |
| // see http://www.mongodb.org/display/DOCS/Pairing+Internals | | class DatabaseIgnorer { | |
| class IdTracker { | | | |
| public: | | public: | |
|
| IdTracker() : | | /** Indicate that operations for 'db' should be ignored until after
'futureOplogTime' */ | |
| dbIds_( "local.temp.replIds" ), | | void doIgnoreUntilAfter( const string &db, const OpTime &futureOplo
gTime ); | |
| dbModIds_( "local.temp.replModIds" ), | | /** | |
| inMem_( true ), | | * Query ignore state of 'db'; if 'currentOplogTime' is after the i
gnore | |
| maxMem_( replSettings.opIdMem ) { | | * limit, the ignore state will be cleared. | |
| } | | */ | |
| void reset( int maxMem = replSettings.opIdMem ) { | | bool ignoreAt( const string &db, const OpTime ¤tOplogTime ); | |
| memIds_.reset(); | | | |
| memModIds_.reset(); | | | |
| dbIds_.reset(); | | | |
| dbModIds_.reset(); | | | |
| maxMem_ = maxMem; | | | |
| inMem_ = true; | | | |
| } | | | |
| bool haveId( const char *ns, const BSONObj &id ) { | | | |
| if ( inMem_ ) | | | |
| return get( memIds_, ns, id ); | | | |
| else | | | |
| return get( dbIds_, ns, id ); | | | |
| } | | | |
| bool haveModId( const char *ns, const BSONObj &id ) { | | | |
| if ( inMem_ ) | | | |
| return get( memModIds_, ns, id ); | | | |
| else | | | |
| return get( dbModIds_, ns, id ); | | | |
| } | | | |
| void haveId( const char *ns, const BSONObj &id, bool val ) { | | | |
| if ( inMem_ ) | | | |
| set( memIds_, ns, id, val ); | | | |
| else | | | |
| set( dbIds_, ns, id, val ); | | | |
| } | | | |
| void haveModId( const char *ns, const BSONObj &id, bool val ) { | | | |
| if ( inMem_ ) | | | |
| set( memModIds_, ns, id, val ); | | | |
| else | | | |
| set( dbModIds_, ns, id, val ); | | | |
| } | | | |
| // will release the db mutex | | | |
| void mayUpgradeStorage() { | | | |
| if ( !inMem_ || memIds_.roughSize() + memModIds_.roughSize() <=
maxMem_ ) | | | |
| return; | | | |
| log() << "saving master modified id information to collection"
<< endl; | | | |
| upgrade( memIds_, dbIds_ ); | | | |
| upgrade( memModIds_, dbModIds_ ); | | | |
| memIds_.reset(); | | | |
| memModIds_.reset(); | | | |
| inMem_ = false; | | | |
| } | | | |
| bool inMem() const { return inMem_; } | | | |
| private: | | private: | |
|
| template< class T > | | map< string, OpTime > _ignores; | |
| bool get( T &ids, const char *ns, const BSONObj &id ) { | | | |
| return ids.get( ns, id ); | | | |
| } | | | |
| template< class T > | | | |
| void set( T &ids, const char *ns, const BSONObj &id, bool val ) { | | | |
| ids.set( ns, id, val ); | | | |
| } | | | |
| void upgrade( MemIds &a, DbIds &b ) { | | | |
| for( MemIds::IdSets::const_iterator i = a.imp_.begin(); i != a.
imp_.end(); ++i ) { | | | |
| for( BSONObjSetDefaultOrder::const_iterator j = i->second.b
egin(); j != i->second.end(); ++j ) { | | | |
| set( b, i->first.c_str(), *j, true ); | | | |
| RARELY { | | | |
| dbtemprelease t; | | | |
| } | | | |
| } | | | |
| } | | | |
| } | | | |
| MemIds memIds_; | | | |
| MemIds memModIds_; | | | |
| DbIds dbIds_; | | | |
| DbIds dbModIds_; | | | |
| bool inMem_; | | | |
| int maxMem_; | | | |
| }; | | }; | |
| | | | |
|
| bool anyReplEnabled(); | | | |
| void appendReplicationInfo( BSONObjBuilder& result , bool authed , int
level = 0 ); | | | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 26 change blocks. |
| 182 lines changed or deleted | | 43 lines changed or added | |
|
| rs.h | | rs.h | |
| | | | |
| skipping to change at line 24 | | skipping to change at line 24 | |
| * | | * | |
| * You should have received a copy of the GNU Affero General Public Licen
se | | * You should have received a copy of the GNU Affero General Public Licen
se | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "../../util/concurrency/list.h" | | #include "../../util/concurrency/list.h" | |
| #include "../../util/concurrency/value.h" | | #include "../../util/concurrency/value.h" | |
| #include "../../util/concurrency/msg.h" | | #include "../../util/concurrency/msg.h" | |
|
| #include "../../util/hostandport.h" | | #include "../../util/net/hostandport.h" | |
| #include "../commands.h" | | #include "../commands.h" | |
|
| | | #include "../oplogreader.h" | |
| #include "rs_exception.h" | | #include "rs_exception.h" | |
| #include "rs_optime.h" | | #include "rs_optime.h" | |
| #include "rs_member.h" | | #include "rs_member.h" | |
| #include "rs_config.h" | | #include "rs_config.h" | |
| | | | |
|
| | | /** | |
| | | * Order of Events | |
| | | * | |
| | | * On startup, if the --replSet option is present, startReplSets is called. | |
| | | * startReplSets forks off a new thread for replica set activities. It cre
ates | |
| | | * the global theReplSet variable and calls go() on it. | |
| | | * | |
| | | * theReplSet's constructor changes the replica set's state to RS_STARTUP, | |
| | | * starts the replica set manager, and loads the config (if the replica set | |
| | | * has been initialized). | |
| | | */ | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
| struct HowToFixUp; | | struct HowToFixUp; | |
| struct Target; | | struct Target; | |
| class DBClientConnection; | | class DBClientConnection; | |
| class ReplSetImpl; | | class ReplSetImpl; | |
| class OplogReader; | | class OplogReader; | |
| extern bool replSet; // true if using repl sets | | extern bool replSet; // true if using repl sets | |
| extern class ReplSet *theReplSet; // null until initialized | | extern class ReplSet *theReplSet; // null until initialized | |
| extern Tee *rsLog; | | extern Tee *rsLog; | |
| | | | |
| /* member of a replica set */ | | /* member of a replica set */ | |
| class Member : public List1<Member>::Base { | | class Member : public List1<Member>::Base { | |
|
| | | private: | |
| | | ~Member(); // intentionally unimplemented as should never be called
-- see List1<>::Base. | |
| | | Member(const Member&); | |
| public: | | public: | |
| Member(HostAndPort h, unsigned ord, const ReplSetConfig::MemberCfg
*c, bool self); | | Member(HostAndPort h, unsigned ord, const ReplSetConfig::MemberCfg
*c, bool self); | |
|
| | | | |
| string fullName() const { return h().toString(); } | | string fullName() const { return h().toString(); } | |
| const ReplSetConfig::MemberCfg& config() const { return _config; } | | const ReplSetConfig::MemberCfg& config() const { return _config; } | |
| const HeartbeatInfo& hbinfo() const { return _hbinfo; } | | const HeartbeatInfo& hbinfo() const { return _hbinfo; } | |
| HeartbeatInfo& get_hbinfo() { return _hbinfo; } | | HeartbeatInfo& get_hbinfo() { return _hbinfo; } | |
| string lhb() const { return _hbinfo.lastHeartbeatMsg; } | | string lhb() const { return _hbinfo.lastHeartbeatMsg; } | |
| MemberState state() const { return _hbinfo.hbstate; } | | MemberState state() const { return _hbinfo.hbstate; } | |
| const HostAndPort& h() const { return _h; } | | const HostAndPort& h() const { return _h; } | |
| unsigned id() const { return _hbinfo.id(); } | | unsigned id() const { return _hbinfo.id(); } | |
|
| | | | |
| bool potentiallyHot() const { return _config.potentiallyHot(); } //
not arbiter, not priority 0 | | bool potentiallyHot() const { return _config.potentiallyHot(); } //
not arbiter, not priority 0 | |
| void summarizeMember(stringstream& s) const; | | void summarizeMember(stringstream& s) const; | |
|
| friend class ReplSetImpl; | | | |
| private: | | private: | |
|
| | | friend class ReplSetImpl; | |
| const ReplSetConfig::MemberCfg _config; | | const ReplSetConfig::MemberCfg _config; | |
| const HostAndPort _h; | | const HostAndPort _h; | |
| HeartbeatInfo _hbinfo; | | HeartbeatInfo _hbinfo; | |
| }; | | }; | |
| | | | |
| class Manager : public task::Server { | | class Manager : public task::Server { | |
| ReplSetImpl *rs; | | ReplSetImpl *rs; | |
| bool busyWithElectSelf; | | bool busyWithElectSelf; | |
| int _primary; | | int _primary; | |
| | | | |
| /** @param two - if true two primaries were seen. this can happen
transiently, in addition to our | | /** @param two - if true two primaries were seen. this can happen
transiently, in addition to our | |
| polling being only occasional. in this case null
is returned, but the caller should | | polling being only occasional. in this case null
is returned, but the caller should | |
| not assume primary itself in that situation. | | not assume primary itself in that situation. | |
| */ | | */ | |
| const Member* findOtherPrimary(bool& two); | | const Member* findOtherPrimary(bool& two); | |
| | | | |
| void noteARemoteIsPrimary(const Member *); | | void noteARemoteIsPrimary(const Member *); | |
|
| | | void checkElectableSet(); | |
| virtual void starting(); | | virtual void starting(); | |
| public: | | public: | |
| Manager(ReplSetImpl *rs); | | Manager(ReplSetImpl *rs); | |
|
| ~Manager(); | | virtual ~Manager(); | |
| void msgReceivedNewConfig(BSONObj); | | void msgReceivedNewConfig(BSONObj); | |
| void msgCheckNewState(); | | void msgCheckNewState(); | |
| }; | | }; | |
| | | | |
|
| | | class GhostSync : public task::Server { | |
| | | struct GhostSlave { | |
| | | GhostSlave() : last(0), slave(0), init(false) {} | |
| | | OplogReader reader; | |
| | | OpTime last; | |
| | | Member* slave; | |
| | | bool init; | |
| | | }; | |
| | | /** | |
| | | * This is a cache of ghost slaves | |
| | | */ | |
| | | typedef map<mongo::OID,GhostSlave> MAP; | |
| | | MAP _ghostCache; | |
| | | RWLock _lock; // protects _ghostCache | |
| | | ReplSetImpl *rs; | |
| | | virtual void starting(); | |
| | | public: | |
| | | GhostSync(ReplSetImpl *_rs) : task::Server("rsGhostSync"), _lock("G
hostSync"), rs(_rs) {} | |
| | | ~GhostSync() { | |
| | | log() << "~GhostSync() called" << rsLog; | |
| | | } | |
| | | | |
| | | /** | |
| | | * Replica sets can sync in a hierarchical fashion, which throws of
f w | |
| | | * calculation on the master. percolate() faux-syncs from an upstr
eam | |
| | | * node so that the primary will know what the slaves are up to. | |
| | | * | |
| | | * We can't just directly sync to the primary because it could be | |
| | | * unreachable, e.g., S1--->S2--->S3--->P. S2 should ghost sync fr
om S3 | |
| | | * and S3 can ghost sync from the primary. | |
| | | * | |
| | | * Say we have an S1--->S2--->P situation and this node is S2. rid | |
| | | * would refer to S1. S2 would create a ghost slave of S1 and conn
ect | |
| | | * it to P (_currentSyncTarget). Then it would use this connection
to | |
| | | * pretend to be S1, replicating off of P. | |
| | | */ | |
| | | void percolate(const BSONObj& rid, const OpTime& last); | |
| | | void associateSlave(const BSONObj& rid, const int memberId); | |
| | | void updateSlave(const mongo::OID& id, const OpTime& last); | |
| | | }; | |
| | | | |
| struct Target; | | struct Target; | |
| | | | |
| class Consensus { | | class Consensus { | |
| ReplSetImpl &rs; | | ReplSetImpl &rs; | |
| struct LastYea { | | struct LastYea { | |
| LastYea() : when(0), who(0xffffffff) { } | | LastYea() : when(0), who(0xffffffff) { } | |
| time_t when; | | time_t when; | |
| unsigned who; | | unsigned who; | |
| }; | | }; | |
|
| Atomic<LastYea> ly; | | static mutex lyMutex; | |
| | | Guarded<LastYea,lyMutex> ly; | |
| unsigned yea(unsigned memberId); // throws VoteException | | unsigned yea(unsigned memberId); // throws VoteException | |
| void electionFailed(unsigned meid); | | void electionFailed(unsigned meid); | |
| void _electSelf(); | | void _electSelf(); | |
| bool weAreFreshest(bool& allUp, int& nTies); | | bool weAreFreshest(bool& allUp, int& nTies); | |
| bool sleptLast; // slept last elect() pass | | bool sleptLast; // slept last elect() pass | |
| public: | | public: | |
| Consensus(ReplSetImpl *t) : rs(*t) { | | Consensus(ReplSetImpl *t) : rs(*t) { | |
| sleptLast = false; | | sleptLast = false; | |
| steppedDown = 0; | | steppedDown = 0; | |
| } | | } | |
| | | | |
| skipping to change at line 117 | | skipping to change at line 179 | |
| time_t steppedDown; | | time_t steppedDown; | |
| | | | |
| int totalVotes() const; | | int totalVotes() const; | |
| bool aMajoritySeemsToBeUp() const; | | bool aMajoritySeemsToBeUp() const; | |
| bool shouldRelinquish() const; | | bool shouldRelinquish() const; | |
| void electSelf(); | | void electSelf(); | |
| void electCmdReceived(BSONObj, BSONObjBuilder*); | | void electCmdReceived(BSONObj, BSONObjBuilder*); | |
| void multiCommand(BSONObj cmd, list<Target>& L); | | void multiCommand(BSONObj cmd, list<Target>& L); | |
| }; | | }; | |
| | | | |
|
| /** most operations on a ReplSet object should be done while locked. th
at logic implemented here. */ | | /** | |
| | | * most operations on a ReplSet object should be done while locked. tha
t | |
| | | * logic implemented here. | |
| | | * | |
| | | * Order of locking: lock the replica set, then take a rwlock. | |
| | | */ | |
| class RSBase : boost::noncopyable { | | class RSBase : boost::noncopyable { | |
| public: | | public: | |
| const unsigned magic; | | const unsigned magic; | |
| void assertValid() { assert( magic == 0x12345677 ); } | | void assertValid() { assert( magic == 0x12345677 ); } | |
| private: | | private: | |
|
| mutex m; | | mongo::mutex m; | |
| int _locked; | | int _locked; | |
| ThreadLocalValue<bool> _lockedByMe; | | ThreadLocalValue<bool> _lockedByMe; | |
| protected: | | protected: | |
| RSBase() : magic(0x12345677), m("RSBase"), _locked(0) { } | | RSBase() : magic(0x12345677), m("RSBase"), _locked(0) { } | |
| ~RSBase() { | | ~RSBase() { | |
| /* this can happen if we throw in the constructor; otherwise ne
ver happens. thus we log it as it is quite unusual. */ | | /* this can happen if we throw in the constructor; otherwise ne
ver happens. thus we log it as it is quite unusual. */ | |
| log() << "replSet ~RSBase called" << rsLog; | | log() << "replSet ~RSBase called" << rsLog; | |
| } | | } | |
| | | | |
|
| | | public: | |
| class lock { | | class lock { | |
| RSBase& rsbase; | | RSBase& rsbase; | |
| auto_ptr<scoped_lock> sl; | | auto_ptr<scoped_lock> sl; | |
| public: | | public: | |
| lock(RSBase* b) : rsbase(*b) { | | lock(RSBase* b) : rsbase(*b) { | |
| if( rsbase._lockedByMe.get() ) | | if( rsbase._lockedByMe.get() ) | |
| return; // recursive is ok... | | return; // recursive is ok... | |
| | | | |
| sl.reset( new scoped_lock(rsbase.m) ); | | sl.reset( new scoped_lock(rsbase.m) ); | |
| DEV assert(rsbase._locked == 0); | | DEV assert(rsbase._locked == 0); | |
| | | | |
| skipping to change at line 156 | | skipping to change at line 224 | |
| ~lock() { | | ~lock() { | |
| if( sl.get() ) { | | if( sl.get() ) { | |
| assert( rsbase._lockedByMe.get() ); | | assert( rsbase._lockedByMe.get() ); | |
| DEV assert(rsbase._locked == 1); | | DEV assert(rsbase._locked == 1); | |
| rsbase._lockedByMe.set(false); | | rsbase._lockedByMe.set(false); | |
| rsbase._locked--; | | rsbase._locked--; | |
| } | | } | |
| } | | } | |
| }; | | }; | |
| | | | |
|
| public: | | | |
| /* for asserts */ | | /* for asserts */ | |
| bool locked() const { return _locked != 0; } | | bool locked() const { return _locked != 0; } | |
| | | | |
| /* if true, is locked, and was locked by this thread. note if false
, it could be in the lock or not for another | | /* if true, is locked, and was locked by this thread. note if false
, it could be in the lock or not for another | |
| just for asserts & such so we can make the contracts clear on wh
o locks what when. | | just for asserts & such so we can make the contracts clear on wh
o locks what when. | |
| we don't use these locks that frequently, so the little bit of o
verhead is fine. | | we don't use these locks that frequently, so the little bit of o
verhead is fine. | |
| */ | | */ | |
| bool lockedByMe() { return _lockedByMe.get(); } | | bool lockedByMe() { return _lockedByMe.get(); } | |
| }; | | }; | |
| | | | |
| | | | |
| skipping to change at line 178 | | skipping to change at line 245 | |
| | | | |
| /* safe container for our state that keeps member pointer and state var
iables always aligned */ | | /* safe container for our state that keeps member pointer and state var
iables always aligned */ | |
| class StateBox : boost::noncopyable { | | class StateBox : boost::noncopyable { | |
| public: | | public: | |
| struct SP { // SP is like pair<MemberState,const Member *> but nice
r | | struct SP { // SP is like pair<MemberState,const Member *> but nice
r | |
| SP() : state(MemberState::RS_STARTUP), primary(0) { } | | SP() : state(MemberState::RS_STARTUP), primary(0) { } | |
| MemberState state; | | MemberState state; | |
| const Member *primary; | | const Member *primary; | |
| }; | | }; | |
| const SP get() { | | const SP get() { | |
|
| scoped_lock lk(m); | | rwlock lk(m, false); | |
| return sp; | | return sp; | |
| } | | } | |
|
| MemberState getState() const { return sp.state; } | | MemberState getState() const { | |
| const Member* getPrimary() const { return sp.primary; } | | rwlock lk(m, false); | |
| | | return sp.state; | |
| | | } | |
| | | const Member* getPrimary() const { | |
| | | rwlock lk(m, false); | |
| | | return sp.primary; | |
| | | } | |
| void change(MemberState s, const Member *self) { | | void change(MemberState s, const Member *self) { | |
|
| scoped_lock lk(m); | | rwlock lk(m, true); | |
| if( sp.state != s ) { | | if( sp.state != s ) { | |
| log() << "replSet " << s.toString() << rsLog; | | log() << "replSet " << s.toString() << rsLog; | |
| } | | } | |
| sp.state = s; | | sp.state = s; | |
| if( s.primary() ) { | | if( s.primary() ) { | |
| sp.primary = self; | | sp.primary = self; | |
| } | | } | |
| else { | | else { | |
| if( self == sp.primary ) | | if( self == sp.primary ) | |
| sp.primary = 0; | | sp.primary = 0; | |
| } | | } | |
| } | | } | |
| void set(MemberState s, const Member *p) { | | void set(MemberState s, const Member *p) { | |
|
| scoped_lock lk(m); | | rwlock lk(m, true); | |
| sp.state = s; sp.primary = p; | | sp.state = s; | |
| | | sp.primary = p; | |
| } | | } | |
| void setSelfPrimary(const Member *self) { change(MemberState::RS_PR
IMARY, self); } | | void setSelfPrimary(const Member *self) { change(MemberState::RS_PR
IMARY, self); } | |
| void setOtherPrimary(const Member *mem) { | | void setOtherPrimary(const Member *mem) { | |
|
| scoped_lock lk(m); | | rwlock lk(m, true); | |
| assert( !sp.state.primary() ); | | assert( !sp.state.primary() ); | |
| sp.primary = mem; | | sp.primary = mem; | |
| } | | } | |
| void noteRemoteIsPrimary(const Member *remote) { | | void noteRemoteIsPrimary(const Member *remote) { | |
|
| scoped_lock lk(m); | | rwlock lk(m, true); | |
| if( !sp.state.secondary() ) | | if( !sp.state.secondary() && !sp.state.fatal() ) | |
| sp.state = MemberState::RS_RECOVERING; | | sp.state = MemberState::RS_RECOVERING; | |
| sp.primary = remote; | | sp.primary = remote; | |
| } | | } | |
| StateBox() : m("StateBox") { } | | StateBox() : m("StateBox") { } | |
| private: | | private: | |
|
| mutex m; | | RWLock m; | |
| SP sp; | | SP sp; | |
| }; | | }; | |
| | | | |
| void parseReplsetCmdLine(string cfgString, string& setname, vector<Host
AndPort>& seeds, set<HostAndPort>& seedSet ); | | void parseReplsetCmdLine(string cfgString, string& setname, vector<Host
AndPort>& seeds, set<HostAndPort>& seedSet ); | |
| | | | |
| /** Parameter given to the --replSet command line option (parsed). | | /** Parameter given to the --replSet command line option (parsed). | |
| Syntax is "<setname>/<seedhost1>,<seedhost2>" | | Syntax is "<setname>/<seedhost1>,<seedhost2>" | |
| where setname is a name and seedhost is "<host>[:<port>]" */ | | where setname is a name and seedhost is "<host>[:<port>]" */ | |
| class ReplSetCmdline { | | class ReplSetCmdline { | |
| public: | | public: | |
| | | | |
| skipping to change at line 244 | | skipping to change at line 318 | |
| singleton and long lived. | | singleton and long lived. | |
| */ | | */ | |
| class ReplSetImpl : protected RSBase { | | class ReplSetImpl : protected RSBase { | |
| public: | | public: | |
| /** info on our state if the replset isn't yet "up". for example,
if we are pre-initiation. */ | | /** info on our state if the replset isn't yet "up". for example,
if we are pre-initiation. */ | |
| enum StartupStatus { | | enum StartupStatus { | |
| PRESTART=0, LOADINGCONFIG=1, BADCONFIG=2, EMPTYCONFIG=3, | | PRESTART=0, LOADINGCONFIG=1, BADCONFIG=2, EMPTYCONFIG=3, | |
| EMPTYUNREACHABLE=4, STARTED=5, SOON=6 | | EMPTYUNREACHABLE=4, STARTED=5, SOON=6 | |
| }; | | }; | |
| static StartupStatus startupStatus; | | static StartupStatus startupStatus; | |
|
| static string startupStatusMsg; | | static DiagStr startupStatusMsg; | |
| static string stateAsHtml(MemberState state); | | static string stateAsHtml(MemberState state); | |
| | | | |
| /* todo thread */ | | /* todo thread */ | |
| void msgUpdateHBInfo(HeartbeatInfo); | | void msgUpdateHBInfo(HeartbeatInfo); | |
| | | | |
| StateBox box; | | StateBox box; | |
| | | | |
| OpTime lastOpTimeWritten; | | OpTime lastOpTimeWritten; | |
| long long lastH; // hash we use to make sure we are reading the rig
ht flow of ops and aren't on an out-of-date "fork" | | long long lastH; // hash we use to make sure we are reading the rig
ht flow of ops and aren't on an out-of-date "fork" | |
| private: | | private: | |
| set<ReplSetHealthPollTask*> healthTasks; | | set<ReplSetHealthPollTask*> healthTasks; | |
| void endOldHealthTasks(); | | void endOldHealthTasks(); | |
| void startHealthTaskFor(Member *m); | | void startHealthTaskFor(Member *m); | |
| | | | |
|
| private: | | | |
| Consensus elect; | | Consensus elect; | |
|
| bool ok() const { return !box.getState().fatal(); } | | | |
| | | | |
| void relinquish(); | | void relinquish(); | |
| void forgetPrimary(); | | void forgetPrimary(); | |
|
| | | | |
| protected: | | protected: | |
|
| bool _stepDown(); | | bool _stepDown(int secs); | |
| | | bool _freeze(int secs); | |
| private: | | private: | |
| void assumePrimary(); | | void assumePrimary(); | |
|
| void loadLastOpTimeWritten(); | | void loadLastOpTimeWritten(bool quiet=false); | |
| void changeState(MemberState s); | | void changeState(MemberState s); | |
| | | | |
|
| | | /** | |
| | | * Find the closest member (using ping time) with a higher latest o
ptime. | |
| | | */ | |
| | | const Member* getMemberToSyncTo(); | |
| | | Member* _currentSyncTarget; | |
| | | | |
| | | // set of electable members' _ids | |
| | | set<unsigned> _electableSet; | |
| protected: | | protected: | |
| // "heartbeat message" | | // "heartbeat message" | |
| // sent in requestHeartbeat respond in field "hbm" | | // sent in requestHeartbeat respond in field "hbm" | |
| char _hbmsg[256]; // we change this unlocked, thus not an stl::stri
ng | | char _hbmsg[256]; // we change this unlocked, thus not an stl::stri
ng | |
| time_t _hbmsgTime; // when it was logged | | time_t _hbmsgTime; // when it was logged | |
| public: | | public: | |
| void sethbmsg(string s, int logLevel = 0); | | void sethbmsg(string s, int logLevel = 0); | |
|
| | | | |
| | | /** | |
| | | * Election with Priorities | |
| | | * | |
| | | * Each node (n) keeps a set of nodes that could be elected primary
. | |
| | | * Each node in this set: | |
| | | * | |
| | | * 1. can connect to a majority of the set | |
| | | * 2. has a priority greater than 0 | |
| | | * 3. has an optime within 10 seconds of the most up-to-date node | |
| | | * that n can reach | |
| | | * | |
| | | * If a node fails to meet one or more of these criteria, it is rem
oved | |
| | | * from the list. This list is updated whenever the node receives
a | |
| | | * heartbeat. | |
| | | * | |
| | | * When a node sends an "am I freshest?" query, the node receiving
the | |
| | | * query checks their electable list to make sure that no one else
is | |
| | | * electable AND higher priority. If this check passes, the node w
ill | |
| | | * return an "ok" response, if not, it will veto. | |
| | | * | |
| | | * If a node is primary and there is another node with higher prior
ity | |
| | | * on the electable list (i.e., it must be synced to within 10 seco
nds | |
| | | * of the current primary), the node (or nodes) with connections to
both | |
| | | * the primary and the secondary with higher priority will issue | |
| | | * replSetStepDown requests to the primary to allow the higher-prio
rity | |
| | | * node to take over. | |
| | | */ | |
| | | void addToElectable(const unsigned m) { lock lk(this); _electableSe
t.insert(m); } | |
| | | void rmFromElectable(const unsigned m) { lock lk(this); _electableS
et.erase(m); } | |
| | | bool iAmElectable() { lock lk(this); return _electableSet.find(_sel
f->id()) != _electableSet.end(); } | |
| | | bool isElectable(const unsigned id) { lock lk(this); return _electa
bleSet.find(id) != _electableSet.end(); } | |
| | | Member* getMostElectable(); | |
| protected: | | protected: | |
|
| bool initFromConfig(ReplSetConfig& c, bool reconf=false); // true i
f ok; throws if config really bad; false if config doesn't include self | | /** | |
| | | * Load a new config as the replica set's main config. | |
| | | * | |
| | | * If there is a "simple" change (just adding a node), this shortcu
ts | |
| | | * the config. Returns true if the config was changed. Returns fal
se | |
| | | * if the config doesn't include a this node. Throws an exception
if | |
| | | * something goes very wrong. | |
| | | * | |
| | | * Behavior to note: | |
| | | * - locks this | |
| | | * - intentionally leaks the old _cfg and any old _members (if the | |
| | | * change isn't strictly additive) | |
| | | */ | |
| | | bool initFromConfig(ReplSetConfig& c, bool reconf=false); | |
| void _fillIsMaster(BSONObjBuilder&); | | void _fillIsMaster(BSONObjBuilder&); | |
| void _fillIsMasterHost(const Member*, vector<string>&, vector<strin
g>&, vector<string>&); | | void _fillIsMasterHost(const Member*, vector<string>&, vector<strin
g>&, vector<string>&); | |
| const ReplSetConfig& config() { return *_cfg; } | | const ReplSetConfig& config() { return *_cfg; } | |
| string name() const { return _name; } /* @return replica set's logi
cal name */ | | string name() const { return _name; } /* @return replica set's logi
cal name */ | |
| MemberState state() const { return box.getState(); } | | MemberState state() const { return box.getState(); } | |
| void _fatal(); | | void _fatal(); | |
| void _getOplogDiagsAsHtml(unsigned server_id, stringstream& ss) con
st; | | void _getOplogDiagsAsHtml(unsigned server_id, stringstream& ss) con
st; | |
| void _summarizeAsHtml(stringstream&) const; | | void _summarizeAsHtml(stringstream&) const; | |
| void _summarizeStatus(BSONObjBuilder&) const; // for replSetGetStat
us command | | void _summarizeStatus(BSONObjBuilder&) const; // for replSetGetStat
us command | |
| | | | |
| | | | |
| skipping to change at line 303 | | skipping to change at line 428 | |
| ReplSetImpl(ReplSetCmdline&); | | ReplSetImpl(ReplSetCmdline&); | |
| | | | |
| /* call afer constructing to start - returns fairly quickly after l
aunching its threads */ | | /* call afer constructing to start - returns fairly quickly after l
aunching its threads */ | |
| void _go(); | | void _go(); | |
| | | | |
| private: | | private: | |
| string _name; | | string _name; | |
| const vector<HostAndPort> *_seeds; | | const vector<HostAndPort> *_seeds; | |
| ReplSetConfig *_cfg; | | ReplSetConfig *_cfg; | |
| | | | |
|
| /** load our configuration from admin.replset. try seed machines t
oo. | | /** | |
| @return true if ok; throws if config really bad; false if confi
g doesn't include self | | * Finds the configuration with the highest version number and atte
mpts | |
| */ | | * load it. | |
| | | */ | |
| bool _loadConfigFinish(vector<ReplSetConfig>& v); | | bool _loadConfigFinish(vector<ReplSetConfig>& v); | |
|
| | | /** | |
| | | * Gather all possible configs (from command line seeds, our own co
nfig | |
| | | * doc, and any hosts listed therein) and try to initiate from the
most | |
| | | * recent config we find. | |
| | | */ | |
| void loadConfig(); | | void loadConfig(); | |
| | | | |
| list<HostAndPort> memberHostnames() const; | | list<HostAndPort> memberHostnames() const; | |
|
| const ReplSetConfig::MemberCfg& myConfig() const { return _self->co
nfig(); } | | const ReplSetConfig::MemberCfg& myConfig() const { return _config;
} | |
| bool iAmArbiterOnly() const { return myConfig().arbiterOnly; } | | bool iAmArbiterOnly() const { return myConfig().arbiterOnly; } | |
|
| bool iAmPotentiallyHot() const { return myConfig().potentiallyHot()
; } | | bool iAmPotentiallyHot() const { | |
| | | return myConfig().potentiallyHot() && // not an arbiter | |
| | | elect.steppedDown <= time(0) && // not stepped down/frozen | |
| | | state() == MemberState::RS_SECONDARY; // not stale | |
| | | } | |
| protected: | | protected: | |
| Member *_self; | | Member *_self; | |
|
| | | bool _buildIndexes; // = _self->config().buildIndexes | |
| | | void setSelfTo(Member *); // use this as it sets buildIndexes var | |
| private: | | private: | |
|
| List1<Member> _members; /* all members of the set EXCEPT self. */ | | List1<Member> _members; // all members of the set EXCEPT _self. | |
| | | ReplSetConfig::MemberCfg _config; // config of _self | |
| | | unsigned _id; // _id of _self | |
| | | | |
|
| | | int _maintenanceMode; // if we should stay in recovering state | |
| public: | | public: | |
|
| unsigned selfId() const { return _self->id(); } | | // this is called from within a writelock in logOpRS | |
| | | unsigned selfId() const { return _id; } | |
| Manager *mgr; | | Manager *mgr; | |
|
| | | GhostSync *ghost; | |
| | | /** | |
| | | * This forces a secondary to go into recovering state and stay the
re | |
| | | * until this is called again, passing in "false". Multiple thread
s can | |
| | | * call this and it will leave maintenance mode once all of the cal
lers | |
| | | * have called it again, passing in false. | |
| | | */ | |
| | | void setMaintenanceMode(const bool inc); | |
| private: | | private: | |
| Member* head() const { return _members.head(); } | | Member* head() const { return _members.head(); } | |
| public: | | public: | |
| const Member* findById(unsigned id) const; | | const Member* findById(unsigned id) const; | |
| private: | | private: | |
| void _getTargets(list<Target>&, int &configVersion); | | void _getTargets(list<Target>&, int &configVersion); | |
| void getTargets(list<Target>&, int &configVersion); | | void getTargets(list<Target>&, int &configVersion); | |
| void startThreads(); | | void startThreads(); | |
| friend class FeedbackThread; | | friend class FeedbackThread; | |
| friend class CmdReplSetElect; | | friend class CmdReplSetElect; | |
| friend class Member; | | friend class Member; | |
| friend class Manager; | | friend class Manager; | |
|
| | | friend class GhostSync; | |
| friend class Consensus; | | friend class Consensus; | |
| | | | |
| private: | | private: | |
| /* pulling data from primary related - see rs_sync.cpp */ | | /* pulling data from primary related - see rs_sync.cpp */ | |
|
| bool initialSyncOplogApplication(string hn, const Member *primary,
OpTime applyGTE, OpTime minValid); | | bool initialSyncOplogApplication(const Member *primary, OpTime appl
yGTE, OpTime minValid); | |
| void _syncDoInitialSync(); | | void _syncDoInitialSync(); | |
| void syncDoInitialSync(); | | void syncDoInitialSync(); | |
| void _syncThread(); | | void _syncThread(); | |
| bool tryToGoLiveAsASecondary(OpTime&); // readlocks | | bool tryToGoLiveAsASecondary(OpTime&); // readlocks | |
| void syncTail(); | | void syncTail(); | |
| void syncApply(const BSONObj &o); | | void syncApply(const BSONObj &o); | |
| unsigned _syncRollback(OplogReader& r); | | unsigned _syncRollback(OplogReader& r); | |
| void syncRollback(OplogReader& r); | | void syncRollback(OplogReader& r); | |
| void syncFixUp(HowToFixUp& h, OplogReader& r); | | void syncFixUp(HowToFixUp& h, OplogReader& r); | |
|
| | | bool _getOplogReader(OplogReader& r, string& hn); | |
| | | bool _isStale(OplogReader& r, const string& hn); | |
| public: | | public: | |
| void syncThread(); | | void syncThread(); | |
|
| | | const OpTime lastOtherOpTime() const; | |
| }; | | }; | |
| | | | |
| class ReplSet : public ReplSetImpl { | | class ReplSet : public ReplSetImpl { | |
| public: | | public: | |
| ReplSet(ReplSetCmdline& replSetCmdline) : ReplSetImpl(replSetCmdlin
e) { } | | ReplSet(ReplSetCmdline& replSetCmdline) : ReplSetImpl(replSetCmdlin
e) { } | |
| | | | |
|
| bool stepDown() { return _stepDown(); } | | // for the replSetStepDown command | |
| | | bool stepDown(int secs) { return _stepDown(secs); } | |
| | | | |
| | | // for the replSetFreeze command | |
| | | bool freeze(int secs) { return _freeze(secs); } | |
| | | | |
| string selfFullName() { | | string selfFullName() { | |
|
| lock lk(this); | | assert( _self ); | |
| return _self->fullName(); | | return _self->fullName(); | |
| } | | } | |
| | | | |
|
| | | bool buildIndexes() const { return _buildIndexes; } | |
| | | | |
| /* call after constructing to start - returns fairly quickly after
la[unching its threads */ | | /* call after constructing to start - returns fairly quickly after
la[unching its threads */ | |
| void go() { _go(); } | | void go() { _go(); } | |
|
| | | | |
| void fatal() { _fatal(); } | | void fatal() { _fatal(); } | |
|
| bool isPrimary(); | | bool isPrimary() { return box.getState().primary(); } | |
| bool isSecondary(); | | bool isSecondary() { return box.getState().secondary(); } | |
| MemberState state() const { return ReplSetImpl::state(); } | | MemberState state() const { return ReplSetImpl::state(); } | |
| string name() const { return ReplSetImpl::name(); } | | string name() const { return ReplSetImpl::name(); } | |
| const ReplSetConfig& config() { return ReplSetImpl::config(); } | | const ReplSetConfig& config() { return ReplSetImpl::config(); } | |
| void getOplogDiagsAsHtml(unsigned server_id, stringstream& ss) cons
t { _getOplogDiagsAsHtml(server_id,ss); } | | void getOplogDiagsAsHtml(unsigned server_id, stringstream& ss) cons
t { _getOplogDiagsAsHtml(server_id,ss); } | |
| void summarizeAsHtml(stringstream& ss) const { _summarizeAsHtml(ss)
; } | | void summarizeAsHtml(stringstream& ss) const { _summarizeAsHtml(ss)
; } | |
| void summarizeStatus(BSONObjBuilder& b) const { _summarizeStatus(b
); } | | void summarizeStatus(BSONObjBuilder& b) const { _summarizeStatus(b
); } | |
| void fillIsMaster(BSONObjBuilder& b) { _fillIsMaster(b); } | | void fillIsMaster(BSONObjBuilder& b) { _fillIsMaster(b); } | |
| | | | |
|
| /* we have a new config (reconfig) - apply it. | | /** | |
| @param comment write a no-op comment to the oplog about it. onl
y makes sense if one is primary and initiating the reconf. | | * We have a new config (reconfig) - apply it. | |
| */ | | * @param comment write a no-op comment to the oplog about it. onl
y | |
| | | * makes sense if one is primary and initiating the reconf. | |
| | | * | |
| | | * The slaves are updated when they get a heartbeat indicating the
new | |
| | | * config. The comment is a no-op. | |
| | | */ | |
| void haveNewConfig(ReplSetConfig& c, bool comment); | | void haveNewConfig(ReplSetConfig& c, bool comment); | |
| | | | |
|
| /* if we delete old configs, this needs to assure locking. currentl
y we don't so it is ok. */ | | /** | |
| | | * Pointer assignment isn't necessarily atomic, so this needs to as
sure | |
| | | * locking, even though we don't delete old configs. | |
| | | */ | |
| const ReplSetConfig& getConfig() { return config(); } | | const ReplSetConfig& getConfig() { return config(); } | |
| | | | |
| bool lockedByMe() { return RSBase::lockedByMe(); } | | bool lockedByMe() { return RSBase::lockedByMe(); } | |
| | | | |
| // heartbeat msg to send to others; descriptive diagnostic info | | // heartbeat msg to send to others; descriptive diagnostic info | |
| string hbmsg() const { | | string hbmsg() const { | |
| if( time(0)-_hbmsgTime > 120 ) return ""; | | if( time(0)-_hbmsgTime > 120 ) return ""; | |
| return _hbmsg; | | return _hbmsg; | |
| } | | } | |
| }; | | }; | |
| | | | |
|
| /** base class for repl set commands. checks basic things such as in r
s mode before the command | | /** | |
| does its real work | | * Base class for repl set commands. Checks basic things such if we're
in | |
| */ | | * rs mode before the command does its real work. | |
| | | */ | |
| class ReplSetCommand : public Command { | | class ReplSetCommand : public Command { | |
| protected: | | protected: | |
| ReplSetCommand(const char * s, bool show=false) : Command(s, show)
{ } | | ReplSetCommand(const char * s, bool show=false) : Command(s, show)
{ } | |
| virtual bool slaveOk() const { return true; } | | virtual bool slaveOk() const { return true; } | |
| virtual bool adminOnly() const { return true; } | | virtual bool adminOnly() const { return true; } | |
| virtual bool logTheOp() { return false; } | | virtual bool logTheOp() { return false; } | |
| virtual LockType locktype() const { return NONE; } | | virtual LockType locktype() const { return NONE; } | |
| virtual void help( stringstream &help ) const { help << "internal";
} | | virtual void help( stringstream &help ) const { help << "internal";
} | |
|
| | | | |
| | | /** | |
| | | * Some replica set commands call this and then call check(). This
is | |
| | | * intentional, as they might do things before theReplSet is initia
lized | |
| | | * that still need to be checked for auth. | |
| | | */ | |
| | | bool checkAuth(string& errmsg, BSONObjBuilder& result) { | |
| | | if( !noauth && adminOnly() ) { | |
| | | AuthenticationInfo *ai = cc().getAuthenticationInfo(); | |
| | | if (!ai->isAuthorizedForLock("admin", locktype())) { | |
| | | errmsg = "replSet command unauthorized"; | |
| | | return false; | |
| | | } | |
| | | } | |
| | | return true; | |
| | | } | |
| | | | |
| bool check(string& errmsg, BSONObjBuilder& result) { | | bool check(string& errmsg, BSONObjBuilder& result) { | |
| if( !replSet ) { | | if( !replSet ) { | |
| errmsg = "not running with --replSet"; | | errmsg = "not running with --replSet"; | |
| return false; | | return false; | |
| } | | } | |
|
| | | | |
| if( theReplSet == 0 ) { | | if( theReplSet == 0 ) { | |
| result.append("startupStatus", ReplSet::startupStatus); | | result.append("startupStatus", ReplSet::startupStatus); | |
|
| errmsg = ReplSet::startupStatusMsg.empty() ? "replset unkno
wn error 2" : ReplSet::startupStatusMsg; | | string s; | |
| | | errmsg = ReplSet::startupStatusMsg.empty() ? "replset unkno
wn error 2" : ReplSet::startupStatusMsg.get(); | |
| if( ReplSet::startupStatus == 3 ) | | if( ReplSet::startupStatus == 3 ) | |
| result.append("info", "run rs.initiate(...) if not yet
done for the set"); | | result.append("info", "run rs.initiate(...) if not yet
done for the set"); | |
| return false; | | return false; | |
| } | | } | |
|
| return true; | | | |
| | | return checkAuth(errmsg, result); | |
| } | | } | |
| }; | | }; | |
| | | | |
|
| | | /** | |
| | | * does local authentication | |
| | | * directly authorizes against AuthenticationInfo | |
| | | */ | |
| | | void replLocalAuth(); | |
| | | | |
| /** inlines ----------------- */ | | /** inlines ----------------- */ | |
| | | | |
| inline Member::Member(HostAndPort h, unsigned ord, const ReplSetConfig:
:MemberCfg *c, bool self) : | | inline Member::Member(HostAndPort h, unsigned ord, const ReplSetConfig:
:MemberCfg *c, bool self) : | |
|
| _config(*c), _h(h), _hbinfo(ord) | | _config(*c), _h(h), _hbinfo(ord) { | |
| { | | assert(c); | |
| if( self ) | | if( self ) | |
| _hbinfo.health = 1.0; | | _hbinfo.health = 1.0; | |
| } | | } | |
| | | | |
|
| inline bool ReplSet::isPrimary() { | | | |
| /* todo replset */ | | | |
| return box.getState().primary(); | | | |
| } | | | |
| | | | |
| inline bool ReplSet::isSecondary() { | | | |
| return box.getState().secondary(); | | | |
| } | | | |
| | | | |
| } | | } | |
| | | | |
End of changes. 60 change blocks. |
| 58 lines changed or deleted | | 243 lines changed or added | |
|
| rwlock.h | | rwlock.h | |
|
| // rwlock.h | | // @file rwlock.h generic reader-writer lock (cross platform support) | |
| | | | |
| /* | | /* | |
| * Copyright (C) 2010 10gen Inc. | | * Copyright (C) 2010 10gen Inc. | |
| * | | * | |
| * This program is free software: you can redistribute it and/or modify | | * 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, | | * it under the terms of the GNU Affero General Public License, version
3, | |
| * as published by the Free Software Foundation. | | * as published by the Free Software Foundation. | |
| * | | * | |
| * This program is distributed in the hope that it will be useful, | | * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Affero General Public License for more details. | | * GNU Affero General Public License for more details. | |
| * | | * | |
| * You should have received a copy of the GNU Affero General Public Lice
nse | | * You should have received a copy of the GNU Affero General Public Lice
nse | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
| #include "mutex.h" | | #include "mutex.h" | |
|
| | | #include "../time_support.h" | |
| | | | |
|
| #if BOOST_VERSION >= 103500 | | // this requires newer windows versions | |
| #define BOOST_RWLOCK | | // it works better than sharable_mutex under high contention | |
| #else | | #if defined(_WIN64) | |
| | | //#define MONGO_USE_SRW_ON_WINDOWS 1 | |
| | | #endif | |
| | | | |
|
| #if defined(_WIN32) | | #if !defined(MONGO_USE_SRW_ON_WINDOWS) | |
| #error need boost >= 1.35 for windows | | | |
| #endif | | | |
| | | | |
|
| #include <pthread.h> | | #if BOOST_VERSION >= 103500 | |
| | | # define BOOST_RWLOCK | |
| | | #else | |
| | | # if defined(_WIN32) | |
| | | # error need boost >= 1.35 for windows | |
| | | # endif | |
| | | # include <pthread.h> | |
| | | #endif | |
| | | | |
|
| | | #if defined(_WIN32) | |
| | | # include "shared_mutex_win.hpp" | |
| | | namespace mongo { | |
| | | typedef boost::modified_shared_mutex shared_mutex; | |
| | | } | |
| | | # undef assert | |
| | | # define assert MONGO_assert | |
| | | #elif defined(BOOST_RWLOCK) | |
| | | # include <boost/thread/shared_mutex.hpp> | |
| | | # undef assert | |
| | | # define assert MONGO_assert | |
| #endif | | #endif | |
| | | | |
|
| #ifdef BOOST_RWLOCK | | | |
| #include <boost/thread/shared_mutex.hpp> | | | |
| #undef assert | | | |
| #define assert MONGO_assert | | | |
| #endif | | #endif | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| #ifdef BOOST_RWLOCK | | #if defined(MONGO_USE_SRW_ON_WINDOWS) && defined(_WIN32) | |
| class RWLock { | | | |
| boost::shared_mutex _m; | | // Windows RWLock implementation (requires newer versions of windows th
us the above macro) | |
| | | class RWLock : boost::noncopyable { | |
| public: | | public: | |
|
| #if defined(_DEBUG) | | RWLock(const char *, int lowPriorityWaitMS=0 ) : _lowPriorityWaitMS
(lowPriorityWaitMS) | |
| const char *_name; | | { InitializeSRWLock(&_lock); } | |
| RWLock(const char *name) : _name(name) { } | | ~RWLock() { } | |
| #else | | const char * implType() const { return "WINSRW"; } | |
| RWLock(const char *) { } | | int lowPriorityWaitMS() const { return _lowPriorityWaitMS; } | |
| #endif | | void lock() { AcquireSRWLockExclusive(&_lock); } | |
| void lock(){ | | void unlock() { ReleaseSRWLockExclusive(&_lock); } | |
| _m.lock(); | | void lock_shared() { AcquireSRWLockShared(&_lock); } | |
| #if defined(_DEBUG) | | void unlock_shared() { ReleaseSRWLockShared(&_lock); } | |
| mutexDebugger.entering(_name); | | bool lock_shared_try( int millis ) { | |
| #endif | | if( TryAcquireSRWLockShared(&_lock) ) | |
| | | return true; | |
| | | if( millis == 0 ) | |
| | | return false; | |
| | | unsigned long long end = curTimeMicros64() + millis*1000; | |
| | | while( 1 ) { | |
| | | Sleep(1); | |
| | | if( TryAcquireSRWLockShared(&_lock) ) | |
| | | return true; | |
| | | if( curTimeMicros64() >= end ) | |
| | | break; | |
| | | } | |
| | | return false; | |
| } | | } | |
|
| void unlock(){ | | bool lock_try( int millis = 0 ) { | |
| #if defined(_DEBUG) | | if( TryAcquireSRWLockExclusive(&_lock) ) // quick check to opti
mistically avoid calling curTimeMicros64 | |
| mutexDebugger.leaving(_name); | | return true; | |
| #endif | | if( millis == 0 ) | |
| | | return false; | |
| | | unsigned long long end = curTimeMicros64() + millis*1000; | |
| | | do { | |
| | | Sleep(1); | |
| | | if( TryAcquireSRWLockExclusive(&_lock) ) | |
| | | return true; | |
| | | } while( curTimeMicros64() < end ); | |
| | | return false; | |
| | | } | |
| | | private: | |
| | | SRWLOCK _lock; | |
| | | const int _lowPriorityWaitMS; | |
| | | }; | |
| | | | |
| | | #elif defined(BOOST_RWLOCK) | |
| | | | |
| | | // Boost based RWLock implementation | |
| | | class RWLock : boost::noncopyable { | |
| | | shared_mutex _m; | |
| | | const int _lowPriorityWaitMS; | |
| | | public: | |
| | | const char * const _name; | |
| | | | |
| | | RWLock(const char *name, int lowPriorityWait=0) : _lowPriorityWaitM
S(lowPriorityWait) , _name(name) { } | |
| | | | |
| | | const char * implType() const { return "boost"; } | |
| | | | |
| | | int lowPriorityWaitMS() const { return _lowPriorityWaitMS; } | |
| | | | |
| | | void lock() { | |
| | | _m.lock(); | |
| | | DEV mutexDebugger.entering(_name); | |
| | | } | |
| | | | |
| | | /*void lock() { | |
| | | // This sequence gives us the lock semantics we want: specifica
lly that write lock acquisition is | |
| | | // greedy EXCEPT when someone already is in upgradable state. | |
| | | lockAsUpgradable(); | |
| | | upgrade(); | |
| | | DEV mutexDebugger.entering(_name); | |
| | | }*/ | |
| | | | |
| | | void unlock() { | |
| | | DEV mutexDebugger.leaving(_name); | |
| _m.unlock(); | | _m.unlock(); | |
| } | | } | |
| | | | |
|
| void lock_shared(){ | | void lockAsUpgradable() { | |
| _m.lock_shared(); | | _m.lock_upgrade(); | |
| | | } | |
| | | void unlockFromUpgradable() { // upgradable -> unlocked | |
| | | _m.unlock_upgrade(); | |
| | | } | |
| | | void upgrade() { // upgradable -> exclusive lock | |
| | | _m.unlock_upgrade_and_lock(); | |
| } | | } | |
| | | | |
|
| void unlock_shared(){ | | void lock_shared() { | |
| | | _m.lock_shared(); | |
| | | } | |
| | | void unlock_shared() { | |
| _m.unlock_shared(); | | _m.unlock_shared(); | |
| } | | } | |
| | | | |
|
| bool lock_shared_try( int millis ){ | | bool lock_shared_try( int millis ) { | |
| boost::system_time until = get_system_time(); | | if( _m.timed_lock_shared( boost::posix_time::milliseconds(milli
s) ) ) { | |
| until += boost::posix_time::milliseconds(millis); | | | |
| if( _m.timed_lock_shared( until ) ) { | | | |
| return true; | | return true; | |
| } | | } | |
| return false; | | return false; | |
| } | | } | |
| | | | |
|
| bool lock_try( int millis = 0 ){ | | bool lock_try( int millis = 0 ) { | |
| boost::system_time until = get_system_time(); | | if( _m.timed_lock( boost::posix_time::milliseconds(millis) ) )
{ | |
| until += boost::posix_time::milliseconds(millis); | | DEV mutexDebugger.entering(_name); | |
| if( _m.timed_lock( until ) ) { | | | |
| #if defined(_DEBUG) | | | |
| mutexDebugger.entering(_name); | | | |
| #endif | | | |
| return true; | | return true; | |
| } | | } | |
| return false; | | return false; | |
| } | | } | |
|
| | | | |
| }; | | }; | |
|
| | | | |
| #else | | #else | |
|
| class RWLock { | | | |
| pthread_rwlock_t _lock; | | | |
| | | | |
|
| inline void check( int x ){ | | // Posix RWLock implementation | |
| if( x == 0 ) | | class RWLock : boost::noncopyable { | |
| | | pthread_rwlock_t _lock; | |
| | | const int _lowPriorityWaitMS; | |
| | | static void check( int x ) { | |
| | | if( MONGO_likely(x == 0) ) | |
| return; | | return; | |
| log() << "pthread rwlock failed: " << x << endl; | | log() << "pthread rwlock failed: " << x << endl; | |
| assert( x == 0 ); | | assert( x == 0 ); | |
| } | | } | |
| | | | |
| public: | | public: | |
|
| #if defined(_DEBUG) | | | |
| const char *_name; | | const char *_name; | |
|
| RWLock(const char *name) : _name(name) { | | RWLock(const char *name, int lowPriorityWaitMS=0) : _lowPriorityWai
tMS(lowPriorityWaitMS), _name(name) | |
| #else | | { | |
| RWLock(const char *) { | | | |
| #endif | | | |
| check( pthread_rwlock_init( &_lock , 0 ) ); | | check( pthread_rwlock_init( &_lock , 0 ) ); | |
| } | | } | |
| | | | |
|
| ~RWLock(){ | | ~RWLock() { | |
| if ( ! __destroyingStatics ){ | | if ( ! StaticObserver::_destroyingStatics ) { | |
| check( pthread_rwlock_destroy( &_lock ) ); | | wassert( pthread_rwlock_destroy( &_lock ) == 0 ); // wasser
t as don't want to throw from a destructor | |
| } | | } | |
| } | | } | |
| | | | |
|
| void lock(){ | | const char * implType() const { return "posix"; } | |
| | | | |
| | | int lowPriorityWaitMS() const { return _lowPriorityWaitMS; } | |
| | | | |
| | | void lock() { | |
| check( pthread_rwlock_wrlock( &_lock ) ); | | check( pthread_rwlock_wrlock( &_lock ) ); | |
|
| #if defined(_DEBUG) | | DEV mutexDebugger.entering(_name); | |
| mutexDebugger.entering(_name); | | | |
| #endif | | | |
| } | | } | |
|
| void unlock(){ | | void unlock() { | |
| #if defined(_DEBUG) | | | |
| mutexDebugger.leaving(_name); | | mutexDebugger.leaving(_name); | |
|
| #endif | | | |
| check( pthread_rwlock_unlock( &_lock ) ); | | check( pthread_rwlock_unlock( &_lock ) ); | |
| } | | } | |
| | | | |
|
| void lock_shared(){ | | void lock_shared() { | |
| check( pthread_rwlock_rdlock( &_lock ) ); | | check( pthread_rwlock_rdlock( &_lock ) ); | |
| } | | } | |
| | | | |
|
| void unlock_shared(){ | | void unlock_shared() { | |
| check( pthread_rwlock_unlock( &_lock ) ); | | check( pthread_rwlock_unlock( &_lock ) ); | |
| } | | } | |
| | | | |
|
| bool lock_shared_try( int millis ){ | | bool lock_shared_try( int millis ) { | |
| return _try( millis , false ); | | return _try( millis , false ); | |
| } | | } | |
| | | | |
|
| bool lock_try( int millis = 0 ){ | | bool lock_try( int millis = 0 ) { | |
| if( _try( millis , true ) ) { | | if( _try( millis , true ) ) { | |
|
| #if defined(_DEBUG) | | DEV mutexDebugger.entering(_name); | |
| mutexDebugger.entering(_name); | | | |
| #endif | | | |
| return true; | | return true; | |
| } | | } | |
| return false; | | return false; | |
| } | | } | |
| | | | |
|
| bool _try( int millis , bool write ){ | | bool _try( int millis , bool write ) { | |
| while ( true ) { | | while ( true ) { | |
| int x = write ? | | int x = write ? | |
|
| pthread_rwlock_trywrlock( &_lock ) : | | pthread_rwlock_trywrlock( &_lock ) : | |
| pthread_rwlock_tryrdlock( &_lock ); | | pthread_rwlock_tryrdlock( &_lock ); | |
| | | | |
| if ( x <= 0 ) { | | if ( x <= 0 ) { | |
| return true; | | return true; | |
| } | | } | |
| | | | |
| if ( millis-- <= 0 ) | | if ( millis-- <= 0 ) | |
| return false; | | return false; | |
| | | | |
|
| if ( x == EBUSY ){ | | if ( x == EBUSY ) { | |
| sleepmillis(1); | | sleepmillis(1); | |
| continue; | | continue; | |
| } | | } | |
| check(x); | | check(x); | |
| } | | } | |
| | | | |
| return false; | | return false; | |
| } | | } | |
| | | | |
| }; | | }; | |
| | | | |
| #endif | | #endif | |
| | | | |
|
| class rwlock_try_write { | | /** throws on failure to acquire in the specified time period. */ | |
| RWLock& _l; | | class rwlock_try_write : boost::noncopyable { | |
| public: | | public: | |
| struct exception { }; | | struct exception { }; | |
| rwlock_try_write(RWLock& l, int millis = 0) : _l(l) { | | rwlock_try_write(RWLock& l, int millis = 0) : _l(l) { | |
|
| if( !l.lock_try(millis) ) throw exception(); | | if( !l.lock_try(millis) ) | |
| | | throw exception(); | |
| } | | } | |
| ~rwlock_try_write() { _l.unlock(); } | | ~rwlock_try_write() { _l.unlock(); } | |
|
| | | private: | |
| | | RWLock& _l; | |
| }; | | }; | |
| | | | |
|
| /* scoped lock */ | | class rwlock_shared : boost::noncopyable { | |
| struct rwlock { | | public: | |
| rwlock( const RWLock& lock , bool write , bool alreadyHaveLock = fa
lse ) | | rwlock_shared(RWLock& rwlock) : _r(rwlock) {_r.lock_shared(); } | |
| : _lock( (RWLock&)lock ) , _write( write ){ | | ~rwlock_shared() { _r.unlock_shared(); } | |
| | | private: | |
| | | RWLock& _r; | |
| | | }; | |
| | | | |
|
| if ( ! alreadyHaveLock ){ | | /* scoped lock for RWLock */ | |
| if ( _write ) | | class rwlock : boost::noncopyable { | |
| _lock.lock(); | | public: | |
| else | | /** | |
| | | * @param write acquire write lock if true sharable if false | |
| | | * @param lowPriority if > 0, will try to get the lock non-greedily
for that many ms | |
| | | */ | |
| | | rwlock( const RWLock& lock , bool write, /* bool alreadyHaveLock =
false , */int lowPriorityWaitMS = 0 ) | |
| | | : _lock( (RWLock&)lock ) , _write( write ) { | |
| | | | |
| | | { | |
| | | if ( _write ) { | |
| | | | |
| | | if ( ! lowPriorityWaitMS && lock.lowPriorityWaitMS() ) | |
| | | lowPriorityWaitMS = lock.lowPriorityWaitMS(); | |
| | | | |
| | | if ( lowPriorityWaitMS ) { | |
| | | bool got = false; | |
| | | for ( int i=0; i<lowPriorityWaitMS; i++ ) { | |
| | | if ( _lock.lock_try(0) ) { | |
| | | got = true; | |
| | | break; | |
| | | } | |
| | | | |
| | | int sleep = 1; | |
| | | if ( i > ( lowPriorityWaitMS / 20 ) ) | |
| | | sleep = 10; | |
| | | sleepmillis(sleep); | |
| | | i += ( sleep - 1 ); | |
| | | } | |
| | | if ( ! got ) { | |
| | | log() << "couldn't get lazy rwlock" << endl; | |
| | | _lock.lock(); | |
| | | } | |
| | | } | |
| | | else { | |
| | | _lock.lock(); | |
| | | } | |
| | | | |
| | | } | |
| | | else { | |
| _lock.lock_shared(); | | _lock.lock_shared(); | |
|
| | | } | |
| } | | } | |
| } | | } | |
|
| | | ~rwlock() { | |
| ~rwlock(){ | | | |
| if ( _write ) | | if ( _write ) | |
| _lock.unlock(); | | _lock.unlock(); | |
| else | | else | |
| _lock.unlock_shared(); | | _lock.unlock_shared(); | |
| } | | } | |
|
| | | private: | |
| RWLock& _lock; | | RWLock& _lock; | |
|
| bool _write; | | const bool _write; | |
| | | }; | |
| | | | |
| | | /** recursive on shared locks is ok for this implementation */ | |
| | | class RWLockRecursive : boost::noncopyable { | |
| | | ThreadLocalValue<int> _state; | |
| | | RWLock _lk; | |
| | | friend class Exclusive; | |
| | | public: | |
| | | /** @param lpwaitms lazy wait */ | |
| | | RWLockRecursive(const char *name, int lpwaitms) : _lk(name, lpwaitm
s) { } | |
| | | | |
| | | void assertExclusivelyLocked() { | |
| | | dassert( _state.get() < 0 ); | |
| | | } | |
| | | | |
| | | // RWLockRecursive::Exclusive scoped lock | |
| | | class Exclusive : boost::noncopyable { | |
| | | RWLockRecursive& _r; | |
| | | rwlock *_scopedLock; | |
| | | public: | |
| | | Exclusive(RWLockRecursive& r) : _r(r), _scopedLock(0) { | |
| | | int s = _r._state.get(); | |
| | | dassert( s <= 0 ); | |
| | | if( s == 0 ) | |
| | | _scopedLock = new rwlock(_r._lk, true); | |
| | | _r._state.set(s-1); | |
| | | } | |
| | | ~Exclusive() { | |
| | | int s = _r._state.get(); | |
| | | DEV wassert( s < 0 ); // wassert: don't throw from destruct
ors | |
| | | _r._state.set(s+1); | |
| | | delete _scopedLock; | |
| | | } | |
| | | }; | |
| | | | |
| | | // RWLockRecursive::Shared scoped lock | |
| | | class Shared : boost::noncopyable { | |
| | | RWLockRecursive& _r; | |
| | | bool _alreadyExclusive; | |
| | | public: | |
| | | Shared(RWLockRecursive& r) : _r(r) { | |
| | | int s = _r._state.get(); | |
| | | _alreadyExclusive = s < 0; | |
| | | if( !_alreadyExclusive ) { | |
| | | dassert( s >= 0 ); // -1 would mean exclusive | |
| | | if( s == 0 ) | |
| | | _r._lk.lock_shared(); | |
| | | _r._state.set(s+1); | |
| | | } | |
| | | } | |
| | | ~Shared() { | |
| | | if( _alreadyExclusive ) { | |
| | | DEV wassert( _r._state.get() < 0 ); | |
| | | } | |
| | | else { | |
| | | int s = _r._state.get() - 1; | |
| | | if( s == 0 ) | |
| | | _r._lk.unlock_shared(); | |
| | | _r._state.set(s); | |
| | | DEV wassert( s >= 0 ); | |
| | | } | |
| | | } | |
| | | }; | |
| }; | | }; | |
| } | | } | |
| | | | |
End of changes. 42 change blocks. |
| 90 lines changed or deleted | | 268 lines changed or added | |
|
| sock.h | | sock.h | |
|
| // sock.h | | // @file sock.h | |
| | | | |
| /* Copyright 2009 10gen Inc. | | /* Copyright 2009 10gen Inc. | |
| * | | * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | | * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | | * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | | * You may obtain a copy of the License at | |
| * | | * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | | * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | | * | |
| * Unless required by applicable law or agreed to in writing, software | | * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | | * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli
ed. | |
| * See the License for the specific language governing permissions and | | * See the License for the specific language governing permissions and | |
| * limitations under the License. | | * limitations under the License. | |
| */ | | */ | |
| | | | |
| #pragma once | | #pragma once | |
| | | | |
|
| #include "../pch.h" | | #include "../../pch.h" | |
| | | | |
| #include <stdio.h> | | #include <stdio.h> | |
| #include <sstream> | | #include <sstream> | |
|
| #include "goodies.h" | | #include "../goodies.h" | |
| #include "../db/jsobj.h" | | #include "../../db/cmdline.h" | |
| | | #include "../mongoutils/str.h" | |
| namespace mongo { | | | |
| | | | |
| const int SOCK_FAMILY_UNKNOWN_ERROR=13078; | | | |
| | | | |
| #if defined(_WIN32) | | | |
| | | | |
| typedef short sa_family_t; | | | |
| typedef int socklen_t; | | | |
| inline int getLastError() { | | | |
| return WSAGetLastError(); | | | |
| } | | | |
| inline const char* gai_strerror(int code) { | | | |
| return ::gai_strerrorA(code); | | | |
| } | | | |
| inline void disableNagle(int sock) { | | | |
| int x = 1; | | | |
| if ( setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *) &x, sizeof
(x)) ) | | | |
| out() << "ERROR: disableNagle failed" << endl; | | | |
| if ( setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &x, sizeof
(x)) ) | | | |
| out() << "ERROR: SO_KEEPALIVE failed" << endl; | | | |
| } | | | |
| inline void prebindOptions( int sock ) { | | | |
| } | | | |
| | | | |
| // This won't actually be used on windows | | | |
| struct sockaddr_un { | | | |
| short sun_family; | | | |
| char sun_path[108]; // length from unix header | | | |
| }; | | | |
| | | | |
| #else | | | |
| | | | |
|
| } // namespace mongo | | #ifndef _WIN32 | |
| | | | |
| #include <sys/socket.h> | | #include <sys/socket.h> | |
| #include <sys/types.h> | | #include <sys/types.h> | |
| #include <sys/socket.h> | | #include <sys/socket.h> | |
| #include <sys/un.h> | | #include <sys/un.h> | |
|
| #include <netinet/in.h> | | | |
| #include <netinet/tcp.h> | | | |
| #include <arpa/inet.h> | | | |
| #include <errno.h> | | #include <errno.h> | |
|
| #include <netdb.h> | | | |
| #ifdef __openbsd__ | | #ifdef __openbsd__ | |
| # include <sys/uio.h> | | # include <sys/uio.h> | |
| #endif | | #endif | |
| | | | |
|
| #ifndef AI_ADDRCONFIG | | #endif // _WIN32 | |
| # define AI_ADDRCONFIG 0 | | | |
| | | #ifdef MONGO_SSL | |
| | | #include <openssl/ssl.h> | |
| #endif | | #endif | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| inline void closesocket(int s) { | | const int SOCK_FAMILY_UNKNOWN_ERROR=13078; | |
| close(s); | | | |
| } | | | |
| const int INVALID_SOCKET = -1; | | | |
| typedef int SOCKET; | | | |
| | | | |
|
| inline void disableNagle(int sock) { | | void disableNagle(int sock); | |
| int x = 1; | | | |
| | | | |
|
| #ifdef SOL_TCP | | #if defined(_WIN32) | |
| int level = SOL_TCP; | | | |
| #else | | | |
| int level = SOL_SOCKET; | | | |
| #endif | | | |
| | | | |
|
| if ( setsockopt(sock, level, TCP_NODELAY, (char *) &x, sizeof(x)) ) | | typedef short sa_family_t; | |
| log() << "ERROR: disableNagle failed: " << errnoWithDescription
() << endl; | | typedef int socklen_t; | |
| | | | |
|
| #ifdef SO_KEEPALIVE | | // This won't actually be used on windows | |
| if ( setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &x, sizeof
(x)) ) | | struct sockaddr_un { | |
| log() << "ERROR: SO_KEEPALIVE failed: " << errnoWithDescription
() << endl; | | short sun_family; | |
| #endif | | char sun_path[108]; // length from unix header | |
| | | }; | |
| | | | |
|
| } | | #else // _WIN32 | |
| inline void prebindOptions( int sock ) { | | | |
| DEV log() << "doing prebind option" << endl; | | | |
| int x = 1; | | | |
| if ( setsockopt( sock , SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)) <
0 ) | | | |
| out() << "Failed to set socket opt, SO_REUSEADDR" << endl; | | | |
| } | | | |
| | | | |
|
| #endif | | inline void closesocket(int s) { close(s); } | |
| | | const int INVALID_SOCKET = -1; | |
| | | typedef int SOCKET; | |
| | | | |
|
| inline string makeUnixSockPath(int port){ | | #endif // _WIN32 | |
| return "/tmp/mongodb-" + BSONObjBuilder::numStr(port) + ".sock"; | | | |
| } | | | |
| | | | |
|
| inline void setSockTimeouts(int sock, int secs) { | | inline string makeUnixSockPath(int port) { | |
| struct timeval tv; | | return mongoutils::str::stream() << cmdLine.socket << "/mongodb-" <
< port << ".sock"; | |
| tv.tv_sec = secs; | | | |
| tv.tv_usec = 0; | | | |
| bool report = logLevel > 3; // solaris doesn't provide these | | | |
| DEV report = true; | | | |
| bool ok = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, s
izeof(tv) ) == 0; | | | |
| if( report && !ok ) log() << "unabled to set SO_RCVTIMEO" << endl; | | | |
| ok = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *) &tv, sizeof
(tv) ) == 0; | | | |
| DEV if( report && !ok ) log() << "unabled to set SO_RCVTIMEO" << en
dl; | | | |
| } | | } | |
| | | | |
| // If an ip address is passed in, just return that. If a hostname is p
assed | | // If an ip address is passed in, just return that. If a hostname is p
assed | |
| // in, look up its ip and return that. Returns "" on failure. | | // in, look up its ip and return that. Returns "" on failure. | |
| string hostbyname(const char *hostname); | | string hostbyname(const char *hostname); | |
| | | | |
| void enableIPv6(bool state=true); | | void enableIPv6(bool state=true); | |
| bool IPv6Enabled(); | | bool IPv6Enabled(); | |
|
| | | void setSockTimeouts(int sock, double secs); | |
| | | | |
|
| | | /** | |
| | | * wrapped around os representation of network address | |
| | | */ | |
| struct SockAddr { | | struct SockAddr { | |
| SockAddr() { | | SockAddr() { | |
| addressSize = sizeof(sa); | | addressSize = sizeof(sa); | |
| memset(&sa, 0, sizeof(sa)); | | memset(&sa, 0, sizeof(sa)); | |
| sa.ss_family = AF_UNSPEC; | | sa.ss_family = AF_UNSPEC; | |
| } | | } | |
| SockAddr(int sourcePort); /* listener side */ | | SockAddr(int sourcePort); /* listener side */ | |
| SockAddr(const char *ip, int port); /* EndPoint (remote) side, or i
f you want to specify which interface locally */ | | SockAddr(const char *ip, int port); /* EndPoint (remote) side, or i
f you want to specify which interface locally */ | |
| | | | |
|
| template <typename T> | | template <typename T> T& as() { return *(T*)(&sa); } | |
| T& as() { return *(T*)(&sa); } | | template <typename T> const T& as() const { return *(const T*)(&sa)
; } | |
| template <typename T> | | | |
| const T& as() const { return *(const T*)(&sa); } | | | |
| | | | |
| string toString(bool includePort=true) const{ | | | |
| string out = getAddr(); | | | |
| if (includePort && getType() != AF_UNIX && getType() != AF_UNSP
EC) | | | |
| out += ':' + BSONObjBuilder::numStr(getPort()); | | | |
| return out; | | | |
| } | | | |
| | | | |
|
| // returns one of AF_INET, AF_INET6, or AF_UNIX | | string toString(bool includePort=true) const; | |
| sa_family_t getType() const { | | | |
| return sa.ss_family; | | | |
| } | | | |
| | | | |
|
| unsigned getPort() const { | | /** | |
| switch (getType()){ | | * @return one of AF_INET, AF_INET6, or AF_UNIX | |
| case AF_INET: return ntohs(as<sockaddr_in>().sin_port); | | */ | |
| case AF_INET6: return ntohs(as<sockaddr_in6>().sin6_port); | | sa_family_t getType() const; | |
| case AF_UNIX: return 0; | | | |
| case AF_UNSPEC: return 0; | | | |
| default: massert(SOCK_FAMILY_UNKNOWN_ERROR, "unsupported ad
dress family", false); return 0; | | | |
| } | | | |
| } | | | |
| | | | |
|
| string getAddr() const { | | unsigned getPort() const; | |
| switch (getType()){ | | | |
| case AF_INET: | | | |
| case AF_INET6: { | | | |
| const int buflen=128; | | | |
| char buffer[buflen]; | | | |
| int ret = getnameinfo(raw(), addressSize, buffer, bufle
n, NULL, 0, NI_NUMERICHOST); | | | |
| massert(13082, gai_strerror(ret), ret == 0); | | | |
| return buffer; | | | |
| } | | | |
| | | | |
|
| case AF_UNIX: return (addressSize > 2 ? as<sockaddr_un>().
sun_path : "anonymous unix socket"); | | string getAddr() const; | |
| case AF_UNSPEC: return "(NONE)"; | | | |
| default: massert(SOCK_FAMILY_UNKNOWN_ERROR, "unsupported ad
dress family", false); return ""; | | | |
| } | | | |
| } | | | |
| | | | |
| bool isLocalHost() const; | | bool isLocalHost() const; | |
| | | | |
|
| bool operator==(const SockAddr& r) const { | | bool operator==(const SockAddr& r) const; | |
| if (getType() != r.getType()) | | | |
| return false; | | | |
| | | | |
| if (getPort() != r.getPort()) | | | |
| return false; | | | |
| | | | |
| switch (getType()){ | | | |
| case AF_INET: return as<sockaddr_in>().sin_addr.s_addr ==
r.as<sockaddr_in>().sin_addr.s_addr; | | | |
| case AF_INET6: return memcmp(as<sockaddr_in6>().sin6_addr.s
6_addr, r.as<sockaddr_in6>().sin6_addr.s6_addr, sizeof(in6_addr)) == 0; | | | |
| case AF_UNIX: return strcmp(as<sockaddr_un>().sun_path, r.
as<sockaddr_un>().sun_path) == 0; | | | |
| case AF_UNSPEC: return true; // assume all unspecified addr
esses are the same | | | |
| default: massert(SOCK_FAMILY_UNKNOWN_ERROR, "unsupported ad
dress family", false); | | | |
| } | | | |
| } | | | |
| bool operator!=(const SockAddr& r) const { | | | |
| return !(*this == r); | | | |
| } | | | |
| bool operator<(const SockAddr& r) const { | | | |
| if (getType() < r.getType()) | | | |
| return true; | | | |
| else if (getType() > r.getType()) | | | |
| return false; | | | |
| | | | |
|
| if (getPort() < r.getPort()) | | bool operator!=(const SockAddr& r) const; | |
| return true; | | | |
| else if (getPort() > r.getPort()) | | | |
| return false; | | | |
| | | | |
|
| switch (getType()){ | | bool operator<(const SockAddr& r) const; | |
| case AF_INET: return as<sockaddr_in>().sin_addr.s_addr < r
.as<sockaddr_in>().sin_addr.s_addr; | | | |
| case AF_INET6: return memcmp(as<sockaddr_in6>().sin6_addr.s
6_addr, r.as<sockaddr_in6>().sin6_addr.s6_addr, sizeof(in6_addr)) < 0; | | | |
| case AF_UNIX: return strcmp(as<sockaddr_un>().sun_path, r.
as<sockaddr_un>().sun_path) < 0; | | | |
| case AF_UNSPEC: return false; | | | |
| default: massert(SOCK_FAMILY_UNKNOWN_ERROR, "unsupported ad
dress family", false); | | | |
| } | | | |
| } | | | |
| | | | |
| const sockaddr* raw() const {return (sockaddr*)&sa;} | | const sockaddr* raw() const {return (sockaddr*)&sa;} | |
| sockaddr* raw() {return (sockaddr*)&sa;} | | sockaddr* raw() {return (sockaddr*)&sa;} | |
| | | | |
| socklen_t addressSize; | | socklen_t addressSize; | |
|
| private: | | private: | |
| struct sockaddr_storage sa; | | struct sockaddr_storage sa; | |
| }; | | }; | |
| | | | |
| extern SockAddr unknownAddress; // ( "0.0.0.0", 0 ) | | extern SockAddr unknownAddress; // ( "0.0.0.0", 0 ) | |
| | | | |
|
| const int MaxMTU = 16384; | | /** this is not cache and does a syscall */ | |
| | | string getHostName(); | |
| inline string getHostName() { | | | |
| char buf[256]; | | | |
| int ec = gethostname(buf, 127); | | | |
| if ( ec || *buf == 0 ) { | | | |
| log() << "can't get this server's hostname " << errnoWithDescri
ption() << endl; | | | |
| return ""; | | | |
| } | | | |
| return buf; | | | |
| } | | | |
| | | | |
|
| | | /** this is cached, so if changes during the process lifetime | |
| | | * will be stale */ | |
| string getHostNameCached(); | | string getHostNameCached(); | |
| | | | |
|
| class ListeningSockets { | | /** | |
| | | * thrown by Socket and SockAddr | |
| | | */ | |
| | | class SocketException : public DBException { | |
| public: | | public: | |
|
| ListeningSockets() : _mutex("ListeningSockets"), _sockets( new set<
int>() ) { } | | const enum Type { CLOSED , RECV_ERROR , SEND_ERROR, RECV_TIMEOUT, S
END_TIMEOUT, FAILED_STATE, CONNECT_ERROR } _type; | |
| void add( int sock ){ | | | |
| scoped_lock lk( _mutex ); | | SocketException( Type t , string server , int code = 9001 , string
extra="" ) | |
| _sockets->insert( sock ); | | : DBException( "socket exception" , code ) , _type(t) , _server
(server), _extra(extra){ } | |
| } | | virtual ~SocketException() throw() {} | |
| void remove( int sock ){ | | | |
| scoped_lock lk( _mutex ); | | bool shouldPrint() const { return _type != CLOSED; } | |
| _sockets->erase( sock ); | | virtual string toString() const; | |
| } | | | |
| void closeAll(){ | | | |
| set<int>* s; | | | |
| { | | | |
| scoped_lock lk( _mutex ); | | | |
| s = _sockets; | | | |
| _sockets = new set<int>(); | | | |
| } | | | |
| for ( set<int>::iterator i=s->begin(); i!=s->end(); i++ ) { | | | |
| int sock = *i; | | | |
| log() << "closing listening socket: " << sock << endl; | | | |
| closesocket( sock ); | | | |
| } | | | |
| } | | | |
| static ListeningSockets* get(); | | | |
| private: | | private: | |
|
| mongo::mutex _mutex; | | string _server; | |
| set<int>* _sockets; | | string _extra; | |
| static ListeningSockets* _instance; | | }; | |
| | | | |
| | | #ifdef MONGO_SSL | |
| | | class SSLManager : boost::noncopyable { | |
| | | public: | |
| | | SSLManager( bool client ); | |
| | | | |
| | | void setupPEM( const string& keyFile , const string& password ); | |
| | | void setupPubPriv( const string& privateKeyFile , const string& pub
licKeyFile ); | |
| | | | |
| | | /** | |
| | | * creates an SSL context to be used for this file descriptor | |
| | | * caller should delete | |
| | | */ | |
| | | SSL * secure( int fd ); | |
| | | | |
| | | static int password_cb( char *buf,int num, int rwflag,void *userdat
a ); | |
| | | | |
| | | private: | |
| | | bool _client; | |
| | | SSL_CTX* _context; | |
| | | string _password; | |
| | | }; | |
| | | #endif | |
| | | | |
| | | /** | |
| | | * thin wrapped around file descriptor and system calls | |
| | | * todo: ssl | |
| | | */ | |
| | | class Socket { | |
| | | public: | |
| | | Socket(int sock, const SockAddr& farEnd); | |
| | | | |
| | | /** In some cases the timeout will actually be 2x this value - eg w
e do a partial send, | |
| | | then the timeout fires, then we try to send again, then the tim
eout fires again with | |
| | | no data sent, then we detect that the other side is down. | |
| | | | |
| | | Generally you don't want a timeout, you should be very prepared
for errors if you set one. | |
| | | */ | |
| | | Socket(double so_timeout = 0, int logLevel = 0 ); | |
| | | | |
| | | bool connect(SockAddr& farEnd); | |
| | | void close(); | |
| | | | |
| | | void send( const char * data , int len, const char *context ); | |
| | | void send( const vector< pair< char *, int > > &data, const char *c
ontext ); | |
| | | | |
| | | // recv len or throw SocketException | |
| | | void recv( char * data , int len ); | |
| | | int unsafe_recv( char *buf, int max ); | |
| | | | |
| | | int getLogLevel() const { return _logLevel; } | |
| | | void setLogLevel( int ll ) { _logLevel = ll; } | |
| | | | |
| | | SockAddr remoteAddr() const { return _remote; } | |
| | | string remoteString() const { return _remote.toString(); } | |
| | | unsigned remotePort() const { return _remote.getPort(); } | |
| | | | |
| | | void clearCounters() { _bytesIn = 0; _bytesOut = 0; } | |
| | | long long getBytesIn() const { return _bytesIn; } | |
| | | long long getBytesOut() const { return _bytesOut; } | |
| | | | |
| | | void setTimeout( double secs ); | |
| | | | |
| | | #ifdef MONGO_SSL | |
| | | /** secures inline */ | |
| | | void secure( SSLManager * ssl ); | |
| | | | |
| | | void secureAccepted( SSLManager * ssl ); | |
| | | #endif | |
| | | | |
| | | /** | |
| | | * call this after a fork for server sockets | |
| | | */ | |
| | | void postFork(); | |
| | | | |
| | | private: | |
| | | void _init(); | |
| | | /** raw send, same semantics as ::send */ | |
| | | int _send( const char * data , int len ); | |
| | | | |
| | | /** sends dumbly, just each buffer at a time */ | |
| | | void _send( const vector< pair< char *, int > > &data, const char *
context ); | |
| | | | |
| | | /** raw recv, same semantics as ::recv */ | |
| | | int _recv( char * buf , int max ); | |
| | | | |
| | | int _fd; | |
| | | SockAddr _remote; | |
| | | double _timeout; | |
| | | | |
| | | long long _bytesIn; | |
| | | long long _bytesOut; | |
| | | | |
| | | #ifdef MONGO_SSL | |
| | | shared_ptr<SSL> _ssl; | |
| | | SSLManager * _sslAccepted; | |
| | | #endif | |
| | | | |
| | | protected: | |
| | | int _logLevel; // passed to log() when logging errors | |
| | | | |
| }; | | }; | |
| | | | |
| } // namespace mongo | | } // namespace mongo | |
| | | | |
End of changes. 32 change blocks. |
| 195 lines changed or deleted | | 166 lines changed or added | |
|
| update.h | | update.h | |
| | | | |
| skipping to change at line 19 | | skipping to change at line 19 | |
| * | | * | |
| * This program is distributed in the hope that it will be useful, | | * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Affero General Public License for more details. | | * GNU Affero General Public License for more details. | |
| * | | * | |
| * You should have received a copy of the GNU Affero General Public Lice
nse | | * You should have received a copy of the GNU Affero General Public Lice
nse | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | | */ | |
| | | | |
|
| #include "../pch.h" | | #include "../../pch.h" | |
| #include "jsobj.h" | | #include "../jsobj.h" | |
| #include "../util/embedded_builder.h" | | #include "../../util/embedded_builder.h" | |
| #include "matcher.h" | | #include "../matcher.h" | |
| | | | |
| namespace mongo { | | namespace mongo { | |
| | | | |
|
| | | // ---------- public ------------- | |
| | | | |
| | | struct UpdateResult { | |
| | | bool existing; // if existing objects were modified | |
| | | bool mod; // was this a $ mod | |
| | | long long num; // how many objects touched | |
| | | OID upserted; // if something was upserted, the new _id of the obj
ect | |
| | | | |
| | | UpdateResult( bool e, bool m, unsigned long long n , const BSONObj&
upsertedObject = BSONObj() ) | |
| | | : existing(e) , mod(m), num(n) { | |
| | | upserted.clear(); | |
| | | | |
| | | BSONElement id = upsertedObject["_id"]; | |
| | | if ( ! e && n == 1 && id.type() == jstOID ) { | |
| | | upserted = id.OID(); | |
| | | } | |
| | | } | |
| | | | |
| | | }; | |
| | | | |
| | | class RemoveSaver; | |
| | | | |
| | | /* returns true if an existing object was updated, false if no existing
object was found. | |
| | | multi - update multiple objects - mostly useful with things like $se
t | |
| | | god - allow access to system namespaces | |
| | | */ | |
| | | UpdateResult updateObjects(const char *ns, const BSONObj& updateobj, BS
ONObj pattern, bool upsert, bool multi , bool logop , OpDebug& debug ); | |
| | | UpdateResult _updateObjects(bool god, const char *ns, const BSONObj& up
dateobj, BSONObj pattern, | |
| | | bool upsert, bool multi , bool logop , OpDe
bug& debug , RemoveSaver * rs = 0 ); | |
| | | | |
| | | // ---------- private ------------- | |
| | | | |
| class ModState; | | class ModState; | |
| class ModSetState; | | class ModSetState; | |
| | | | |
| /* Used for modifiers such as $inc, $set, $push, ... | | /* Used for modifiers such as $inc, $set, $push, ... | |
| * stores the info about a single operation | | * stores the info about a single operation | |
| * once created should never be modified | | * once created should never be modified | |
| */ | | */ | |
| struct Mod { | | struct Mod { | |
| // See opFromStr below | | // See opFromStr below | |
|
| // 0 1 2 3 4 5 6 7 8
9 10 11 | | // 0 1 2 3 4 5 6 7 8
9 10 11 12 13 | |
| enum Op { INC, SET, PUSH, PUSH_ALL, PULL, PULL_ALL , POP, UNSET, BI
TAND, BITOR , BIT , ADDTOSET } op; | | enum Op { INC, SET, PUSH, PUSH_ALL, PULL, PULL_ALL , POP, UNSET, BI
TAND, BITOR , BIT , ADDTOSET, RENAME_FROM, RENAME_TO } op; | |
| | | | |
| static const char* modNames[]; | | static const char* modNames[]; | |
| static unsigned modNamesNum; | | static unsigned modNamesNum; | |
| | | | |
| const char *fieldName; | | const char *fieldName; | |
| const char *shortFieldName; | | const char *shortFieldName; | |
| | | | |
| BSONElement elt; // x:5 note: this is the actual element from the u
pdateobj | | BSONElement elt; // x:5 note: this is the actual element from the u
pdateobj | |
| boost::shared_ptr<Matcher> matcher; | | boost::shared_ptr<Matcher> matcher; | |
|
| | | bool matcherOnPrimitive; | |
| | | | |
|
| void init( Op o , BSONElement& e ){ | | void init( Op o , BSONElement& e ) { | |
| op = o; | | op = o; | |
| elt = e; | | elt = e; | |
|
| if ( op == PULL && e.type() == Object ) | | if ( op == PULL && e.type() == Object ) { | |
| matcher.reset( new Matcher( e.embeddedObject() ) ); | | BSONObj t = e.embeddedObject(); | |
| | | if ( t.firstElement().getGtLtOp() == 0 ) { | |
| | | matcher.reset( new Matcher( t ) ); | |
| | | matcherOnPrimitive = false; | |
| | | } | |
| | | else { | |
| | | matcher.reset( new Matcher( BSON( "" << t ) ) ); | |
| | | matcherOnPrimitive = true; | |
| | | } | |
| | | } | |
| } | | } | |
| | | | |
|
| void setFieldName( const char * s ){ | | void setFieldName( const char * s ) { | |
| fieldName = s; | | fieldName = s; | |
| shortFieldName = strrchr( fieldName , '.' ); | | shortFieldName = strrchr( fieldName , '.' ); | |
| if ( shortFieldName ) | | if ( shortFieldName ) | |
| shortFieldName++; | | shortFieldName++; | |
| else | | else | |
| shortFieldName = fieldName; | | shortFieldName = fieldName; | |
| } | | } | |
| | | | |
| /** | | /** | |
| * @param in incrememnts the actual value inside in | | * @param in incrememnts the actual value inside in | |
| */ | | */ | |
| void incrementMe( BSONElement& in ) const { | | void incrementMe( BSONElement& in ) const { | |
| BSONElementManipulator manip( in ); | | BSONElementManipulator manip( in ); | |
|
| | | switch ( in.type() ) { | |
| switch ( in.type() ){ | | | |
| case NumberDouble: | | case NumberDouble: | |
| manip.setNumber( elt.numberDouble() + in.numberDouble() ); | | manip.setNumber( elt.numberDouble() + in.numberDouble() ); | |
| break; | | break; | |
| case NumberLong: | | case NumberLong: | |
| manip.setLong( elt.numberLong() + in.numberLong() ); | | manip.setLong( elt.numberLong() + in.numberLong() ); | |
| break; | | break; | |
| case NumberInt: | | case NumberInt: | |
| manip.setInt( elt.numberInt() + in.numberInt() ); | | manip.setInt( elt.numberInt() + in.numberInt() ); | |
| break; | | break; | |
| default: | | default: | |
| assert(0); | | assert(0); | |
| } | | } | |
|
| | | } | |
| | | void IncrementMe( BSONElement& in ) const { | |
| | | BSONElementManipulator manip( in ); | |
| | | switch ( in.type() ) { | |
| | | case NumberDouble: | |
| | | manip.SetNumber( elt.numberDouble() + in.numberDouble() ); | |
| | | break; | |
| | | case NumberLong: | |
| | | manip.SetLong( elt.numberLong() + in.numberLong() ); | |
| | | break; | |
| | | case NumberInt: | |
| | | manip.SetInt( elt.numberInt() + in.numberInt() ); | |
| | | break; | |
| | | default: | |
| | | assert(0); | |
| | | } | |
| } | | } | |
| | | | |
| template< class Builder > | | template< class Builder > | |
| void appendIncremented( Builder& bb , const BSONElement& in, ModSta
te& ms ) const; | | void appendIncremented( Builder& bb , const BSONElement& in, ModSta
te& ms ) const; | |
| | | | |
| bool operator<( const Mod &other ) const { | | bool operator<( const Mod &other ) const { | |
| return strcmp( fieldName, other.fieldName ) < 0; | | return strcmp( fieldName, other.fieldName ) < 0; | |
| } | | } | |
| | | | |
| bool arrayDep() const { | | bool arrayDep() const { | |
|
| switch (op){ | | switch (op) { | |
| case PUSH: | | case PUSH: | |
| case PUSH_ALL: | | case PUSH_ALL: | |
| case POP: | | case POP: | |
| return true; | | return true; | |
| default: | | default: | |
| return false; | | return false; | |
| } | | } | |
| } | | } | |
| | | | |
|
| static bool isIndexed( const string& fullName , const set<string>&
idxKeys ){ | | static bool isIndexed( const string& fullName , const set<string>&
idxKeys ) { | |
| const char * fieldName = fullName.c_str(); | | const char * fieldName = fullName.c_str(); | |
| // check if there is an index key that is a parent of mod | | // check if there is an index key that is a parent of mod | |
| for( const char *dot = strchr( fieldName, '.' ); dot; dot = str
chr( dot + 1, '.' ) ) | | for( const char *dot = strchr( fieldName, '.' ); dot; dot = str
chr( dot + 1, '.' ) ) | |
| if ( idxKeys.count( string( fieldName, dot - fieldName ) )
) | | if ( idxKeys.count( string( fieldName, dot - fieldName ) )
) | |
| return true; | | return true; | |
| | | | |
| // check if there is an index key equal to mod | | // check if there is an index key equal to mod | |
| if ( idxKeys.count(fullName) ) | | if ( idxKeys.count(fullName) ) | |
| return true; | | return true; | |
| // check if there is an index key that is a child of mod | | // check if there is an index key that is a child of mod | |
| | | | |
| skipping to change at line 127 | | skipping to change at line 183 | |
| | | | |
| return false; | | return false; | |
| } | | } | |
| | | | |
| bool isIndexed( const set<string>& idxKeys ) const { | | bool isIndexed( const set<string>& idxKeys ) const { | |
| string fullName = fieldName; | | string fullName = fieldName; | |
| | | | |
| if ( isIndexed( fullName , idxKeys ) ) | | if ( isIndexed( fullName , idxKeys ) ) | |
| return true; | | return true; | |
| | | | |
|
| if ( strstr( fieldName , "." ) ){ | | if ( strstr( fieldName , "." ) ) { | |
| // check for a.0.1 | | // check for a.0.1 | |
| StringBuilder buf( fullName.size() + 1 ); | | StringBuilder buf( fullName.size() + 1 ); | |
|
| for ( size_t i=0; i<fullName.size(); i++ ){ | | for ( size_t i=0; i<fullName.size(); i++ ) { | |
| char c = fullName[i]; | | char c = fullName[i]; | |
| | | | |
| if ( c == '$' && | | if ( c == '$' && | |
|
| i > 0 && fullName[i-1] == '.' && | | i > 0 && fullName[i-1] == '.' && | |
| i+1<fullName.size() && | | i+1<fullName.size() && | |
| fullName[i+1] == '.' ){ | | fullName[i+1] == '.' ) { | |
| i++; | | i++; | |
| continue; | | continue; | |
| } | | } | |
| | | | |
| buf << c; | | buf << c; | |
| | | | |
| if ( c != '.' ) | | if ( c != '.' ) | |
| continue; | | continue; | |
| | | | |
| if ( ! isdigit( fullName[i+1] ) ) | | if ( ! isdigit( fullName[i+1] ) ) | |
| continue; | | continue; | |
| | | | |
| bool possible = true; | | bool possible = true; | |
| size_t j=i+2; | | size_t j=i+2; | |
|
| for ( ; j<fullName.size(); j++ ){ | | for ( ; j<fullName.size(); j++ ) { | |
| char d = fullName[j]; | | char d = fullName[j]; | |
| if ( d == '.' ) | | if ( d == '.' ) | |
| break; | | break; | |
| if ( isdigit( d ) ) | | if ( isdigit( d ) ) | |
| continue; | | continue; | |
| possible = false; | | possible = false; | |
| break; | | break; | |
| } | | } | |
| | | | |
| if ( possible ) | | if ( possible ) | |
| | | | |
| skipping to change at line 181 | | skipping to change at line 237 | |
| | | | |
| template< class Builder > | | template< class Builder > | |
| void apply( Builder& b , BSONElement in , ModState& ms ) const; | | void apply( Builder& b , BSONElement in , ModState& ms ) const; | |
| | | | |
| /** | | /** | |
| * @return true iff toMatch should be removed from the array | | * @return true iff toMatch should be removed from the array | |
| */ | | */ | |
| bool _pullElementMatch( BSONElement& toMatch ) const; | | bool _pullElementMatch( BSONElement& toMatch ) const; | |
| | | | |
| void _checkForAppending( const BSONElement& e ) const { | | void _checkForAppending( const BSONElement& e ) const { | |
|
| if ( e.type() == Object ){ | | if ( e.type() == Object ) { | |
| // this is a tiny bit slow, but rare and important | | // this is a tiny bit slow, but rare and important | |
| // only when setting something TO an object, not setting so
mething in an object | | // only when setting something TO an object, not setting so
mething in an object | |
| // and it checks for { $set : { x : { 'a.b' : 1 } } } | | // and it checks for { $set : { x : { 'a.b' : 1 } } } | |
| // which is feel has been common | | // which is feel has been common | |
| uassert( 12527 , "not okForStorage" , e.embeddedObject().ok
ForStorage() ); | | uassert( 12527 , "not okForStorage" , e.embeddedObject().ok
ForStorage() ); | |
| } | | } | |
| } | | } | |
| | | | |
| bool isEach() const { | | bool isEach() const { | |
| if ( elt.type() != Object ) | | if ( elt.type() != Object ) | |
| | | | |
| skipping to change at line 205 | | skipping to change at line 261 | |
| return false; | | return false; | |
| return strcmp( e.fieldName() , "$each" ) == 0; | | return strcmp( e.fieldName() , "$each" ) == 0; | |
| } | | } | |
| | | | |
| BSONObj getEach() const { | | BSONObj getEach() const { | |
| return elt.embeddedObjectUserCheck().firstElement().embeddedObj
ectUserCheck(); | | return elt.embeddedObjectUserCheck().firstElement().embeddedObj
ectUserCheck(); | |
| } | | } | |
| | | | |
| void parseEach( BSONElementSet& s ) const { | | void parseEach( BSONElementSet& s ) const { | |
| BSONObjIterator i(getEach()); | | BSONObjIterator i(getEach()); | |
|
| while ( i.more() ){ | | while ( i.more() ) { | |
| s.insert( i.next() ); | | s.insert( i.next() ); | |
| } | | } | |
| } | | } | |
| | | | |
|
| | | const char *renameFrom() const { | |
| | | massert( 13492, "mod must be RENAME_TO type", op == Mod::RENAME
_TO ); | |
| | | return elt.fieldName(); | |
| | | } | |
| }; | | }; | |
| | | | |
| /** | | /** | |
| * stores a set of Mods | | * stores a set of Mods | |
| * once created, should never be changed | | * once created, should never be changed | |
| */ | | */ | |
| class ModSet : boost::noncopyable { | | class ModSet : boost::noncopyable { | |
| typedef map<string,Mod> ModHolder; | | typedef map<string,Mod> ModHolder; | |
| ModHolder _mods; | | ModHolder _mods; | |
| int _isIndexed; | | int _isIndexed; | |
| | | | |
| skipping to change at line 242 | | skipping to change at line 302 | |
| if ( mDone ) | | if ( mDone ) | |
| return RIGHT_BEFORE; | | return RIGHT_BEFORE; | |
| if ( pDone ) | | if ( pDone ) | |
| return LEFT_BEFORE; | | return LEFT_BEFORE; | |
| | | | |
| return compareDottedFieldNames( m->first, p->first.c_str() ); | | return compareDottedFieldNames( m->first, p->first.c_str() ); | |
| } | | } | |
| | | | |
| bool mayAddEmbedded( map< string, BSONElement > &existing, string r
ight ) { | | bool mayAddEmbedded( map< string, BSONElement > &existing, string r
ight ) { | |
| for( string left = EmbeddedBuilder::splitDot( right ); | | for( string left = EmbeddedBuilder::splitDot( right ); | |
|
| left.length() > 0 && left[ left.length() - 1 ] != '.'; | | left.length() > 0 && left[ left.length() - 1 ] != '.'; | |
| left += "." + EmbeddedBuilder::splitDot( right ) ) { | | left += "." + EmbeddedBuilder::splitDot( right ) ) { | |
| if ( existing.count( left ) > 0 && existing[ left ].type()
!= Object ) | | if ( existing.count( left ) > 0 && existing[ left ].type()
!= Object ) | |
| return false; | | return false; | |
| if ( haveModForField( left.c_str() ) ) | | if ( haveModForField( left.c_str() ) ) | |
| return false; | | return false; | |
| } | | } | |
| return true; | | return true; | |
| } | | } | |
| static Mod::Op opFromStr( const char *fn ) { | | static Mod::Op opFromStr( const char *fn ) { | |
| assert( fn[0] == '$' ); | | assert( fn[0] == '$' ); | |
|
| switch( fn[1] ){ | | switch( fn[1] ) { | |
| case 'i': { | | case 'i': { | |
| if ( fn[2] == 'n' && fn[3] == 'c' && fn[4] == 0 ) | | if ( fn[2] == 'n' && fn[3] == 'c' && fn[4] == 0 ) | |
| return Mod::INC; | | return Mod::INC; | |
| break; | | break; | |
| } | | } | |
| case 's': { | | case 's': { | |
| if ( fn[2] == 'e' && fn[3] == 't' && fn[4] == 0 ) | | if ( fn[2] == 'e' && fn[3] == 't' && fn[4] == 0 ) | |
| return Mod::SET; | | return Mod::SET; | |
| break; | | break; | |
| } | | } | |
| case 'p': { | | case 'p': { | |
|
| if ( fn[2] == 'u' ){ | | if ( fn[2] == 'u' ) { | |
| if ( fn[3] == 's' && fn[4] == 'h' ){ | | if ( fn[3] == 's' && fn[4] == 'h' ) { | |
| if ( fn[5] == 0 ) | | if ( fn[5] == 0 ) | |
| return Mod::PUSH; | | return Mod::PUSH; | |
| if ( fn[5] == 'A' && fn[6] == 'l' && fn[7] == 'l' &
& fn[8] == 0 ) | | if ( fn[5] == 'A' && fn[6] == 'l' && fn[7] == 'l' &
& fn[8] == 0 ) | |
| return Mod::PUSH_ALL; | | return Mod::PUSH_ALL; | |
| } | | } | |
|
| else if ( fn[3] == 'l' && fn[4] == 'l' ){ | | else if ( fn[3] == 'l' && fn[4] == 'l' ) { | |
| if ( fn[5] == 0 ) | | if ( fn[5] == 0 ) | |
| return Mod::PULL; | | return Mod::PULL; | |
| if ( fn[5] == 'A' && fn[6] == 'l' && fn[7] == 'l' &
& fn[8] == 0 ) | | if ( fn[5] == 'A' && fn[6] == 'l' && fn[7] == 'l' &
& fn[8] == 0 ) | |
| return Mod::PULL_ALL; | | return Mod::PULL_ALL; | |
| } | | } | |
| } | | } | |
| else if ( fn[2] == 'o' && fn[3] == 'p' && fn[4] == 0 ) | | else if ( fn[2] == 'o' && fn[3] == 'p' && fn[4] == 0 ) | |
| return Mod::POP; | | return Mod::POP; | |
| break; | | break; | |
| } | | } | |
| case 'u': { | | case 'u': { | |
| if ( fn[2] == 'n' && fn[3] == 's' && fn[4] == 'e' && fn[5]
== 't' && fn[6] == 0 ) | | if ( fn[2] == 'n' && fn[3] == 's' && fn[4] == 'e' && fn[5]
== 't' && fn[6] == 0 ) | |
| return Mod::UNSET; | | return Mod::UNSET; | |
| break; | | break; | |
| } | | } | |
| case 'b': { | | case 'b': { | |
|
| if ( fn[2] == 'i' && fn[3] == 't' ){ | | if ( fn[2] == 'i' && fn[3] == 't' ) { | |
| if ( fn[4] == 0 ) | | if ( fn[4] == 0 ) | |
| return Mod::BIT; | | return Mod::BIT; | |
| if ( fn[4] == 'a' && fn[5] == 'n' && fn[6] == 'd' && fn
[7] == 0 ) | | if ( fn[4] == 'a' && fn[5] == 'n' && fn[6] == 'd' && fn
[7] == 0 ) | |
| return Mod::BITAND; | | return Mod::BITAND; | |
| if ( fn[4] == 'o' && fn[5] == 'r' && fn[6] == 0 ) | | if ( fn[4] == 'o' && fn[5] == 'r' && fn[6] == 0 ) | |
| return Mod::BITOR; | | return Mod::BITOR; | |
| } | | } | |
| break; | | break; | |
| } | | } | |
| case 'a': { | | case 'a': { | |
|
| if ( fn[2] == 'd' && fn[3] == 'd' ){ | | if ( fn[2] == 'd' && fn[3] == 'd' ) { | |
| // add | | // add | |
| if ( fn[4] == 'T' && fn[5] == 'o' && fn[6] == 'S' && fn
[7] == 'e' && fn[8] == 't' && fn[9] == 0 ) | | if ( fn[4] == 'T' && fn[5] == 'o' && fn[6] == 'S' && fn
[7] == 'e' && fn[8] == 't' && fn[9] == 0 ) | |
| return Mod::ADDTOSET; | | return Mod::ADDTOSET; | |
| | | | |
| } | | } | |
|
| | | break; | |
| | | } | |
| | | case 'r': { | |
| | | if ( fn[2] == 'e' && fn[3] == 'n' && fn[4] == 'a' && fn[5]
== 'm' && fn[6] =='e' ) { | |
| | | return Mod::RENAME_TO; // with this return code we hand
le both RENAME_TO and RENAME_FROM | |
| | | } | |
| | | break; | |
| } | | } | |
| default: break; | | default: break; | |
| } | | } | |
| uassert( 10161 , "Invalid modifier specified " + string( fn ),
false ); | | uassert( 10161 , "Invalid modifier specified " + string( fn ),
false ); | |
| return Mod::INC; | | return Mod::INC; | |
| } | | } | |
| | | | |
|
| ModSet(){} | | ModSet() {} | |
| | | | |
| | | void updateIsIndexed( const Mod &m, const set<string> &idxKeys, con
st set<string> *backgroundKeys ) { | |
| | | if ( m.isIndexed( idxKeys ) || | |
| | | (backgroundKeys && m.isIndexed(*backgroundKeys)) ) { | |
| | | _isIndexed++; | |
| | | } | |
| | | } | |
| | | | |
| public: | | public: | |
| | | | |
| ModSet( const BSONObj &from , | | ModSet( const BSONObj &from , | |
|
| const set<string>& idxKeys = set<string>(), | | const set<string>& idxKeys = set<string>(), | |
| const set<string>* backgroundKeys = 0 | | const set<string>* backgroundKeys = 0 | |
| ); | | ); | |
| | | | |
| // TODO: this is inefficient - should probably just handle when ite
rating | | // TODO: this is inefficient - should probably just handle when ite
rating | |
| ModSet * fixDynamicArray( const char * elemMatchKey ) const; | | ModSet * fixDynamicArray( const char * elemMatchKey ) const; | |
| | | | |
| bool hasDynamicArray() const { return _hasDynamicArray; } | | bool hasDynamicArray() const { return _hasDynamicArray; } | |
| | | | |
| /** | | /** | |
| * creates a ModSetState suitable for operation on obj | | * creates a ModSetState suitable for operation on obj | |
| * doesn't change or modify this ModSet or any underying Mod | | * doesn't change or modify this ModSet or any underying Mod | |
| */ | | */ | |
| | | | |
| skipping to change at line 352 | | skipping to change at line 426 | |
| int isIndexed() const { | | int isIndexed() const { | |
| return _isIndexed; | | return _isIndexed; | |
| } | | } | |
| | | | |
| unsigned size() const { return _mods.size(); } | | unsigned size() const { return _mods.size(); } | |
| | | | |
| bool haveModForField( const char *fieldName ) const { | | bool haveModForField( const char *fieldName ) const { | |
| return _mods.find( fieldName ) != _mods.end(); | | return _mods.find( fieldName ) != _mods.end(); | |
| } | | } | |
| | | | |
|
| bool haveConflictingMod( const string& fieldName ){ | | bool haveConflictingMod( const string& fieldName ) { | |
| size_t idx = fieldName.find( '.' ); | | size_t idx = fieldName.find( '.' ); | |
| if ( idx == string::npos ) | | if ( idx == string::npos ) | |
| idx = fieldName.size(); | | idx = fieldName.size(); | |
| | | | |
| ModHolder::const_iterator start = _mods.lower_bound(fieldName.s
ubstr(0,idx)); | | ModHolder::const_iterator start = _mods.lower_bound(fieldName.s
ubstr(0,idx)); | |
|
| for ( ; start != _mods.end(); start++ ){ | | for ( ; start != _mods.end(); start++ ) { | |
| FieldCompareResult r = compareDottedFieldNames( fieldName ,
start->first ); | | FieldCompareResult r = compareDottedFieldNames( fieldName ,
start->first ); | |
|
| switch ( r ){ | | switch ( r ) { | |
| case LEFT_SUBFIELD: return true; | | case LEFT_SUBFIELD: return true; | |
| case LEFT_BEFORE: return false; | | case LEFT_BEFORE: return false; | |
| case SAME: return true; | | case SAME: return true; | |
| case RIGHT_BEFORE: return false; | | case RIGHT_BEFORE: return false; | |
| case RIGHT_SUBFIELD: return true; | | case RIGHT_SUBFIELD: return true; | |
| } | | } | |
| } | | } | |
| return false; | | return false; | |
| | | | |
| } | | } | |
| | | | |
| }; | | }; | |
| | | | |
| /** | | /** | |
| * stores any information about a single Mod operating on a single Obje
ct | | * stores any information about a single Mod operating on a single Obje
ct | |
| */ | | */ | |
| class ModState { | | class ModState { | |
| public: | | public: | |
| const Mod * m; | | const Mod * m; | |
| BSONElement old; | | BSONElement old; | |
|
| | | BSONElement newVal; | |
| | | BSONObj _objData; | |
| | | | |
| const char * fixedOpName; | | const char * fixedOpName; | |
| BSONElement * fixed; | | BSONElement * fixed; | |
| int pushStartSize; | | int pushStartSize; | |
| | | | |
| BSONType incType; | | BSONType incType; | |
| int incint; | | int incint; | |
| double incdouble; | | double incdouble; | |
| long long inclong; | | long long inclong; | |
| | | | |
|
| ModState(){ | | bool dontApply; | |
| | | | |
| | | ModState() { | |
| fixedOpName = 0; | | fixedOpName = 0; | |
| fixed = 0; | | fixed = 0; | |
| pushStartSize = -1; | | pushStartSize = -1; | |
| incType = EOO; | | incType = EOO; | |
|
| | | dontApply = false; | |
| } | | } | |
| | | | |
| Mod::Op op() const { | | Mod::Op op() const { | |
| return m->op; | | return m->op; | |
| } | | } | |
| | | | |
| const char * fieldName() const { | | const char * fieldName() const { | |
| return m->fieldName; | | return m->fieldName; | |
| } | | } | |
| | | | |
| bool needOpLogRewrite() const { | | bool needOpLogRewrite() const { | |
|
| | | if ( dontApply ) | |
| | | return false; | |
| | | | |
| if ( fixed || fixedOpName || incType ) | | if ( fixed || fixedOpName || incType ) | |
| return true; | | return true; | |
| | | | |
|
| switch( op() ){ | | switch( op() ) { | |
| | | case Mod::RENAME_FROM: | |
| | | case Mod::RENAME_TO: | |
| | | return true; | |
| case Mod::BIT: | | case Mod::BIT: | |
| case Mod::BITAND: | | case Mod::BITAND: | |
| case Mod::BITOR: | | case Mod::BITOR: | |
| // TODO: should we convert this to $set? | | // TODO: should we convert this to $set? | |
| return false; | | return false; | |
| default: | | default: | |
| return false; | | return false; | |
| } | | } | |
| } | | } | |
| | | | |
| void appendForOpLog( BSONObjBuilder& b ) const; | | void appendForOpLog( BSONObjBuilder& b ) const; | |
| | | | |
| template< class Builder > | | template< class Builder > | |
|
| void apply( Builder& b , BSONElement in ){ | | void apply( Builder& b , BSONElement in ) { | |
| m->apply( b , in , *this ); | | m->apply( b , in , *this ); | |
| } | | } | |
| | | | |
| template< class Builder > | | template< class Builder > | |
| void appendIncValue( Builder& b , bool useFullName ) const { | | void appendIncValue( Builder& b , bool useFullName ) const { | |
| const char * n = useFullName ? m->fieldName : m->shortFieldName
; | | const char * n = useFullName ? m->fieldName : m->shortFieldName
; | |
| | | | |
|
| switch ( incType ){ | | switch ( incType ) { | |
| case NumberDouble: | | case NumberDouble: | |
| b.append( n , incdouble ); break; | | b.append( n , incdouble ); break; | |
| case NumberLong: | | case NumberLong: | |
| b.append( n , inclong ); break; | | b.append( n , inclong ); break; | |
| case NumberInt: | | case NumberInt: | |
| b.append( n , incint ); break; | | b.append( n , incint ); break; | |
| default: | | default: | |
| assert(0); | | assert(0); | |
| } | | } | |
| } | | } | |
| | | | |
| string toString() const; | | string toString() const; | |
|
| | | | |
| | | template< class Builder > | |
| | | void handleRename( Builder &newObjBuilder, const char *shortFieldNa
me ); | |
| }; | | }; | |
| | | | |
| /** | | /** | |
| * this is used to hold state, meta data while applying a ModSet to a B
SONObj | | * this is used to hold state, meta data while applying a ModSet to a B
SONObj | |
| * the goal is to make ModSet const so its re-usable | | * the goal is to make ModSet const so its re-usable | |
| */ | | */ | |
| class ModSetState : boost::noncopyable { | | class ModSetState : boost::noncopyable { | |
| struct FieldCmp { | | struct FieldCmp { | |
|
| bool operator()( const string &l, const string &r ) const { | | bool operator()( const string &l, const string &r ) const; | |
| return lexNumCmp( l.c_str(), r.c_str() ) < 0; | | | |
| } | | | |
| }; | | }; | |
| typedef map<string,ModState,FieldCmp> ModStateHolder; | | typedef map<string,ModState,FieldCmp> ModStateHolder; | |
| const BSONObj& _obj; | | const BSONObj& _obj; | |
| ModStateHolder _mods; | | ModStateHolder _mods; | |
| bool _inPlacePossible; | | bool _inPlacePossible; | |
|
| | | BSONObj _newFromMods; // keep this data alive, as oplog generation
may depend on it | |
| | | | |
| ModSetState( const BSONObj& obj ) | | ModSetState( const BSONObj& obj ) | |
|
| : _obj( obj ) , _inPlacePossible(true){ | | : _obj( obj ) , _inPlacePossible(true) { | |
| } | | } | |
| | | | |
| /** | | /** | |
| * @return if in place is still possible | | * @return if in place is still possible | |
| */ | | */ | |
|
| bool amIInPlacePossible( bool inPlacePossible ){ | | bool amIInPlacePossible( bool inPlacePossible ) { | |
| if ( ! inPlacePossible ) | | if ( ! inPlacePossible ) | |
| _inPlacePossible = false; | | _inPlacePossible = false; | |
| return _inPlacePossible; | | return _inPlacePossible; | |
| } | | } | |
| | | | |
| template< class Builder > | | template< class Builder > | |
| void createNewFromMods( const string& root , Builder& b , const BSO
NObj &obj ); | | void createNewFromMods( const string& root , Builder& b , const BSO
NObj &obj ); | |
| | | | |
| template< class Builder > | | template< class Builder > | |
| void _appendNewFromMods( const string& root , ModState& m , Builder
& b , set<string>& onedownseen ); | | void _appendNewFromMods( const string& root , ModState& m , Builder
& b , set<string>& onedownseen ); | |
| | | | |
| template< class Builder > | | template< class Builder > | |
|
| void appendNewFromMod( ModState& ms , Builder& b ){ | | void appendNewFromMod( ModState& ms , Builder& b ) { | |
| | | if ( ms.dontApply ) { | |
| | | return; | |
| | | } | |
| | | | |
| //const Mod& m = *(ms.m); // HACK | | //const Mod& m = *(ms.m); // HACK | |
| Mod& m = *((Mod*)(ms.m)); // HACK | | Mod& m = *((Mod*)(ms.m)); // HACK | |
| | | | |
|
| switch ( m.op ){ | | switch ( m.op ) { | |
| | | | |
| case Mod::PUSH: | | case Mod::PUSH: | |
| case Mod::ADDTOSET: { | | case Mod::ADDTOSET: { | |
|
| if ( m.isEach() ){ | | if ( m.isEach() ) { | |
| b.appendArray( m.shortFieldName , m.getEach() ); | | b.appendArray( m.shortFieldName , m.getEach() ); | |
| } | | } | |
| else { | | else { | |
| BSONObjBuilder arr( b.subarrayStart( m.shortFieldName )
); | | BSONObjBuilder arr( b.subarrayStart( m.shortFieldName )
); | |
| arr.appendAs( m.elt, "0" ); | | arr.appendAs( m.elt, "0" ); | |
| arr.done(); | | arr.done(); | |
| } | | } | |
| break; | | break; | |
| } | | } | |
| | | | |
| | | | |
| skipping to change at line 519 | | skipping to change at line 610 | |
| // no-op b/c unset/pull of nothing does nothing | | // no-op b/c unset/pull of nothing does nothing | |
| break; | | break; | |
| | | | |
| case Mod::INC: | | case Mod::INC: | |
| ms.fixedOpName = "$set"; | | ms.fixedOpName = "$set"; | |
| case Mod::SET: { | | case Mod::SET: { | |
| m._checkForAppending( m.elt ); | | m._checkForAppending( m.elt ); | |
| b.appendAs( m.elt, m.shortFieldName ); | | b.appendAs( m.elt, m.shortFieldName ); | |
| break; | | break; | |
| } | | } | |
|
| | | // shouldn't see RENAME_FROM here | |
| | | case Mod::RENAME_TO: | |
| | | ms.handleRename( b, m.shortFieldName ); | |
| | | break; | |
| default: | | default: | |
| stringstream ss; | | stringstream ss; | |
| ss << "unknown mod in appendNewFromMod: " << m.op; | | ss << "unknown mod in appendNewFromMod: " << m.op; | |
| throw UserException( 9015, ss.str() ); | | throw UserException( 9015, ss.str() ); | |
| } | | } | |
| | | | |
| } | | } | |
| | | | |
| public: | | public: | |
| | | | |
| bool canApplyInPlace() const { | | bool canApplyInPlace() const { | |
| return _inPlacePossible; | | return _inPlacePossible; | |
| } | | } | |
| | | | |
| /** | | /** | |
| * modified underlying _obj | | * modified underlying _obj | |
|
| | | * @param isOnDisk - true means this is an on disk object, and this
update needs to be made durable | |
| */ | | */ | |
|
| void applyModsInPlace(); | | void applyModsInPlace( bool isOnDisk ); | |
| | | | |
| BSONObj createNewFromMods(); | | BSONObj createNewFromMods(); | |
| | | | |
| // re-writing for oplog | | // re-writing for oplog | |
| | | | |
| bool needOpLogRewrite() const { | | bool needOpLogRewrite() const { | |
| for ( ModStateHolder::const_iterator i = _mods.begin(); i != _m
ods.end(); i++ ) | | for ( ModStateHolder::const_iterator i = _mods.begin(); i != _m
ods.end(); i++ ) | |
| if ( i->second.needOpLogRewrite() ) | | if ( i->second.needOpLogRewrite() ) | |
| return true; | | return true; | |
| return false; | | return false; | |
| | | | |
| skipping to change at line 566 | | skipping to change at line 662 | |
| bool haveArrayDepMod() const { | | bool haveArrayDepMod() const { | |
| for ( ModStateHolder::const_iterator i = _mods.begin(); i != _m
ods.end(); i++ ) | | for ( ModStateHolder::const_iterator i = _mods.begin(); i != _m
ods.end(); i++ ) | |
| if ( i->second.m->arrayDep() ) | | if ( i->second.m->arrayDep() ) | |
| return true; | | return true; | |
| return false; | | return false; | |
| } | | } | |
| | | | |
| void appendSizeSpecForArrayDepMods( BSONObjBuilder &b ) const { | | void appendSizeSpecForArrayDepMods( BSONObjBuilder &b ) const { | |
| for ( ModStateHolder::const_iterator i = _mods.begin(); i != _m
ods.end(); i++ ) { | | for ( ModStateHolder::const_iterator i = _mods.begin(); i != _m
ods.end(); i++ ) { | |
| const ModState& m = i->second; | | const ModState& m = i->second; | |
|
| if ( m.m->arrayDep() ){ | | if ( m.m->arrayDep() ) { | |
| if ( m.pushStartSize == -1 ) | | if ( m.pushStartSize == -1 ) | |
| b.appendNull( m.fieldName() ); | | b.appendNull( m.fieldName() ); | |
| else | | else | |
| b << m.fieldName() << BSON( "$size" << m.pushStartS
ize ); | | b << m.fieldName() << BSON( "$size" << m.pushStartS
ize ); | |
| } | | } | |
| } | | } | |
| } | | } | |
| | | | |
| string toString() const; | | string toString() const; | |
| | | | |
| | | | |
End of changes. 49 change blocks. |
| 52 lines changed or deleted | | 148 lines changed or added | |
|