/**
* Copyright (C) 2014 MongoDB 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.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
#include "mongo/bson/bsonelement.h"
#include
#include
#include "mongo/base/compare_numbers.h"
#include "mongo/base/data_cursor.h"
#include "mongo/db/jsobj.h"
#include "mongo/util/base64.h"
#include "mongo/util/hex.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
namespace str = mongoutils::str;
using std::dec;
using std::hex;
using std::string;
string BSONElement::jsonString(JsonStringFormat format, bool includeFieldNames, int pretty) const {
std::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() >= -std::numeric_limits::max() &&
number() <= std::numeric_limits::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 (std::isnan(number())) {
s << "NaN";
} else if (std::isinf(number())) {
s << (number() > 0 ? "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: {
if (format == TenGen)
s << "Dbref( ";
else
s << "{ \"$ref\" : ";
s << '"' << valuestr() << "\", ";
if (format != TenGen)
s << "\"$id\" : ";
s << '"' << mongo::OID::from(valuestr() + valuestrsize()) << "\" ";
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: {
ConstDataCursor reader(value());
const int len = reader.readAndAdvance>();
BinDataType type = static_cast(reader.readAndAdvance());
s << "{ \"$binary\" : \"";
base64::encode(s, reader.view(), 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.isFormattable()) {
s << "\"" << dateToISOStringLocal(date()) << "\"";
} else {
s << "{ \"$numberLong\" : \"" << d.toMillisSinceEpoch() << "\" }";
}
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.isFormattable()) {
s << "\"" << dateToISOStringLocal(date()) << "\"";
} else {
// FIXME: This is not parseable by the shell, since it may not fit in a
// float
s << d.toMillisSinceEpoch();
}
} 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 bsonTimestamp:
if (format == TenGen) {
s << "Timestamp( " << durationCount(timestampTime().toDurationSinceEpoch())
<< ", " << timestampInc() << " )";
} else {
s << "{ \"$timestamp\" : { \"t\" : "
<< durationCount(timestampTime().toDurationSinceEpoch())
<< ", \"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;
}
/* wo = "well ordered"
note: (mongodb related) : this can only change in behavior when index version # changes
*/
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;
}
BSONObj BSONElement::embeddedObjectUserCheck() const {
if (MONGO_likely(isABSONObj()))
return BSONObj(value());
std::stringstream ss;
ss << "invalid parameter: expected an object (" << fieldName() << ")";
uasserted(10065, ss.str());
return BSONObj(); // never reachable
}
BSONObj BSONElement::embeddedObject() const {
verify(isABSONObj());
return BSONObj(value());
}
BSONObj BSONElement::codeWScopeObject() const {
verify(type() == CodeWScope);
int strSizeWNull = ConstDataView(value() + 4).read>();
return BSONObj(value() + 4 + 4 + strSizeWNull);
}
// wrap this element up as a singleton object.
BSONObj BSONElement::wrap() const {
BSONObjBuilder b(size() + 6);
b.append(*this);
return b.obj();
}
BSONObj BSONElement::wrap(StringData newName) const {
BSONObjBuilder b(size() + 6 + newName.size());
b.appendAs(*this, newName);
return b.obj();
}
void BSONElement::Val(BSONObj& v) const {
v = Obj();
}
BSONObj BSONElement::Obj() const {
return embeddedObjectUserCheck();
}
BSONElement BSONElement::operator[](const std::string& field) const {
BSONObj o = Obj();
return o[field];
}
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 bsonTimestamp:
case mongo::Date:
case NumberDouble:
case NumberLong:
x = 8;
break;
case jstOID:
x = OID::kOIDSize;
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) : strnlen(p, remain);
massert(10318, "Invalid regex string", maxLen == -1 || len1 < size_t(remain));
p = p + len1 + 1;
size_t len2;
if (maxLen == -1)
len2 = strlen(p);
else {
size_t x = remain - len1 - 1;
verify(x <= 0x7fffffff);
len2 = strnlen(p, x);
massert(10319, "Invalid regex options string", len2 < x);
}
x = (int)(len1 + 1 + len2 + 1);
} break;
default: {
StringBuilder ss;
ss << "BSONElement: bad type " << (int)type();
std::string msg = ss.str();
massert(13655, msg.c_str(), false);
}
}
totalSize = x + fieldNameSize() + 1; // BSONType
return totalSize;
}
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 bsonTimestamp:
case mongo::Date:
case NumberDouble:
case NumberLong:
x = 8;
break;
case jstOID:
x = OID::kOIDSize;
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();
std::string msg = ss.str();
massert(10320, msg.c_str(), false);
}
}
totalSize = x + fieldNameSize() + 1; // BSONType
return totalSize;
}
std::string BSONElement::toString(bool includeFieldName, bool full) const {
StringBuilder s;
toString(s, includeFieldName, full);
return s.str();
}
void BSONElement::toString(StringBuilder& s, bool includeFieldName, bool full, int depth) const {
if (depth > BSONObj::maxToStringRecursionDepth) {
// check if we want the full/complete string
if (full) {
StringBuilder s;
s << "Reached maximum recursion depth of ";
s << BSONObj::maxToStringRecursionDepth;
uassert(16150, s.str(), full != true);
}
s << "...";
return;
}
if (includeFieldName && type() != EOO)
s << fieldName() << ": ";
switch (type()) {
case EOO:
s << "EOO";
break;
case mongo::Date:
s << "new Date(" << date().toMillisSinceEpoch() << ')';
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, depth + 1);
break;
case mongo::Array:
embeddedObject().toString(s, true, full, depth + 1);
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() << "',";
s << mongo::OID::from(valuestr() + valuestrsize()) << ')';
break;
case jstOID:
s << "ObjectId('";
s << __oid() << "')";
break;
case BinData:
s << "BinData(" << binDataType() << ", ";
{
int len;
const char* data = binDataClean(len);
if (!full && len > 80) {
s << toHex(data, 70) << "...)";
} else {
s << toHex(data, len) << ")";
}
}
break;
case bsonTimestamp:
s << "Timestamp " << timestampTime().toMillisSinceEpoch() << "|" << timestampInc();
break;
default:
s << "?type=" << type();
break;
}
}
std::string BSONElement::_asCode() const {
switch (type()) {
case mongo::String:
case Code:
return std::string(valuestr(), valuestrsize() - 1);
case CodeWScope:
return std::string(codeWScopeCode(),
ConstDataView(valuestr()).read>() - 1);
default:
log() << "can't convert type: " << (int)(type()) << " to code" << std::endl;
}
uassert(10062, "not code", 0);
return "";
}
std::ostream& operator<<(std::ostream& s, const BSONElement& e) {
return s << e.toString();
}
StringBuilder& operator<<(StringBuilder& s, const BSONElement& e) {
e.toString(s);
return s;
}
template <>
bool BSONElement::coerce(std::string* out) const {
if (type() != mongo::String)
return false;
*out = String();
return true;
}
template <>
bool BSONElement::coerce(int* out) const {
if (!isNumber())
return false;
*out = numberInt();
return true;
}
template <>
bool BSONElement::coerce(long long* out) const {
if (!isNumber())
return false;
*out = numberLong();
return true;
}
template <>
bool BSONElement::coerce(double* out) const {
if (!isNumber())
return false;
*out = numberDouble();
return true;
}
template <>
bool BSONElement::coerce(bool* out) const {
*out = trueValue();
return true;
}
template <>
bool BSONElement::coerce>(std::vector* out) const {
if (type() != mongo::Array)
return false;
return Obj().coerceVector(out);
}
template
bool BSONObj::coerceVector(std::vector* out) const {
BSONObjIterator i(*this);
while (i.more()) {
BSONElement e = i.next();
T t;
if (!e.coerce(&t))
return false;
out->push_back(t);
}
return true;
}
// used by jsonString()
std::string escape(const std::string& s, bool escape_slash) {
StringBuilder ret;
for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) {
switch (*i) {
case '"':
ret << "\\\"";
break;
case '\\':
ret << "\\\\";
break;
case '/':
ret << (escape_slash ? "\\/" : "/");
break;
case '\b':
ret << "\\b";
break;
case '\f':
ret << "\\f";
break;
case '\n':
ret << "\\n";
break;
case '\r':
ret << "\\r";
break;
case '\t':
ret << "\\t";
break;
default:
if (*i >= 0 && *i <= 0x1f) {
// TODO: these should be utf16 code-units not bytes
char c = *i;
ret << "\\u00" << toHexLower(&c, 1);
} else {
ret << *i;
}
}
}
return ret.str();
}
/**
* l and r must be same canonicalType when called.
*/
int compareElementValues(const BSONElement& l, const BSONElement& r) {
int f;
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 bsonTimestamp:
// 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:
// Signed comparisons for Dates.
{
const Date_t a = l.Date();
const Date_t b = r.Date();
if (a < b)
return -1;
return a == b ? 0 : 1;
}
case NumberInt: {
// All types can precisely represent all NumberInts, so it is safe to simply convert to
// whatever rhs's type is.
switch (r.type()) {
case NumberInt:
return compareInts(l._numberInt(), r._numberInt());
case NumberLong:
return compareLongs(l._numberInt(), r._numberLong());
case NumberDouble:
return compareDoubles(l._numberInt(), r._numberDouble());
default:
invariant(false);
}
}
case NumberLong: {
switch (r.type()) {
case NumberLong:
return compareLongs(l._numberLong(), r._numberLong());
case NumberInt:
return compareLongs(l._numberLong(), r._numberInt());
case NumberDouble:
return compareLongToDouble(l._numberLong(), r._numberDouble());
default:
invariant(false);
}
}
case NumberDouble: {
switch (r.type()) {
case NumberDouble:
return compareDoubles(l._numberDouble(), r._numberDouble());
case NumberInt:
return compareDoubles(l._numberDouble(), r._numberInt());
case NumberLong:
return compareDoubleToLong(l._numberDouble(), r._numberLong());
default:
invariant(false);
}
}
case jstOID:
return memcmp(l.value(), r.value(), OID::kOIDSize);
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 = std::min(lsz, rsz);
int res = memcmp(l.valuestr(), r.valuestr(), common);
if (res)
return res;
// longer std::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 /*+1 for subtype byte*/);
}
case RegEx: {
int c = strcmp(l.regex(), r.regex());
if (c)
return c;
return strcmp(l.regexFlags(), r.regexFlags());
}
case CodeWScope: {
int cmp = StringData(l.codeWScopeCode(), l.codeWScopeCodeLen() - 1)
.compare(StringData(r.codeWScopeCode(), r.codeWScopeCodeLen() - 1));
if (cmp)
return cmp;
return l.codeWScopeObject().woCompare(r.codeWScopeObject());
}
default:
verify(false);
}
return -1;
}
size_t BSONElement::Hasher::operator()(const BSONElement& elem) const {
size_t hash = 0;
boost::hash_combine(hash, elem.canonicalType());
const StringData fieldName = elem.fieldNameStringData();
if (!fieldName.empty()) {
boost::hash_combine(hash, StringData::Hasher()(fieldName));
}
switch (elem.type()) {
// Order of types is the same as in compareElementValues().
case mongo::EOO:
case mongo::Undefined:
case mongo::jstNULL:
case mongo::MaxKey:
case mongo::MinKey:
// These are valueless types
break;
case mongo::Bool:
boost::hash_combine(hash, elem.boolean());
break;
case mongo::bsonTimestamp:
boost::hash_combine(hash, elem.timestamp().asULL());
break;
case mongo::Date:
boost::hash_combine(hash, elem.date().asInt64());
break;
case mongo::NumberDouble:
case mongo::NumberLong:
case mongo::NumberInt: {
// This converts all numbers to doubles, which ignores the low-order bits of
// NumberLongs > 2**53, but that is ok since the hash will still be the same for
// equal numbers and is still likely to be different for different numbers.
// SERVER-16851
const double dbl = elem.numberDouble();
if (std::isnan(dbl)) {
boost::hash_combine(hash, std::numeric_limits::quiet_NaN());
} else {
boost::hash_combine(hash, dbl);
}
break;
}
case mongo::jstOID:
elem.__oid().hash_combine(hash);
break;
case mongo::Code:
case mongo::Symbol:
case mongo::String:
boost::hash_combine(hash, StringData::Hasher()(elem.valueStringData()));
break;
case mongo::Object:
case mongo::Array:
boost::hash_combine(hash, BSONObj::Hasher()(elem.embeddedObject()));
break;
case mongo::DBRef:
case mongo::BinData:
// All bytes of the value are required to be identical.
boost::hash_combine(hash,
StringData::Hasher()(StringData(elem.value(), elem.valuesize())));
break;
case mongo::RegEx:
boost::hash_combine(hash, StringData::Hasher()(elem.regex()));
boost::hash_combine(hash, StringData::Hasher()(elem.regexFlags()));
break;
case mongo::CodeWScope: {
boost::hash_combine(
hash,
StringData::Hasher()(StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen())));
boost::hash_combine(hash, BSONObj::Hasher()(elem.codeWScopeObject()));
break;
}
}
return hash;
}
} // namespace mongo