/** @file jsobj.cpp - BSON implementation http://dochub.mongodb.org/core/bson */ /* Copyright 2009 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #include "mongo/pch.h" #include "mongo/db/jsobj.h" #include #include #include #include #include "mongo/bson/bson_validate.h" #include "mongo/bson/oid.h" #include "mongo/bson/util/atomic_int.h" #include "mongo/db/json.h" #include "mongo/bson/optime.h" #include "mongo/platform/float_utils.h" #include "mongo/util/base64.h" #include "mongo/util/embedded_builder.h" #include "mongo/util/md5.hpp" #include "mongo/util/mongoutils/str.h" #include "mongo/util/startup_test.h" #include "mongo/util/stringutils.h" #include "mongo/util/time_support.h" // make sure our assumptions are valid BOOST_STATIC_ASSERT( sizeof(short) == 2 ); BOOST_STATIC_ASSERT( sizeof(int) == 4 ); BOOST_STATIC_ASSERT( sizeof(long long) == 8 ); BOOST_STATIC_ASSERT( sizeof(double) == 8 ); BOOST_STATIC_ASSERT( sizeof(mongo::Date_t) == 8 ); BOOST_STATIC_ASSERT( sizeof(mongo::OID) == 12 ); namespace mongo { namespace str = mongoutils::str; BSONElement eooElement; GENOIDLabeler GENOID; DateNowLabeler DATENOW; NullLabeler BSONNULL; UndefinedLabeler BSONUndefined; MinKeyLabeler MINKEY; MaxKeyLabeler MAXKEY; // need to move to bson/, but has dependency on base64 so move that to bson/util/ first. inline string BSONElement::jsonString( JsonStringFormat format, bool includeFieldNames, int pretty ) const { int sign; stringstream s; if ( includeFieldNames ) s << '"' << escape( fieldName() ) << "\" : "; switch ( type() ) { case mongo::String: case Symbol: s << '"' << escape( string(valuestr(), valuestrsize()-1) ) << '"'; break; case NumberLong: if (format == TenGen) { s << "NumberLong(" << _numberLong() << ")"; } else { s << "{ \"$numberLong\" : \"" << _numberLong() << "\" }"; } break; case NumberInt: if(format == JS) { s << "NumberInt(" << _numberInt() << ")"; break; } case NumberDouble: if ( number() >= -numeric_limits< double >::max() && number() <= numeric_limits< double >::max() ) { s.precision( 16 ); s << number(); } // This is not valid JSON, but according to RFC-4627, "Numeric values that cannot be // represented as sequences of digits (such as Infinity and NaN) are not permitted." so // we are accepting the fact that if we have such values we cannot output valid JSON. else if ( mongo::isNaN(number()) ) { s << "NaN"; } else if ( mongo::isInf(number(), &sign) ) { s << ( sign == 1 ? "Infinity" : "-Infinity"); } else { StringBuilder ss; ss << "Number " << number() << " cannot be represented in JSON"; string message = ss.str(); massert( 10311 , message.c_str(), false ); } break; case mongo::Bool: s << ( boolean() ? "true" : "false" ); break; case jstNULL: s << "null"; break; case Undefined: if ( format == Strict ) { s << "{ \"$undefined\" : true }"; } else { s << "undefined"; } break; case Object: s << embeddedObject().jsonString( format, pretty ); break; case mongo::Array: { if ( embeddedObject().isEmpty() ) { s << "[]"; break; } s << "[ "; BSONObjIterator i( embeddedObject() ); BSONElement e = i.next(); if ( !e.eoo() ) { int count = 0; while ( 1 ) { if( pretty ) { s << '\n'; for( int x = 0; x < pretty; x++ ) s << " "; } if (strtol(e.fieldName(), 0, 10) > count) { s << "undefined"; } else { s << e.jsonString( format, false, pretty?pretty+1:0 ); e = i.next(); } count++; if ( e.eoo() ) break; s << ", "; } } s << " ]"; break; } case DBRef: { mongo::OID *x = (mongo::OID *) (valuestr() + valuestrsize()); if ( format == TenGen ) s << "Dbref( "; else s << "{ \"$ref\" : "; s << '"' << valuestr() << "\", "; if ( format != TenGen ) s << "\"$id\" : "; s << '"' << *x << "\" "; if ( format == TenGen ) s << ')'; else s << '}'; break; } case jstOID: if ( format == TenGen ) { s << "ObjectId( "; } else { s << "{ \"$oid\" : "; } s << '"' << __oid() << '"'; if ( format == TenGen ) { s << " )"; } else { s << " }"; } break; case BinData: { const int len = *( reinterpret_cast( value() ) ); BinDataType type = BinDataType( *( reinterpret_cast( value() ) + sizeof( int ) ) ); s << "{ \"$binary\" : \""; const char *start = reinterpret_cast( value() ) + sizeof( int ) + 1; base64::encode( s , start , len ); s << "\", \"$type\" : \"" << hex; s.width( 2 ); s.fill( '0' ); s << type << dec; s << "\" }"; break; } case mongo::Date: if (format == Strict) { Date_t d = date(); s << "{ \"$date\" : "; // The two cases in which we cannot convert Date_t::millis to an ISO Date string are // when the date is too large to format (SERVER-13760), and when the date is before // the epoch (SERVER-11273). Since Date_t internally stores millis as an unsigned // long long, despite the fact that it is logically signed (SERVER-8573), this check // handles both the case where Date_t::millis is too large, and the case where // Date_t::millis is negative (before the epoch). if (d.millis > Date_t::maxFormatableDate) { s << "{ \"$numberLong\" : \"" << static_cast(d.millis) << "\" }"; } else { s << "\"" << dateToISOStringLocal(date()) << "\""; } s << " }"; } else { s << "Date( "; if (pretty) { Date_t d = date(); // The two cases in which we cannot convert Date_t::millis to an ISO Date string // are when the date is too large to format (SERVER-13760), and when the date is // before the epoch (SERVER-11273). Since Date_t internally stores millis as an // unsigned long long, despite the fact that it is logically signed // (SERVER-8573), this check handles both the case where Date_t::millis is too // large, and the case where Date_t::millis is negative (before the epoch). if (d.millis > Date_t::maxFormatableDate) { // FIXME: This is not parseable by the shell, since it may not fit in a // float s << d.millis; } else { s << "\"" << dateToISOStringLocal(date()) << "\""; } } else { s << date().asInt64(); } s << " )"; } break; case RegEx: if ( format == Strict ) { s << "{ \"$regex\" : \"" << escape( regex() ); s << "\", \"$options\" : \"" << regexFlags() << "\" }"; } else { s << "/" << escape( regex() , true ) << "/"; // FIXME Worry about alpha order? for ( const char *f = regexFlags(); *f; ++f ) { switch ( *f ) { case 'g': case 'i': case 'm': s << *f; default: break; } } } break; case CodeWScope: { BSONObj scope = codeWScopeObject(); if ( ! scope.isEmpty() ) { s << "{ \"$code\" : \"" << escape(_asCode()) << "\" , " << "\"$scope\" : " << scope.jsonString() << " }"; break; } } case Code: s << "\"" << escape(_asCode()) << "\""; break; case Timestamp: if ( format == TenGen ) { s << "Timestamp( " << ( timestampTime() / 1000 ) << ", " << timestampInc() << " )"; } else { s << "{ \"$timestamp\" : { \"t\" : " << ( timestampTime() / 1000 ) << ", \"i\" : " << timestampInc() << " } }"; } break; case MinKey: s << "{ \"$minKey\" : 1 }"; break; case MaxKey: s << "{ \"$maxKey\" : 1 }"; break; default: StringBuilder ss; ss << "Cannot create a properly formatted JSON string with " << "element: " << toString() << " of type: " << type(); string message = ss.str(); massert( 10312 , message.c_str(), false ); } return s.str(); } int BSONElement::getGtLtOp( int def ) const { const char *fn = fieldName(); if ( fn[0] == '$' && fn[1] ) { if ( fn[2] == 't' ) { if ( fn[1] == 'g' ) { if ( fn[3] == 0 ) return BSONObj::GT; else if ( fn[3] == 'e' && fn[4] == 0 ) return BSONObj::GTE; } else if ( fn[1] == 'l' ) { if ( fn[3] == 0 ) return BSONObj::LT; else if ( fn[3] == 'e' && fn[4] == 0 ) return BSONObj::LTE; } } else if ( fn[1] == 'n' && fn[2] == 'e' ) { if ( fn[3] == 0 ) return BSONObj::NE; if ( fn[3] == 'a' && fn[4] == 'r') // matches anything with $near prefix return BSONObj::opNEAR; } else if ( fn[1] == 'm' ) { if ( fn[2] == 'o' && fn[3] == 'd' && fn[4] == 0 ) return BSONObj::opMOD; if ( fn[2] == 'a' && fn[3] == 'x' && fn[4] == 'D' && fn[5] == 'i' && fn[6] == 's' && fn[7] == 't' && fn[8] == 'a' && fn[9] == 'n' && fn[10] == 'c' && fn[11] == 'e' && fn[12] == 0 ) return BSONObj::opMAX_DISTANCE; } else if ( fn[1] == 't' && fn[2] == 'y' && fn[3] == 'p' && fn[4] == 'e' && fn[5] == 0 ) return BSONObj::opTYPE; else if ( fn[1] == 'i' && fn[2] == 'n' && fn[3] == 0) { return BSONObj::opIN; } else if ( fn[1] == 'n' && fn[2] == 'i' && fn[3] == 'n' && fn[4] == 0 ) return BSONObj::NIN; else if ( fn[1] == 'a' && fn[2] == 'l' && fn[3] == 'l' && fn[4] == 0 ) return BSONObj::opALL; else if ( fn[1] == 's' && fn[2] == 'i' && fn[3] == 'z' && fn[4] == 'e' && fn[5] == 0 ) return BSONObj::opSIZE; else if ( fn[1] == 'e' ) { if ( fn[2] == 'x' && fn[3] == 'i' && fn[4] == 's' && fn[5] == 't' && fn[6] == 's' && fn[7] == 0 ) return BSONObj::opEXISTS; if ( fn[2] == 'l' && fn[3] == 'e' && fn[4] == 'm' && fn[5] == 'M' && fn[6] == 'a' && fn[7] == 't' && fn[8] == 'c' && fn[9] == 'h' && fn[10] == 0 ) return BSONObj::opELEM_MATCH; } else if ( fn[1] == 'r' && fn[2] == 'e' && fn[3] == 'g' && fn[4] == 'e' && fn[5] == 'x' && fn[6] == 0 ) return BSONObj::opREGEX; else if ( fn[1] == 'o' && fn[2] == 'p' && fn[3] == 't' && fn[4] == 'i' && fn[5] == 'o' && fn[6] == 'n' && fn[7] == 's' && fn[8] == 0 ) return BSONObj::opOPTIONS; else if ( fn[1] == 'w' && fn[2] == 'i' && fn[3] == 't' && fn[4] == 'h' && fn[5] == 'i' && fn[6] == 'n' && fn[7] == 0 ) return BSONObj::opWITHIN; else if (str::equals(fn + 1, "geoIntersects")) return BSONObj::opGEO_INTERSECTS; else if (str::equals(fn + 1, "geoNear")) return BSONObj::opNEAR; else if (str::equals(fn + 1, "geoWithin")) return BSONObj::opWITHIN; } return def; } /** transform a BSON array into a vector of BSONElements. we match array # positions with their vector position, and ignore any fields with non-numeric field names. */ std::vector BSONElement::Array() const { chk(mongo::Array); std::vector v; BSONObjIterator i(Obj()); while( i.more() ) { BSONElement e = i.next(); const char *f = e.fieldName(); unsigned u; Status status = parseNumberFromString( f, &u ); if ( status.isOK() ) { verify( u < 1000000 ); if( u >= v.size() ) v.resize(u+1); v[u] = e; } else { // ignore? } } return v; } /* Matcher --------------------------------------*/ // If the element is something like: // a : { $gt : 3 } // we append // a : 3 // else we just append the element. // void appendElementHandlingGtLt(BSONObjBuilder& b, const BSONElement& e) { if ( e.type() == Object ) { BSONElement fe = e.embeddedObject().firstElement(); const char *fn = fe.fieldName(); if ( fn[0] == '$' && fn[1] && fn[2] == 't' ) { b.appendAs(fe, e.fieldName()); return; } } b.append(e); } int getGtLtOp(const BSONElement& e) { if ( e.type() != Object ) return BSONObj::Equality; BSONElement fe = e.embeddedObject().firstElement(); return fe.getGtLtOp(); } FieldCompareResult compareDottedFieldNames( const string& l , const string& r , const LexNumCmp& cmp ) { static int maxLoops = 1024 * 1024; size_t lstart = 0; size_t rstart = 0; for ( int i=0; i 0 ) return RIGHT_BEFORE; lstart = lend + 1; rstart = rend + 1; if ( lstart >= l.size() ) { if ( rstart >= r.size() ) return SAME; return RIGHT_SUBFIELD; } if ( rstart >= r.size() ) return LEFT_SUBFIELD; } log() << "compareDottedFieldNames ERROR l: " << l << " r: " << r << " TOO MANY LOOPS" << endl; verify(0); return SAME; // will never get here } /* BSONObj ------------------------------------------------------------*/ string BSONObj::md5() const { md5digest d; md5_state_t st; md5_init(&st); md5_append( &st , (const md5_byte_t*)_objdata , objsize() ); md5_finish(&st, d); return digestToString( d ); } string BSONObj::jsonString( JsonStringFormat format, int pretty ) const { if ( isEmpty() ) return "{}"; StringBuilder s; s << "{ "; BSONObjIterator i(*this); BSONElement e = i.next(); if ( !e.eoo() ) while ( 1 ) { s << e.jsonString( format, true, pretty?pretty+1:0 ); e = i.next(); if ( e.eoo() ) break; s << ","; if ( pretty ) { s << '\n'; for( int x = 0; x < pretty; x++ ) s << " "; } else { s << " "; } } s << " }"; return s.str(); } bool BSONObj::valid() const { return validateBSON( objdata(), objsize() ).isOK(); } int BSONObj::woCompare(const BSONObj& r, const Ordering &o, bool considerFieldName) const { if ( isEmpty() ) return r.isEmpty() ? 0 : -1; if ( r.isEmpty() ) return 1; BSONObjIterator i(*this); BSONObjIterator j(r); unsigned mask = 1; while ( 1 ) { // so far, equal... BSONElement l = i.next(); BSONElement r = j.next(); if ( l.eoo() ) return r.eoo() ? 0 : -1; if ( r.eoo() ) return 1; int x; { x = l.woCompare( r, considerFieldName ); if( o.descending(mask) ) x = -x; } if ( x != 0 ) return x; mask <<= 1; } return -1; } /* well ordered compare */ int BSONObj::woCompare(const BSONObj &r, const BSONObj &idxKey, bool considerFieldName) const { if ( isEmpty() ) return r.isEmpty() ? 0 : -1; if ( r.isEmpty() ) return 1; bool ordered = !idxKey.isEmpty(); BSONObjIterator i(*this); BSONObjIterator j(r); BSONObjIterator k(idxKey); while ( 1 ) { // so far, equal... BSONElement l = i.next(); BSONElement r = j.next(); BSONElement o; if ( ordered ) o = k.next(); if ( l.eoo() ) return r.eoo() ? 0 : -1; if ( r.eoo() ) return 1; int x; /* if( ordered && o.type() == String && strcmp(o.valuestr(), "ascii-proto") == 0 && l.type() == String && r.type() == String ) { // note: no negative support yet, as this is just sort of a POC x = _stricmp(l.valuestr(), r.valuestr()); } else*/ { x = l.woCompare( r, considerFieldName ); if ( ordered && o.number() < 0 ) x = -x; } if ( x != 0 ) return x; } return -1; } BSONObj staticNull = fromjson( "{'':null}" ); BSONObj makeUndefined() { BSONObjBuilder b; b.appendUndefined( "" ); return b.obj(); } BSONObj staticUndefined = makeUndefined(); /* well ordered compare */ int BSONObj::woSortOrder(const BSONObj& other, const BSONObj& sortKey , bool useDotted ) const { if ( isEmpty() ) return other.isEmpty() ? 0 : -1; if ( other.isEmpty() ) return 1; uassert( 10060 , "woSortOrder needs a non-empty sortKey" , ! sortKey.isEmpty() ); BSONObjIterator i(sortKey); while ( 1 ) { BSONElement f = i.next(); if ( f.eoo() ) return 0; BSONElement l = useDotted ? getFieldDotted( f.fieldName() ) : getField( f.fieldName() ); if ( l.eoo() ) l = staticNull.firstElement(); BSONElement r = useDotted ? other.getFieldDotted( f.fieldName() ) : other.getField( f.fieldName() ); if ( r.eoo() ) r = staticNull.firstElement(); int x = l.woCompare( r, false ); if ( f.number() < 0 ) x = -x; if ( x != 0 ) return x; } return -1; } bool BSONObj::isPrefixOf( const BSONObj& otherObj ) const { BSONObjIterator a( *this ); BSONObjIterator b( otherObj ); while ( a.more() && b.more() ) { BSONElement x = a.next(); BSONElement y = b.next(); if ( x != y ) return false; } return ! a.more(); } bool BSONObj::isFieldNamePrefixOf( const BSONObj& otherObj ) const { BSONObjIterator a( *this ); BSONObjIterator b( otherObj ); while ( a.more() && b.more() ) { BSONElement x = a.next(); BSONElement y = b.next(); if ( ! str::equals( x.fieldName() , y.fieldName() ) ) { return false; } } return ! a.more(); } template void _getFieldsDotted( const BSONObj* obj, const StringData& name, BSONElementColl &ret, bool expandLastArray ) { BSONElement e = obj->getField( name ); if ( e.eoo() ) { size_t idx = name.find( '.' ); if ( idx != string::npos ) { StringData left = name.substr( 0, idx ); StringData next = name.substr( idx + 1, name.size() ); BSONElement e = obj->getField( left ); if (e.type() == Object) { e.embeddedObject().getFieldsDotted(next, ret, expandLastArray ); } else if (e.type() == Array) { bool allDigits = false; if ( next.size() > 0 && isdigit( next[0] ) ) { unsigned temp = 1; while ( temp < next.size() && isdigit( next[temp] ) ) temp++; allDigits = temp == next.size() || next[temp] == '.'; } if (allDigits) { e.embeddedObject().getFieldsDotted(next, ret, expandLastArray ); } else { BSONObjIterator i(e.embeddedObject()); while ( i.more() ) { BSONElement e2 = i.next(); if (e2.type() == Object || e2.type() == Array) e2.embeddedObject().getFieldsDotted(next, ret, expandLastArray ); } } } else { // do nothing: no match } } } else { if (e.type() == Array && expandLastArray) { BSONObjIterator i(e.embeddedObject()); while ( i.more() ) ret.insert(i.next()); } else { ret.insert(e); } } } void BSONObj::getFieldsDotted(const StringData& name, BSONElementSet &ret, bool expandLastArray ) const { _getFieldsDotted( this, name, ret, expandLastArray ); } void BSONObj::getFieldsDotted(const StringData& name, BSONElementMSet &ret, bool expandLastArray ) const { _getFieldsDotted( this, name, ret, expandLastArray ); } BSONElement BSONObj::getFieldDottedOrArray(const char *&name) const { const char *p = strchr(name, '.'); BSONElement sub; if ( p ) { sub = getField( string(name, p-name) ); name = p + 1; } else { sub = getField( name ); name = name + strlen(name); } if ( sub.eoo() ) return eooElement; else if ( sub.type() == Array || name[0] == '\0' ) return sub; else if ( sub.type() == Object ) return sub.embeddedObject().getFieldDottedOrArray( name ); else return eooElement; } BSONObj BSONObj::extractFieldsUnDotted(const BSONObj& pattern) const { BSONObjBuilder b; BSONObjIterator i(pattern); while ( i.moreWithEOO() ) { BSONElement e = i.next(); if ( e.eoo() ) break; BSONElement x = getField(e.fieldName()); if ( !x.eoo() ) b.appendAs(x, ""); } return b.obj(); } BSONObj BSONObj::extractFields(const BSONObj& pattern , bool fillWithNull ) const { BSONObjBuilder b(32); // scanandorder.h can make a zillion of these, so we start the allocation very small BSONObjIterator i(pattern); while ( i.moreWithEOO() ) { BSONElement e = i.next(); if ( e.eoo() ) break; BSONElement x = getFieldDotted(e.fieldName()); if ( ! x.eoo() ) b.appendAs( x, e.fieldName() ); else if ( fillWithNull ) b.appendNull( e.fieldName() ); } return b.obj(); } BSONObj BSONObj::filterFieldsUndotted( const BSONObj &filter, bool inFilter ) const { BSONObjBuilder b; BSONObjIterator i( *this ); while( i.moreWithEOO() ) { BSONElement e = i.next(); if ( e.eoo() ) break; BSONElement x = filter.getField( e.fieldName() ); if ( ( x.eoo() && !inFilter ) || ( !x.eoo() && inFilter ) ) b.append( e ); } return b.obj(); } BSONElement BSONObj::getFieldUsingIndexNames(const StringData& fieldName, const BSONObj &indexKey) const { BSONObjIterator i( indexKey ); int j = 0; while( i.moreWithEOO() ) { BSONElement f = i.next(); if ( f.eoo() ) return BSONElement(); if ( f.fieldName() == fieldName ) break; ++j; } BSONObjIterator k( *this ); while( k.moreWithEOO() ) { BSONElement g = k.next(); if ( g.eoo() ) return BSONElement(); if ( j == 0 ) { return g; } --j; } return BSONElement(); } /* grab names of all the fields in this object */ int BSONObj::getFieldNames(set& fields) const { int n = 0; BSONObjIterator i(*this); while ( i.moreWithEOO() ) { BSONElement e = i.next(); if ( e.eoo() ) break; fields.insert(e.fieldName()); n++; } return n; } /* note: addFields always adds _id even if not specified returns n added not counting _id unless requested. */ int BSONObj::addFields(BSONObj& from, set& fields) { verify( isEmpty() && !isOwned() ); /* partial implementation for now... */ BSONObjBuilder b; int N = fields.size(); int n = 0; BSONObjIterator i(from); bool gotId = false; while ( i.moreWithEOO() ) { BSONElement e = i.next(); const char *fname = e.fieldName(); if ( fields.count(fname) ) { b.append(e); ++n; gotId = gotId || strcmp(fname, "_id")==0; if ( n == N && gotId ) break; } else if ( strcmp(fname, "_id")==0 ) { b.append(e); gotId = true; if ( n == N && gotId ) break; } } if ( n ) { *this = b.obj(); } return n; } bool BSONObj::couldBeArray() const { BSONObjIterator i( *this ); int index = 0; while( i.moreWithEOO() ){ BSONElement e = i.next(); if( e.eoo() ) break; // TODO: If actually important, may be able to do int->char* much faster if( strcmp( e.fieldName(), ((string)( str::stream() << index )).c_str() ) != 0 ) return false; index++; } return true; } BSONObj BSONObj::clientReadable() const { BSONObjBuilder b; BSONObjIterator i( *this ); while( i.moreWithEOO() ) { BSONElement e = i.next(); if ( e.eoo() ) break; switch( e.type() ) { case MinKey: { BSONObjBuilder m; m.append( "$minElement", 1 ); b.append( e.fieldName(), m.done() ); break; } case MaxKey: { BSONObjBuilder m; m.append( "$maxElement", 1 ); b.append( e.fieldName(), m.done() ); break; } default: b.append( e ); } } return b.obj(); } BSONObj BSONObj::replaceFieldNames( const BSONObj &names ) const { BSONObjBuilder b; BSONObjIterator i( *this ); BSONObjIterator j( names ); BSONElement f = j.moreWithEOO() ? j.next() : BSONObj().firstElement(); while( i.moreWithEOO() ) { BSONElement e = i.next(); if ( e.eoo() ) break; if ( !f.eoo() ) { b.appendAs( e, f.fieldName() ); f = j.next(); } else { b.append( e ); } } return b.obj(); } Status BSONObj::_okForStorage(bool root, bool deep) const { BSONObjIterator i( *this ); // The first field is special in the case of a DBRef where the first field must be $ref bool first = true; while ( i.more() ) { BSONElement e = i.next(); const char* name = e.fieldName(); // Cannot start with "$", unless dbref which must start with ($ref, $id) if (str::startsWith(name, '$')) { if ( first && // $ref is a collection name and must be a String str::equals(name, "$ref") && e.type() == String && str::equals(i.next().fieldName(), "$id") ) { first = false; // keep inspecting fields for optional "$db" e = i.next(); name = e.fieldName(); // "" if eoo() // optional $db field must be a String if (str::equals(name, "$db") && e.type() == String) { continue; //this element is fine, so continue on to siblings (if any more) } // Can't start with a "$", all other checks are done below (outside if blocks) if (str::startsWith(name, '$')) { return Status(ErrorCodes::DollarPrefixedFieldName, str::stream() << name << " is not valid for storage."); } } else { // not an okay, $ prefixed field name. return Status(ErrorCodes::DollarPrefixedFieldName, str::stream() << name << " is not valid for storage."); } } // Do not allow "." in the field name if (strchr(name, '.')) { return Status(ErrorCodes::DottedFieldName, str::stream() << name << " is not valid for storage."); } // (SERVER-9502) Do not allow storing an _id field with a RegEx type or // Array type in a root document if (root && (e.type() == RegEx || e.type() == Array || e.type() == Undefined) && str::equals(name,"_id")) { return Status(ErrorCodes::InvalidIdField, str::stream() << name << " is not valid for storage because it is of type " << typeName(e.type())); } if ( deep && e.mayEncapsulate() ) { switch ( e.type() ) { case Object: case Array: { Status s = e.embeddedObject()._okForStorage(false, true); // TODO: combine field names for better error messages if ( ! s.isOK() ) return s; } break; case CodeWScope: { Status s = e.codeWScopeObject()._okForStorage(false, true); // TODO: combine field names for better error messages if ( ! s.isOK() ) return s; } break; default: uassert( 12579, "unhandled cases in BSONObj okForStorage" , 0 ); } } // After we have processed one field, we are no longer on the first field first = false; } return Status::OK(); } void BSONObj::dump() const { LogstreamBuilder builder = out(); builder << hex; const char *p = objdata(); for ( int i = 0; i < objsize(); i++ ) { builder << i << '\t' << ( 0xff & ( (unsigned) *p ) ); if ( *p >= 'A' && *p <= 'z' ) builder << '\t' << *p; builder << endl; p++; } } void nested2dotted(BSONObjBuilder& b, const BSONObj& obj, const string& base) { BSONObjIterator it(obj); while (it.more()) { BSONElement e = it.next(); if (e.type() == Object) { string newbase = base + e.fieldName() + "."; nested2dotted(b, e.embeddedObject(), newbase); } else { string newbase = base + e.fieldName(); b.appendAs(e, newbase); } } } void dotted2nested(BSONObjBuilder& b, const BSONObj& obj) { //use map to sort fields BSONMap sorted = bson2map(obj); EmbeddedBuilder eb(&b); for(BSONMap::const_iterator it=sorted.begin(); it!=sorted.end(); ++it) { eb.appendAs(it->second, it->first); } eb.done(); } /*-- test things ----------------------------------------------------*/ #pragma pack(1) struct MaxKeyData { MaxKeyData() { totsize=7; maxkey=MaxKey; name=0; eoo=EOO; } int totsize; char maxkey; char name; char eoo; } maxkeydata; BSONObj maxKey((const char *) &maxkeydata); struct MinKeyData { MinKeyData() { totsize=7; minkey=MinKey; name=0; eoo=EOO; } int totsize; char minkey; char name; char eoo; } minkeydata; BSONObj minKey((const char *) &minkeydata); /* struct JSObj0 { JSObj0() { totsize = 5; eoo = EOO; } int totsize; char eoo; } js0; */ #pragma pack() struct BsonUnitTest : public StartupTest { void testRegex() { BSONObjBuilder b; b.appendRegex("x", "foo"); BSONObj o = b.done(); BSONObjBuilder c; c.appendRegex("x", "goo"); BSONObj p = c.done(); verify( !o.binaryEqual( p ) ); verify( o.woCompare( p ) < 0 ); } void testoid() { OID id; id.init(); // sleepsecs(3); OID b; // goes with sleep above... // b.init(); // verify( memcmp(id.getData(), b.getData(), 12) < 0 ); b.init( id.str() ); verify( b == id ); } void testbounds() { BSONObj l , r; { BSONObjBuilder b; b.append( "x" , numeric_limits::max() ); l = b.obj(); } { BSONObjBuilder b; b.append( "x" , numeric_limits::max() ); r = b.obj(); } verify( l.woCompare( r ) < 0 ); verify( r.woCompare( l ) > 0 ); { BSONObjBuilder b; b.append( "x" , numeric_limits::max() ); l = b.obj(); } verify( l.woCompare( r ) < 0 ); verify( r.woCompare( l ) > 0 ); } void testorder() { { BSONObj x,y,z; { BSONObjBuilder b; b.append( "x" , (long long)2 ); x = b.obj(); } { BSONObjBuilder b; b.append( "x" , (int)3 ); y = b.obj(); } { BSONObjBuilder b; b.append( "x" , (long long)4 ); z = b.obj(); } verify( x.woCompare( y ) < 0 ); verify( x.woCompare( z ) < 0 ); verify( y.woCompare( x ) > 0 ); verify( z.woCompare( x ) > 0 ); verify( y.woCompare( z ) < 0 ); verify( z.woCompare( y ) > 0 ); } { BSONObj ll,d,i,n,u; { BSONObjBuilder b; b.append( "x" , (long long)2 ); ll = b.obj(); } { BSONObjBuilder b; b.append( "x" , (double)2 ); d = b.obj(); } { BSONObjBuilder b; b.append( "x" , (int)2 ); i = b.obj(); } { BSONObjBuilder b; b.appendNull( "x" ); n = b.obj(); } { BSONObjBuilder b; u = b.obj(); } verify( ll.woCompare( u ) == d.woCompare( u ) ); verify( ll.woCompare( u ) == i.woCompare( u ) ); BSONObj k = BSON( "x" << 1 ); verify( ll.woCompare( u , k ) == d.woCompare( u , k ) ); verify( ll.woCompare( u , k ) == i.woCompare( u , k ) ); verify( u.woCompare( ll ) == u.woCompare( d ) ); verify( u.woCompare( ll ) == u.woCompare( i ) ); verify( u.woCompare( ll , k ) == u.woCompare( d , k ) ); verify( u.woCompare( ll , k ) == u.woCompare( d , k ) ); verify( i.woCompare( n ) == d.woCompare( n ) ); verify( ll.woCompare( n ) == d.woCompare( n ) ); verify( ll.woCompare( n ) == i.woCompare( n ) ); verify( ll.woCompare( n , k ) == d.woCompare( n , k ) ); verify( ll.woCompare( n , k ) == i.woCompare( n , k ) ); verify( n.woCompare( ll ) == n.woCompare( d ) ); verify( n.woCompare( ll ) == n.woCompare( i ) ); verify( n.woCompare( ll , k ) == n.woCompare( d , k ) ); verify( n.woCompare( ll , k ) == n.woCompare( d , k ) ); } { BSONObj l,r; { BSONObjBuilder b; b.append( "x" , "eliot" ); l = b.obj(); } { BSONObjBuilder b; b.appendSymbol( "x" , "eliot" ); r = b.obj(); } verify( l.woCompare( r ) == 0 ); verify( r.woCompare( l ) == 0 ); } } void run() { testRegex(); BSONObjBuilder A,B,C; A.append("x", 2); B.append("x", 2.0); C.append("x", 2.1); BSONObj a = A.done(); BSONObj b = B.done(); BSONObj c = C.done(); verify( !a.binaryEqual( b ) ); // comments on operator== int cmp = a.woCompare(b); verify( cmp == 0 ); cmp = a.woCompare(c); verify( cmp < 0 ); testoid(); testbounds(); testorder(); } } bson_unittest; Labeler::Label GT( "$gt" ); Labeler::Label GTE( "$gte" ); Labeler::Label LT( "$lt" ); Labeler::Label LTE( "$lte" ); Labeler::Label NE( "$ne" ); Labeler::Label NIN( "$nin" ); Labeler::Label BSIZE( "$size" ); void BSONObjBuilder::appendMinForType( const StringData& fieldName , int t ) { switch ( t ) { // Shared canonical types case NumberInt: case NumberDouble: case NumberLong: append( fieldName , - numeric_limits::max() ); return; case Symbol: case String: append( fieldName , "" ); return; case Date: // min varies with V0 and V1 indexes, so we go one type lower. appendBool(fieldName, true); //appendDate( fieldName , numeric_limits::min() ); return; case Timestamp: // TODO integrate with Date SERVER-3304 appendTimestamp( fieldName , 0 ); return; case Undefined: // shared with EOO appendUndefined( fieldName ); return; // Separate canonical types case MinKey: appendMinKey( fieldName ); return; case MaxKey: appendMaxKey( fieldName ); return; case jstOID: { OID o; memset(&o, 0, sizeof(o)); appendOID( fieldName , &o); return; } case Bool: appendBool( fieldName , false); return; case jstNULL: appendNull( fieldName ); return; case Object: append( fieldName , BSONObj() ); return; case Array: appendArray( fieldName , BSONObj() ); return; case BinData: appendBinData( fieldName , 0 , BinDataGeneral , (const char *) 0 ); return; case RegEx: appendRegex( fieldName , "" ); return; case DBRef: { OID o; memset(&o, 0, sizeof(o)); appendDBRef( fieldName , "" , o ); return; } case Code: appendCode( fieldName , "" ); return; case CodeWScope: appendCodeWScope( fieldName , "" , BSONObj() ); return; }; log() << "type not supported for appendMinElementForType: " << t << endl; uassert( 10061 , "type not supported for appendMinElementForType" , false ); } void BSONObjBuilder::appendMaxForType( const StringData& fieldName , int t ) { switch ( t ) { // Shared canonical types case NumberInt: case NumberDouble: case NumberLong: append( fieldName , numeric_limits::max() ); return; case Symbol: case String: appendMinForType( fieldName, Object ); return; case Date: appendDate( fieldName , numeric_limits::max() ); return; case Timestamp: // TODO integrate with Date SERVER-3304 append( fieldName , OpTime::max() ); return; case Undefined: // shared with EOO appendUndefined( fieldName ); return; // Separate canonical types case MinKey: appendMinKey( fieldName ); return; case MaxKey: appendMaxKey( fieldName ); return; case jstOID: { OID o; memset(&o, 0xFF, sizeof(o)); appendOID( fieldName , &o); return; } case Bool: appendBool( fieldName , true ); return; case jstNULL: appendNull( fieldName ); return; case Object: appendMinForType( fieldName, Array ); return; case Array: appendMinForType( fieldName, BinData ); return; case BinData: appendMinForType( fieldName, jstOID ); return; case RegEx: appendMinForType( fieldName, DBRef ); return; case DBRef: appendMinForType( fieldName, Code ); return; case Code: appendMinForType( fieldName, CodeWScope ); return; case CodeWScope: // This upper bound may change if a new bson type is added. appendMinForType( fieldName , MaxKey ); return; } log() << "type not supported for appendMaxElementForType: " << t << endl; uassert( 14853 , "type not supported for appendMaxElementForType" , false ); } bool fieldsMatch(const BSONObj& lhs, const BSONObj& rhs) { BSONObjIterator l(lhs); BSONObjIterator r(rhs); while (l.more() && r.more()){ if (strcmp(l.next().fieldName(), r.next().fieldName())) { return false; } } return !(l.more() || r.more()); // false if lhs and rhs have diff nFields() } /** Compare two bson elements, provided as const char *'s, by field name. */ class BSONIteratorSorted::ElementFieldCmp { public: ElementFieldCmp( bool isArray ); bool operator()( const char *s1, const char *s2 ) const; private: LexNumCmp _cmp; }; BSONIteratorSorted::ElementFieldCmp::ElementFieldCmp( bool isArray ) : _cmp( !isArray ) { } bool BSONIteratorSorted::ElementFieldCmp::operator()( const char *s1, const char *s2 ) const { // Skip the type byte and compare field names. return _cmp( s1 + 1, s2 + 1 ); } BSONIteratorSorted::BSONIteratorSorted( const BSONObj &o, const ElementFieldCmp &cmp ) { _nfields = o.nFields(); _fields = new const char*[_nfields]; int x = 0; BSONObjIterator i( o ); while ( i.more() ) { _fields[x++] = i.next().rawdata(); verify( _fields[x-1] ); } verify( x == _nfields ); std::sort( _fields , _fields + _nfields , cmp ); _cur = 0; } BSONObjIteratorSorted::BSONObjIteratorSorted( const BSONObj &object ) : BSONIteratorSorted( object, ElementFieldCmp( false ) ) { } BSONArrayIteratorSorted::BSONArrayIteratorSorted( const BSONArray &array ) : BSONIteratorSorted( array, ElementFieldCmp( true ) ) { } bool BSONObjBuilder::appendAsNumber( const StringData& fieldName , const string& data ) { if ( data.size() == 0 || data == "-" || data == ".") return false; unsigned int pos=0; if ( data[0] == '-' ) pos++; bool hasDec = false; for ( ; pos( data ); append( fieldName , num ); return true; } catch(boost::bad_lexical_cast &) { return false; } } /* take a BSONType and return the name of that type as a char* */ const char* typeName (BSONType type) { switch (type) { case MinKey: return "MinKey"; case EOO: return "EOO"; case NumberDouble: return "NumberDouble"; case String: return "String"; case Object: return "Object"; case Array: return "Array"; case BinData: return "BinaryData"; case Undefined: return "Undefined"; case jstOID: return "OID"; case Bool: return "Bool"; case Date: return "Date"; case jstNULL: return "NULL"; case RegEx: return "RegEx"; case DBRef: return "DBRef"; case Code: return "Code"; case Symbol: return "Symbol"; case CodeWScope: return "CodeWScope"; case NumberInt: return "NumberInt32"; case Timestamp: return "Timestamp"; case NumberLong: return "NumberLong64"; // JSTypeMax doesn't make sense to turn into a string; overlaps with highest-valued type case MaxKey: return "MaxKey"; default: return "Invalid"; } } } // namespace mongo