0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-12-01 09:32:32 +01:00
mongodb/bson/bson-inl.h
2011-06-06 19:53:49 -04:00

905 lines
28 KiB
C++

/** @file bsoninlines.h
a goal here is that the most common bson methods can be used inline-only, a la boost.
thus some things are inline that wouldn't necessarily be otherwise.
*/
/* Copyright 2009 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <map>
#include <limits>
#if defined(_WIN32)
#undef max
#undef min
#endif
namespace mongo {
/* must be same type when called, unless both sides are #s
this large function is in header to facilitate inline-only use of bson
*/
inline int compareElementValues(const BSONElement& l, const BSONElement& r) {
int f;
double x;
switch ( l.type() ) {
case EOO:
case Undefined: // EOO and Undefined are same canonicalType
case jstNULL:
case MaxKey:
case MinKey:
f = l.canonicalType() - r.canonicalType();
if ( f<0 ) return -1;
return f==0 ? 0 : 1;
case Bool:
return *l.value() - *r.value();
case Timestamp:
// unsigned compare for timestamps - note they are not really dates but (ordinal + time_t)
if ( l.date() < r.date() )
return -1;
return l.date() == r.date() ? 0 : 1;
case Date:
{
long long a = (long long) l.Date().millis;
long long b = (long long) r.Date().millis;
if( a < b )
return -1;
return a == b ? 0 : 1;
}
case NumberLong:
if( r.type() == NumberLong ) {
long long L = l._numberLong();
long long R = r._numberLong();
if( L < R ) return -1;
if( L == R ) return 0;
return 1;
}
// else fall through
case NumberInt:
case NumberDouble: {
double left = l.number();
double right = r.number();
bool lNan = !( left <= std::numeric_limits< double >::max() &&
left >= -std::numeric_limits< double >::max() );
bool rNan = !( right <= numeric_limits< double >::max() &&
right >= -numeric_limits< double >::max() );
if ( lNan ) {
if ( rNan ) {
return 0;
}
else {
return -1;
}
}
else if ( rNan ) {
return 1;
}
x = left - right;
if ( x < 0 ) return -1;
return x == 0 ? 0 : 1;
}
case jstOID:
return memcmp(l.value(), r.value(), 12);
case Code:
case Symbol:
case String:
/* todo: a utf sort order version one day... */
{
// we use memcmp as we allow zeros in UTF8 strings
int lsz = l.valuestrsize();
int rsz = r.valuestrsize();
int common = min(lsz, rsz);
int res = memcmp(l.valuestr(), r.valuestr(), common);
if( res )
return res;
// longer string is the greater one
return lsz-rsz;
}
case Object:
case Array:
return l.embeddedObject().woCompare( r.embeddedObject() );
case DBRef: {
int lsz = l.valuesize();
int rsz = r.valuesize();
if ( lsz - rsz != 0 ) return lsz - rsz;
return memcmp(l.value(), r.value(), lsz);
}
case BinData: {
int lsz = l.objsize(); // our bin data size in bytes, not including the subtype byte
int rsz = r.objsize();
if ( lsz - rsz != 0 ) return lsz - rsz;
return memcmp(l.value()+4, r.value()+4, lsz+1);
}
case RegEx: {
int c = strcmp(l.regex(), r.regex());
if ( c )
return c;
return strcmp(l.regexFlags(), r.regexFlags());
}
case CodeWScope : {
f = l.canonicalType() - r.canonicalType();
if ( f )
return f;
f = strcmp( l.codeWScopeCode() , r.codeWScopeCode() );
if ( f )
return f;
f = strcmp( l.codeWScopeScopeData() , r.codeWScopeScopeData() );
if ( f )
return f;
return 0;
}
default:
assert( false);
}
return -1;
}
/* wo = "well ordered" */
inline int BSONElement::woCompare( const BSONElement &e,
bool considerFieldName ) const {
int lt = (int) canonicalType();
int rt = (int) e.canonicalType();
int x = lt - rt;
if( x != 0 && (!isNumber() || !e.isNumber()) )
return x;
if ( considerFieldName ) {
x = strcmp(fieldName(), e.fieldName());
if ( x != 0 )
return x;
}
x = compareElementValues(*this, e);
return x;
}
inline BSONObjIterator BSONObj::begin() const {
return BSONObjIterator(*this);
}
inline BSONObj BSONElement::embeddedObjectUserCheck() const {
if ( isABSONObj() )
return BSONObj(value());
stringstream ss;
ss << "invalid parameter: expected an object (" << fieldName() << ")";
uasserted( 10065 , ss.str() );
return BSONObj(); // never reachable
}
inline BSONObj BSONElement::embeddedObject() const {
assert( isABSONObj() );
return BSONObj(value());
}
inline BSONObj BSONElement::codeWScopeObject() const {
assert( type() == CodeWScope );
int strSizeWNull = *(int *)( value() + 4 );
return BSONObj( value() + 4 + 4 + strSizeWNull );
}
// deep (full) equality
inline bool BSONObj::equal(const BSONObj &rhs) const {
BSONObjIterator i(*this);
BSONObjIterator j(rhs);
BSONElement l,r;
do {
// so far, equal...
l = i.next();
r = j.next();
if ( l.eoo() )
return r.eoo();
} while( l == r );
return false;
}
inline NOINLINE_DECL void BSONObj::_assertInvalid() const {
StringBuilder ss;
int os = objsize();
ss << "Invalid BSONObj size: " << os << " (0x" << toHex( &os, 4 ) << ')';
try {
BSONElement e = firstElement();
ss << " first element: " << e.toString();
}
catch ( ... ) { }
massert( 10334 , ss.str() , 0 );
}
/* the idea with NOINLINE_DECL here is to keep this from inlining in the
getOwned() method. the presumption being that is better.
*/
inline NOINLINE_DECL BSONObj BSONObj::copy() const {
Holder *h = (Holder*) malloc(objsize() + sizeof(unsigned));
h->zero();
memcpy(h->data, objdata(), objsize());
return BSONObj(h);
}
inline BSONObj BSONObj::getOwned() const {
if ( isOwned() )
return *this;
return copy();
}
// wrap this element up as a singleton object.
inline BSONObj BSONElement::wrap() const {
BSONObjBuilder b(size()+6);
b.append(*this);
return b.obj();
}
inline BSONObj BSONElement::wrap( const char * newName ) const {
BSONObjBuilder b(size()+6+(int)strlen(newName));
b.appendAs(*this,newName);
return b.obj();
}
inline void BSONObj::getFields(unsigned n, const char **fieldNames, BSONElement *fields) const {
BSONObjIterator i(*this);
while ( i.more() ) {
BSONElement e = i.next();
const char *p = e.fieldName();
for( unsigned i = 0; i < n; i++ ) {
if( strcmp(p, fieldNames[i]) == 0 ) {
fields[i] = e;
break;
}
}
}
}
inline BSONElement BSONObj::getField(const StringData& name) const {
BSONObjIterator i(*this);
while ( i.more() ) {
BSONElement e = i.next();
if ( strcmp(e.fieldName(), name.data()) == 0 )
return e;
}
return BSONElement();
}
inline int BSONObj::getIntField(const char *name) const {
BSONElement e = getField(name);
return e.isNumber() ? (int) e.number() : std::numeric_limits< int >::min();
}
inline bool BSONObj::getBoolField(const char *name) const {
BSONElement e = getField(name);
return e.type() == Bool ? e.boolean() : false;
}
inline const char * BSONObj::getStringField(const char *name) const {
BSONElement e = getField(name);
return e.type() == String ? e.valuestr() : "";
}
/* add all the fields from the object specified to this object */
inline BSONObjBuilder& BSONObjBuilder::appendElements(BSONObj x) {
BSONObjIterator it(x);
while ( it.moreWithEOO() ) {
BSONElement e = it.next();
if ( e.eoo() ) break;
append(e);
}
return *this;
}
/* add all the fields from the object specified to this object if they don't exist */
inline BSONObjBuilder& BSONObjBuilder::appendElementsUnique(BSONObj x) {
set<string> have;
{
BSONObjIterator i = iterator();
while ( i.more() )
have.insert( i.next().fieldName() );
}
BSONObjIterator it(x);
while ( it.more() ) {
BSONElement e = it.next();
if ( have.count( e.fieldName() ) )
continue;
append(e);
}
return *this;
}
inline bool BSONObj::isValid() {
int x = objsize();
return x > 0 && x <= BSONObjMaxInternalSize;
}
inline bool BSONObj::getObjectID(BSONElement& e) const {
BSONElement f = getField("_id");
if( !f.eoo() ) {
e = f;
return true;
}
return false;
}
inline BSONObjBuilderValueStream::BSONObjBuilderValueStream( BSONObjBuilder * builder ) {
_fieldName = 0;
_builder = builder;
}
template<class T>
inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<( T value ) {
_builder->append(_fieldName, value);
_fieldName = 0;
return *_builder;
}
inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<( const BSONElement& e ) {
_builder->appendAs( e , _fieldName );
_fieldName = 0;
return *_builder;
}
inline Labeler BSONObjBuilderValueStream::operator<<( const Labeler::Label &l ) {
return Labeler( l, this );
}
inline void BSONObjBuilderValueStream::endField( const char *nextFieldName ) {
if ( _fieldName && haveSubobj() ) {
_builder->append( _fieldName, subobj()->done() );
}
_subobj.reset();
_fieldName = nextFieldName;
}
inline BSONObjBuilder *BSONObjBuilderValueStream::subobj() {
if ( !haveSubobj() )
_subobj.reset( new BSONObjBuilder() );
return _subobj.get();
}
template<class T> inline
BSONObjBuilder& Labeler::operator<<( T value ) {
s_->subobj()->append( l_.l_, value );
return *s_->_builder;
}
inline
BSONObjBuilder& Labeler::operator<<( const BSONElement& e ) {
s_->subobj()->appendAs( e, l_.l_ );
return *s_->_builder;
}
// {a: {b:1}} -> {a.b:1}
void nested2dotted(BSONObjBuilder& b, const BSONObj& obj, const string& base="");
inline BSONObj nested2dotted(const BSONObj& obj) {
BSONObjBuilder b;
nested2dotted(b, obj);
return b.obj();
}
// {a.b:1} -> {a: {b:1}}
void dotted2nested(BSONObjBuilder& b, const BSONObj& obj);
inline BSONObj dotted2nested(const BSONObj& obj) {
BSONObjBuilder b;
dotted2nested(b, obj);
return b.obj();
}
inline BSONObjIterator BSONObjBuilder::iterator() const {
const char * s = _b.buf() + _offset;
const char * e = _b.buf() + _b.len();
return BSONObjIterator( s , e );
}
inline bool BSONObjBuilder::hasField( const StringData& name ) const {
BSONObjIterator i = iterator();
while ( i.more() )
if ( strcmp( name.data() , i.next().fieldName() ) == 0 )
return true;
return false;
}
/* WARNING: nested/dotted conversions are not 100% reversible
* nested2dotted(dotted2nested({a.b: {c:1}})) -> {a.b.c: 1}
* also, dotted2nested ignores order
*/
typedef map<string, BSONElement> BSONMap;
inline BSONMap bson2map(const BSONObj& obj) {
BSONMap m;
BSONObjIterator it(obj);
while (it.more()) {
BSONElement e = it.next();
m[e.fieldName()] = e;
}
return m;
}
struct BSONElementFieldNameCmp {
bool operator()( const BSONElement &l, const BSONElement &r ) const {
return strcmp( l.fieldName() , r.fieldName() ) <= 0;
}
};
typedef set<BSONElement, BSONElementFieldNameCmp> BSONSortedElements;
inline BSONSortedElements bson2set( const BSONObj& obj ) {
BSONSortedElements s;
BSONObjIterator it(obj);
while ( it.more() )
s.insert( it.next() );
return s;
}
inline string BSONObj::toString( bool isArray, bool full ) const {
if ( isEmpty() ) return "{}";
StringBuilder s;
toString(s, isArray, full);
return s.str();
}
inline void BSONObj::toString(StringBuilder& s, bool isArray, bool full ) const {
if ( isEmpty() ) {
s << "{}";
return;
}
s << ( isArray ? "[ " : "{ " );
BSONObjIterator i(*this);
bool first = true;
while ( 1 ) {
massert( 10327 , "Object does not end with EOO", i.moreWithEOO() );
BSONElement e = i.next( true );
massert( 10328 , "Invalid element size", e.size() > 0 );
massert( 10329 , "Element too large", e.size() < ( 1 << 30 ) );
int offset = (int) (e.rawdata() - this->objdata());
massert( 10330 , "Element extends past end of object",
e.size() + offset <= this->objsize() );
e.validate();
bool end = ( e.size() + offset == this->objsize() );
if ( e.eoo() ) {
massert( 10331 , "EOO Before end of object", end );
break;
}
if ( first )
first = false;
else
s << ", ";
e.toString(s, !isArray, full );
}
s << ( isArray ? " ]" : " }" );
}
inline void BSONElement::validate() const {
const BSONType t = type();
switch( t ) {
case DBRef:
case Code:
case Symbol:
case mongo::String: {
unsigned x = (unsigned) valuestrsize();
bool lenOk = x > 0 && x < (unsigned) BSONObjMaxInternalSize;
if( lenOk && valuestr()[x-1] == 0 )
return;
StringBuilder buf;
buf << "Invalid dbref/code/string/symbol size: " << x;
if( lenOk )
buf << " strnlen:" << mongo::strnlen( valuestr() , x );
msgasserted( 10321 , buf.str() );
break;
}
case CodeWScope: {
int totalSize = *( int * )( value() );
massert( 10322 , "Invalid CodeWScope size", totalSize >= 8 );
int strSizeWNull = *( int * )( value() + 4 );
massert( 10323 , "Invalid CodeWScope string size", totalSize >= strSizeWNull + 4 + 4 );
massert( 10324 , "Invalid CodeWScope string size",
strSizeWNull > 0 &&
(strSizeWNull - 1) == mongo::strnlen( codeWScopeCode(), strSizeWNull ) );
massert( 10325 , "Invalid CodeWScope size", totalSize >= strSizeWNull + 4 + 4 + 4 );
int objSize = *( int * )( value() + 4 + 4 + strSizeWNull );
massert( 10326 , "Invalid CodeWScope object size", totalSize == 4 + 4 + strSizeWNull + objSize );
// Subobject validation handled elsewhere.
}
case Object:
// We expect Object size validation to be handled elsewhere.
default:
break;
}
}
inline int BSONElement::size( int maxLen ) const {
if ( totalSize >= 0 )
return totalSize;
int remain = maxLen - fieldNameSize() - 1;
int x = 0;
switch ( type() ) {
case EOO:
case Undefined:
case jstNULL:
case MaxKey:
case MinKey:
break;
case mongo::Bool:
x = 1;
break;
case NumberInt:
x = 4;
break;
case Timestamp:
case mongo::Date:
case NumberDouble:
case NumberLong:
x = 8;
break;
case jstOID:
x = 12;
break;
case Symbol:
case Code:
case mongo::String:
massert( 10313 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 );
x = valuestrsize() + 4;
break;
case CodeWScope:
massert( 10314 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 );
x = objsize();
break;
case DBRef:
massert( 10315 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 );
x = valuestrsize() + 4 + 12;
break;
case Object:
case mongo::Array:
massert( 10316 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 );
x = objsize();
break;
case BinData:
massert( 10317 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 );
x = valuestrsize() + 4 + 1/*subtype*/;
break;
case RegEx: {
const char *p = value();
size_t len1 = ( maxLen == -1 ) ? strlen( p ) : (size_t)mongo::strnlen( p, remain );
//massert( 10318 , "Invalid regex string", len1 != -1 ); // ERH - 4/28/10 - don't think this does anything
p = p + len1 + 1;
size_t len2;
if( maxLen == -1 )
len2 = strlen( p );
else {
size_t x = remain - len1 - 1;
assert( x <= 0x7fffffff );
len2 = mongo::strnlen( p, (int) x );
}
//massert( 10319 , "Invalid regex options string", len2 != -1 ); // ERH - 4/28/10 - don't think this does anything
x = (int) (len1 + 1 + len2 + 1);
}
break;
default: {
StringBuilder ss;
ss << "BSONElement: bad type " << (int) type();
string msg = ss.str();
massert( 13655 , msg.c_str(),false);
}
}
totalSize = x + fieldNameSize() + 1; // BSONType
return totalSize;
}
inline int BSONElement::size() const {
if ( totalSize >= 0 )
return totalSize;
int x = 0;
switch ( type() ) {
case EOO:
case Undefined:
case jstNULL:
case MaxKey:
case MinKey:
break;
case mongo::Bool:
x = 1;
break;
case NumberInt:
x = 4;
break;
case Timestamp:
case mongo::Date:
case NumberDouble:
case NumberLong:
x = 8;
break;
case jstOID:
x = 12;
break;
case Symbol:
case Code:
case mongo::String:
x = valuestrsize() + 4;
break;
case DBRef:
x = valuestrsize() + 4 + 12;
break;
case CodeWScope:
case Object:
case mongo::Array:
x = objsize();
break;
case BinData:
x = valuestrsize() + 4 + 1/*subtype*/;
break;
case RegEx:
{
const char *p = value();
size_t len1 = strlen(p);
p = p + len1 + 1;
size_t len2;
len2 = strlen( p );
x = (int) (len1 + 1 + len2 + 1);
}
break;
default:
{
StringBuilder ss;
ss << "BSONElement: bad type " << (int) type();
string msg = ss.str();
massert(10320 , msg.c_str(),false);
}
}
totalSize = x + fieldNameSize() + 1; // BSONType
return totalSize;
}
inline string BSONElement::toString( bool includeFieldName, bool full ) const {
StringBuilder s;
toString(s, includeFieldName, full);
return s.str();
}
inline void BSONElement::toString(StringBuilder& s, bool includeFieldName, bool full ) const {
if ( includeFieldName && type() != EOO )
s << fieldName() << ": ";
switch ( type() ) {
case EOO:
s << "EOO";
break;
case mongo::Date:
s << "new Date(" << (long long) date() << ')';
break;
case RegEx: {
s << "/" << regex() << '/';
const char *p = regexFlags();
if ( p ) s << p;
}
break;
case NumberDouble:
s.appendDoubleNice( number() );
break;
case NumberLong:
s << _numberLong();
break;
case NumberInt:
s << _numberInt();
break;
case mongo::Bool:
s << ( boolean() ? "true" : "false" );
break;
case Object:
embeddedObject().toString(s, false, full);
break;
case mongo::Array:
embeddedObject().toString(s, true, full);
break;
case Undefined:
s << "undefined";
break;
case jstNULL:
s << "null";
break;
case MaxKey:
s << "MaxKey";
break;
case MinKey:
s << "MinKey";
break;
case CodeWScope:
s << "CodeWScope( "
<< codeWScopeCode() << ", " << codeWScopeObject().toString(false, full) << ")";
break;
case Code:
if ( !full && valuestrsize() > 80 ) {
s.write(valuestr(), 70);
s << "...";
}
else {
s.write(valuestr(), valuestrsize()-1);
}
break;
case Symbol:
case mongo::String:
s << '"';
if ( !full && valuestrsize() > 160 ) {
s.write(valuestr(), 150);
s << "...\"";
}
else {
s.write(valuestr(), valuestrsize()-1);
s << '"';
}
break;
case DBRef:
s << "DBRef('" << valuestr() << "',";
{
mongo::OID *x = (mongo::OID *) (valuestr() + valuestrsize());
s << *x << ')';
}
break;
case jstOID:
s << "ObjectId('";
s << __oid() << "')";
break;
case BinData:
s << "BinData";
if (full) {
int len;
const char* data = binDataClean(len);
s << '(' << binDataType() << ", " << toHex(data, len) << ')';
}
break;
case Timestamp:
s << "Timestamp " << timestampTime() << "|" << timestampInc();
break;
default:
s << "?type=" << type();
break;
}
}
/* return has eoo() true if no match
supports "." notation to reach into embedded objects
*/
inline BSONElement BSONObj::getFieldDotted(const char *name) const {
BSONElement e = getField( name );
if ( e.eoo() ) {
const char *p = strchr(name, '.');
if ( p ) {
string left(name, p-name);
BSONObj sub = getObjectField(left.c_str());
return sub.isEmpty() ? BSONElement() : sub.getFieldDotted(p+1);
}
}
return e;
}
inline BSONObj BSONObj::getObjectField(const char *name) const {
BSONElement e = getField(name);
BSONType t = e.type();
return t == Object || t == Array ? e.embeddedObject() : BSONObj();
}
inline int BSONObj::nFields() const {
int n = 0;
BSONObjIterator i(*this);
while ( i.moreWithEOO() ) {
BSONElement e = i.next();
if ( e.eoo() )
break;
n++;
}
return n;
}
inline BSONObj::BSONObj() {
/* little endian ordering here, but perhaps that is ok regardless as BSON is spec'd
to be little endian external to the system. (i.e. the rest of the implementation of bson,
not this part, fails to support big endian)
*/
static char p[] = { /*size*/5, 0, 0, 0, /*eoo*/0 };
_objdata = p;
}
inline BSONObj BSONElement::Obj() const { return embeddedObjectUserCheck(); }
inline BSONElement BSONElement::operator[] (const string& field) const {
BSONObj o = Obj();
return o[field];
}
inline void BSONObj::elems(vector<BSONElement> &v) const {
BSONObjIterator i(*this);
while( i.more() )
v.push_back(i.next());
}
inline void BSONObj::elems(list<BSONElement> &v) const {
BSONObjIterator i(*this);
while( i.more() )
v.push_back(i.next());
}
template <class T>
void BSONObj::Vals(vector<T>& v) const {
BSONObjIterator i(*this);
while( i.more() ) {
T t;
i.next().Val(t);
v.push_back(t);
}
}
template <class T>
void BSONObj::Vals(list<T>& v) const {
BSONObjIterator i(*this);
while( i.more() ) {
T t;
i.next().Val(t);
v.push_back(t);
}
}
template <class T>
void BSONObj::vals(vector<T>& v) const {
BSONObjIterator i(*this);
while( i.more() ) {
try {
T t;
i.next().Val(t);
v.push_back(t);
}
catch(...) { }
}
}
template <class T>
void BSONObj::vals(list<T>& v) const {
BSONObjIterator i(*this);
while( i.more() ) {
try {
T t;
i.next().Val(t);
v.push_back(t);
}
catch(...) { }
}
}
inline ostream& operator<<( ostream &s, const BSONObj &o ) {
return s << o.toString();
}
inline ostream& operator<<( ostream &s, const BSONElement &e ) {
return s << e.toString();
}
inline StringBuilder& operator<<( StringBuilder &s, const BSONObj &o ) {
o.toString( s );
return s;
}
inline StringBuilder& operator<<( StringBuilder &s, const BSONElement &e ) {
e.toString( s );
return s;
}
inline void BSONElement::Val(BSONObj& v) const { v = Obj(); }
template<typename T>
inline BSONFieldValue<BSONObj> BSONField<T>::query( const char * q , const T& t ) const {
BSONObjBuilder b;
b.append( q , t );
return BSONFieldValue<BSONObj>( _name , b.obj() );
}
}