// documenttests.cpp : Unit tests for Document, Value, and related classes.
/**
* Copyright (C) 2012 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/platform/basic.h"
#include "mongo/db/pipeline/document.h"
#include "mongo/db/pipeline/field_path.h"
#include "mongo/db/pipeline/value.h"
#include "mongo/dbtests/dbtests.h"
#include "mongo/util/print.h"
namespace DocumentTests {
using std::endl;
using std::numeric_limits;
using std::string;
using std::vector;
mongo::Document::FieldPair getNthField(mongo::Document doc, size_t index) {
mongo::FieldIterator it (doc);
while (index--) // advance index times
it.next();
return it.next();
}
namespace Document {
using mongo::Document;
BSONObj toBson( const Document& document ) {
return document.toBson();
}
Document fromBson( BSONObj obj ) {
return Document(obj);
}
void assertRoundTrips( const Document& document1 ) {
BSONObj obj1 = toBson( document1 );
Document document2 = fromBson( obj1 );
BSONObj obj2 = toBson( document2 );
ASSERT_EQUALS( obj1, obj2 );
ASSERT_EQUALS( document1, document2 );
}
/** Create a Document. */
class Create {
public:
void run() {
Document document;
ASSERT_EQUALS( 0U, document.size() );
assertRoundTrips( document );
}
};
/** Create a Document from a BSONObj. */
class CreateFromBsonObj {
public:
void run() {
Document document = fromBson( BSONObj() );
ASSERT_EQUALS( 0U, document.size() );
document = fromBson( BSON( "a" << 1 << "b" << "q" ) );
ASSERT_EQUALS( 2U, document.size() );
ASSERT_EQUALS( "a", getNthField(document, 0).first.toString() );
ASSERT_EQUALS( 1, getNthField(document, 0).second.getInt() );
ASSERT_EQUALS( "b", getNthField(document, 1).first.toString() );
ASSERT_EQUALS( "q", getNthField(document, 1).second.getString() );
assertRoundTrips( document );
}
};
/** Add Document fields. */
class AddField {
public:
void run() {
MutableDocument md;
md.addField( "foo", Value( 1 ) );
ASSERT_EQUALS( 1U, md.peek().size() );
ASSERT_EQUALS( 1, md.peek()["foo"].getInt() );
md.addField( "bar", Value( 99 ) );
ASSERT_EQUALS( 2U, md.peek().size() );
ASSERT_EQUALS( 99, md.peek()["bar"].getInt() );
// No assertion is triggered by a duplicate field name.
md.addField( "a", Value( 5 ) );
Document final = md.freeze();
ASSERT_EQUALS( 3U, final.size() );
assertRoundTrips( final );
}
};
/** Get Document values. */
class GetValue {
public:
void run() {
Document document = fromBson( BSON( "a" << 1 << "b" << 2.2 ) );
ASSERT_EQUALS( 1, document["a"].getInt() );
ASSERT_EQUALS( 1, document["a"].getInt() );
ASSERT_EQUALS( 2.2, document["b"].getDouble() );
ASSERT_EQUALS( 2.2, document["b"].getDouble() );
// Missing field.
ASSERT( document["c"].missing() );
ASSERT( document["c"].missing() );
assertRoundTrips( document );
}
};
/** Get Document fields. */
class SetField {
public:
void run() {
Document original = fromBson(BSON("a" << 1 << "b" << 2.2 << "c" << 99));
// Initial positions. Used at end of function to make sure nothing moved
const Position apos = original.positionOf("a");
const Position bpos = original.positionOf("c");
const Position cpos = original.positionOf("c");
MutableDocument md (original);
// Set the first field.
md.setField( "a" , Value( "foo" ) );
ASSERT_EQUALS( 3U, md.peek().size() );
ASSERT_EQUALS( "foo", md.peek()["a"].getString() );
ASSERT_EQUALS( "foo", getNthField(md.peek(), 0).second.getString() );
assertRoundTrips( md.peek() );
// Set the second field.
md["b"] = Value("bar");
ASSERT_EQUALS( 3U, md.peek().size() );
ASSERT_EQUALS( "bar", md.peek()["b"].getString() );
ASSERT_EQUALS( "bar", getNthField(md.peek(), 1).second.getString() );
assertRoundTrips( md.peek() );
// Remove the second field.
md.setField("b", Value());
PRINT(md.peek().toString());
ASSERT_EQUALS( 2U, md.peek().size() );
ASSERT( md.peek()["b"].missing() );
ASSERT_EQUALS( "a", getNthField(md.peek(), 0 ).first.toString() );
ASSERT_EQUALS( "c", getNthField(md.peek(), 1 ).first.toString() );
ASSERT_EQUALS( 99, md.peek()["c"].getInt() );
assertRoundTrips( md.peek() );
// Remove the first field.
md["a"] = Value();
ASSERT_EQUALS( 1U, md.peek().size() );
ASSERT( md.peek()["a"].missing() );
ASSERT_EQUALS( "c", getNthField(md.peek(), 0 ).first.toString() );
ASSERT_EQUALS( 99, md.peek()["c"].getInt() );
assertRoundTrips( md.peek() );
// Remove the final field. Verify document is empty.
md.remove("c");
ASSERT( md.peek().empty() );
ASSERT_EQUALS( 0U, md.peek().size() );
ASSERT_EQUALS( md.peek(), Document() );
ASSERT( !FieldIterator(md.peek()).more() );
ASSERT( md.peek()["c"].missing() );
assertRoundTrips( md.peek() );
// Set a nested field using []
md["x"]["y"]["z"] = Value("nested");
ASSERT_EQUALS(md.peek()["x"]["y"]["z"], Value("nested"));
// Set a nested field using setNestedField
FieldPath xxyyzz = string("xx.yy.zz");
md.setNestedField(xxyyzz, Value("nested"));
ASSERT_EQUALS(md.peek().getNestedField(xxyyzz), Value("nested") );
// Set a nested fields through an existing empty document
md["xxx"] = Value(Document());
md["xxx"]["yyy"] = Value(Document());
FieldPath xxxyyyzzz = string("xxx.yyy.zzz");
md.setNestedField(xxxyyyzzz, Value("nested"));
ASSERT_EQUALS(md.peek().getNestedField(xxxyyyzzz), Value("nested") );
// Make sure nothing moved
ASSERT_EQUALS(apos, md.peek().positionOf("a"));
ASSERT_EQUALS(bpos, md.peek().positionOf("c"));
ASSERT_EQUALS(cpos, md.peek().positionOf("c"));
ASSERT_EQUALS(Position(), md.peek().positionOf("d"));
}
};
/** Document comparator. */
class Compare {
public:
void run() {
assertComparison( 0, BSONObj(), BSONObj() );
assertComparison( 0, BSON( "a" << 1 ), BSON( "a" << 1 ) );
assertComparison( -1, BSONObj(), BSON( "a" << 1 ) );
assertComparison( -1, BSON( "a" << 1 ), BSON( "c" << 1 ) );
assertComparison( 0, BSON( "a" << 1 << "r" << 2 ), BSON( "a" << 1 << "r" << 2 ) );
assertComparison( -1, BSON( "a" << 1 ), BSON( "a" << 1 << "r" << 2 ) );
assertComparison( 0, BSON( "a" << 2 ), BSON( "a" << 2 ) );
assertComparison( -1, BSON( "a" << 1 ), BSON( "a" << 2 ) );
assertComparison( -1, BSON( "a" << 1 << "b" << 1 ), BSON( "a" << 1 << "b" << 2 ) );
// numbers sort before strings
assertComparison( -1, BSON( "a" << 1 ), BSON( "a" << "foo" ) );
// numbers sort before strings, even if keys compare otherwise
assertComparison( -1, BSON( "b" << 1 ), BSON( "a" << "foo" ) );
// null before number, even if keys compare otherwise
assertComparison( -1, BSON( "z" << BSONNULL ), BSON( "a" << 1 ) );
}
public:
int cmp( const BSONObj& a, const BSONObj& b ) {
int result = Document::compare( fromBson( a ), fromBson( b ) );
return // sign
result < 0 ? -1 :
result > 0 ? 1 :
0;
}
void assertComparison( int expectedResult, const BSONObj& a, const BSONObj& b ) {
ASSERT_EQUALS( expectedResult, cmp( a, b ) );
ASSERT_EQUALS( -expectedResult, cmp( b, a ) );
if ( expectedResult == 0 ) {
ASSERT_EQUALS( hash( a ), hash( b ) );
}
}
size_t hash( const BSONObj& obj ) {
size_t seed = 0x106e1e1;
Document(obj).hash_combine(seed);
return seed;
}
};
/** Shallow copy clone of a single field Document. */
class Clone {
public:
void run() {
const Document document = fromBson( BSON( "a" << BSON( "b" << 1 ) ) );
MutableDocument cloneOnDemand (document);
// Check equality.
ASSERT_EQUALS(document, cloneOnDemand.peek());
// Check pointer equality of sub document.
ASSERT_EQUALS( document["a"].getDocument().getPtr(),
cloneOnDemand.peek()["a"].getDocument().getPtr() );
// Change field in clone and ensure the original document's field is unchanged.
cloneOnDemand.setField( StringData("a"), Value(2) );
ASSERT_EQUALS( Value(1), document.getNestedField(FieldPath("a.b")) );
// setNestedField and ensure the original document is unchanged.
cloneOnDemand.reset(document);
vector path;
ASSERT_EQUALS( Value(1), document.getNestedField(FieldPath("a.b"), &path) );
cloneOnDemand.setNestedField(path, Value(2));
ASSERT_EQUALS( Value(1), document.getNestedField(FieldPath("a.b")) );
ASSERT_EQUALS( Value(2), cloneOnDemand.peek().getNestedField(FieldPath("a.b")) );
ASSERT_EQUALS( DOC( "a" << DOC( "b" << 1 ) ), document );
ASSERT_EQUALS( DOC( "a" << DOC( "b" << 2 ) ), cloneOnDemand.freeze() );
}
};
/** Shallow copy clone of a multi field Document. */
class CloneMultipleFields {
public:
void run() {
Document document =
fromBson( fromjson( "{a:1,b:['ra',4],c:{z:1},d:'lal'}" ) );
Document clonedDocument = document.clone();
ASSERT_EQUALS(document, clonedDocument);
}
};
/** FieldIterator for an empty Document. */
class FieldIteratorEmpty {
public:
void run() {
FieldIterator iterator ( (Document()) );
ASSERT( !iterator.more() );
}
};
/** FieldIterator for a single field Document. */
class FieldIteratorSingle {
public:
void run() {
FieldIterator iterator (fromBson( BSON( "a" << 1 ) ));
ASSERT( iterator.more() );
Document::FieldPair field = iterator.next();
ASSERT_EQUALS( "a", field.first.toString() );
ASSERT_EQUALS( 1, field.second.getInt() );
ASSERT( !iterator.more() );
}
};
/** FieldIterator for a multiple field Document. */
class FieldIteratorMultiple {
public:
void run() {
FieldIterator iterator (fromBson( BSON( "a" << 1 << "b" << 5.6 << "c" << "z" )));
ASSERT( iterator.more() );
Document::FieldPair field = iterator.next();
ASSERT_EQUALS( "a", field.first.toString() );
ASSERT_EQUALS( 1, field.second.getInt() );
ASSERT( iterator.more() );
Document::FieldPair field2 = iterator.next();
ASSERT_EQUALS( "b", field2.first.toString() );
ASSERT_EQUALS( 5.6, field2.second.getDouble() );
ASSERT( iterator.more() );
Document::FieldPair field3 = iterator.next();
ASSERT_EQUALS( "c", field3.first.toString() );
ASSERT_EQUALS( "z", field3.second.getString() );
ASSERT( !iterator.more() );
}
};
class AllTypesDoc {
public:
void run() {
// These are listed in order of BSONType with some duplicates
append("minkey", MINKEY);
// EOO not valid in middle of BSONObj
append("double", 1.0);
append("c-string", "string\0after NUL"); // after NULL is ignored
append("c++", StringData("string\0after NUL", StringData::LiteralTag()).toString());
append("StringData", StringData("string\0after NUL", StringData::LiteralTag()));
append("emptyObj", BSONObj());
append("filledObj", BSON("a" << 1));
append("emptyArray", BSON("" << BSONArray()).firstElement());
append("filledArray", BSON("" << BSON_ARRAY(1 << "a")).firstElement());
append("binData", BSONBinData("a\0b", 3, BinDataGeneral));
append("binDataCustom", BSONBinData("a\0b", 3, bdtCustom));
append("binDataUUID", BSONBinData("123456789\0abcdef", 16, bdtUUID));
append("undefined", BSONUndefined);
append("oid", OID());
append("true", true);
append("false", false);
append("date", jsTime());
append("null", BSONNULL);
append("regex", BSONRegEx(".*"));
append("regexFlags", BSONRegEx(".*", "i"));
append("regexEmpty", BSONRegEx("", ""));
append("dbref", BSONDBRef("foo", OID()));
append("code", BSONCode("function() {}"));
append("codeNul", BSONCode(StringData("var nul = '\0'", StringData::LiteralTag())));
append("symbol", BSONSymbol("foo"));
append("symbolNul", BSONSymbol(StringData("f\0o", StringData::LiteralTag())));
append("codeWScope", BSONCodeWScope("asdf", BSONObj()));
append("codeWScopeWScope", BSONCodeWScope("asdf", BSON("one" << 1)));
append("int", 1);
append("timestamp", Timestamp());
append("long", 1LL);
append("very long", 1LL << 40);
append("maxkey", MAXKEY);
const BSONArray arr = arrBuilder.arr();
// can't use append any more since arrBuilder is done
objBuilder << "mega array" << arr;
docBuilder["mega array"] = mongo::Value(values);
const BSONObj obj = objBuilder.obj();
const Document doc = docBuilder.freeze();
const BSONObj obj2 = toBson(doc);
const Document doc2 = fromBson(obj);
// logical equality
ASSERT_EQUALS(obj, obj2);
ASSERT_EQUALS(doc, doc2);
// binary equality
ASSERT_EQUALS(obj.objsize(), obj2.objsize());
ASSERT_EQUALS(memcmp(obj.objdata(), obj2.objdata(), obj.objsize()), 0);
// ensure sorter serialization round-trips correctly
BufBuilder bb;
doc.serializeForSorter(bb);
BufReader reader(bb.buf(), bb.len());
const Document doc3 = Document::deserializeForSorter(
reader, Document::SorterDeserializeSettings());
BSONObj obj3 = toBson(doc3);
ASSERT_EQUALS(obj.objsize(), obj3.objsize());
ASSERT_EQUALS(memcmp(obj.objdata(), obj3.objdata(), obj.objsize()), 0);
}
template
void append(const char* name, const T& thing) {
objBuilder << name << thing;
arrBuilder << thing;
docBuilder[name] = mongo::Value(thing);
values.push_back(mongo::Value(thing));
}
vector values;
MutableDocument docBuilder;
BSONObjBuilder objBuilder;
BSONArrayBuilder arrBuilder;
};
} // namespace Document
namespace Value {
using mongo::Value;
BSONObj toBson( const Value& value ) {
if (value.missing())
return BSONObj(); // EOO
BSONObjBuilder bob;
value.addToBsonObj( &bob, "" );
return bob.obj();
}
Value fromBson( const BSONObj& obj ) {
BSONElement element = obj.firstElement();
return Value( element );
}
void assertRoundTrips( const Value& value1 ) {
BSONObj obj1 = toBson( value1 );
Value value2 = fromBson( obj1 );
BSONObj obj2 = toBson( value2 );
ASSERT_EQUALS( obj1, obj2 );
ASSERT_EQUALS(value1, value2);
ASSERT_EQUALS(value1.getType(), value2.getType());
}
class BSONArrayTest {
public:
void run() {
ASSERT_EQUALS(Value(BSON_ARRAY(1 << 2 << 3)), DOC_ARRAY(1 << 2 << 3));
ASSERT_EQUALS(Value(BSONArray()), Value(vector()));
}
};
/** Int type. */
class Int {
public:
void run() {
Value value = Value( 5 );
ASSERT_EQUALS( 5, value.getInt() );
ASSERT_EQUALS( 5, value.getLong() );
ASSERT_EQUALS( 5, value.getDouble() );
ASSERT_EQUALS( NumberInt, value.getType() );
assertRoundTrips( value );
}
};
/** Long type. */
class Long {
public:
void run() {
Value value = Value( 99LL );
ASSERT_EQUALS( 99, value.getLong() );
ASSERT_EQUALS( 99, value.getDouble() );
ASSERT_EQUALS( NumberLong, value.getType() );
assertRoundTrips( value );
}
};
/** Double type. */
class Double {
public:
void run() {
Value value = Value( 5.5 );
ASSERT_EQUALS( 5.5, value.getDouble() );
ASSERT_EQUALS( NumberDouble, value.getType() );
assertRoundTrips( value );
}
};
/** String type. */
class String {
public:
void run() {
Value value = Value( "foo" );
ASSERT_EQUALS( "foo", value.getString() );
ASSERT_EQUALS( mongo::String, value.getType() );
assertRoundTrips( value );
}
};
/** String with a null character. */
class StringWithNull {
public:
void run() {
string withNull( "a\0b", 3 );
BSONObj objWithNull = BSON( "" << withNull );
ASSERT_EQUALS( withNull, objWithNull[ "" ].str() );
Value value = fromBson( objWithNull );
ASSERT_EQUALS( withNull, value.getString() );
assertRoundTrips( value );
}
};
/** Date type. */
class Date {
public:
void run() {
Value value = Value(Date_t(999));
ASSERT_EQUALS( 999, value.getDate() );
ASSERT_EQUALS( mongo::Date, value.getType() );
assertRoundTrips( value );
}
};
/** Timestamp type. */
class JSTimestamp {
public:
void run() {
Value value = Value( Timestamp( 777 ) );
ASSERT( Timestamp( 777 ) == value.getTimestamp() );
ASSERT_EQUALS( mongo::bsonTimestamp, value.getType() );
assertRoundTrips( value );
}
};
/** Document with no fields. */
class EmptyDocument {
public:
void run() {
mongo::Document document = mongo::Document();
Value value = Value( document );
ASSERT_EQUALS( document.getPtr(), value.getDocument().getPtr() );
ASSERT_EQUALS( Object, value.getType() );
assertRoundTrips( value );
}
};
/** Document type. */
class Document {
public:
void run() {
mongo::MutableDocument md;
md.addField( "a", Value( 5 ) );
md.addField( "apple", Value( "rrr" ) );
md.addField( "banana", Value( -.3 ) );
mongo::Document document = md.freeze();
Value value = Value( document );
// Check document pointers are equal.
ASSERT_EQUALS( document.getPtr(), value.getDocument().getPtr() );
// Check document contents.
ASSERT_EQUALS( 5, document["a"].getInt() );
ASSERT_EQUALS( "rrr", document["apple"].getString() );
ASSERT_EQUALS( -.3, document["banana"].getDouble() );
ASSERT_EQUALS( Object, value.getType() );
assertRoundTrips( value );
}
};
/** Array with no elements. */
class EmptyArray {
public:
void run() {
vector array;
Value value (array);
const vector& array2 = value.getArray();
ASSERT( array2.empty() );
ASSERT_EQUALS( Array, value.getType() );
ASSERT_EQUALS( 0U, value.getArrayLength() );
assertRoundTrips( value );
}
};
/** Array type. */
class Array {
public:
void run() {
vector array;
array.push_back( Value( 5 ) );
array.push_back( Value( "lala" ) );
array.push_back( Value( 3.14 ) );
Value value = Value( array );
const vector& array2 = value.getArray();
ASSERT( !array2.empty() );
ASSERT_EQUALS( array2.size(), 3U);
ASSERT_EQUALS( 5, array2[0].getInt() );
ASSERT_EQUALS( "lala", array2[1].getString() );
ASSERT_EQUALS( 3.14, array2[2].getDouble() );
ASSERT_EQUALS( mongo::Array, value.getType() );
ASSERT_EQUALS( 3U, value.getArrayLength() );
assertRoundTrips( value );
}
};
/** Oid type. */
class Oid {
public:
void run() {
Value value =
fromBson( BSON( "" << OID( "abcdefabcdefabcdefabcdef" ) ) );
ASSERT_EQUALS( OID( "abcdefabcdefabcdefabcdef" ), value.getOid() );
ASSERT_EQUALS( jstOID, value.getType() );
assertRoundTrips( value );
}
};
/** Bool type. */
class Bool {
public:
void run() {
Value value = fromBson( BSON( "" << true ) );
ASSERT_EQUALS( true, value.getBool() );
ASSERT_EQUALS( mongo::Bool, value.getType() );
assertRoundTrips( value );
}
};
/** Regex type. */
class Regex {
public:
void run() {
Value value = fromBson( fromjson( "{'':/abc/}" ) );
ASSERT_EQUALS( string("abc"), value.getRegex() );
ASSERT_EQUALS( RegEx, value.getType() );
assertRoundTrips( value );
}
};
/** Symbol type (currently unsupported). */
class Symbol {
public:
void run() {
Value value (BSONSymbol("FOOBAR"));
ASSERT_EQUALS( "FOOBAR", value.getSymbol() );
ASSERT_EQUALS( mongo::Symbol, value.getType() );
assertRoundTrips( value );
}
};
/** Undefined type. */
class Undefined {
public:
void run() {
Value value = Value(BSONUndefined);
ASSERT_EQUALS( mongo::Undefined, value.getType() );
assertRoundTrips( value );
}
};
/** Null type. */
class Null {
public:
void run() {
Value value = Value(BSONNULL);
ASSERT_EQUALS( jstNULL, value.getType() );
assertRoundTrips( value );
}
};
/** True value. */
class True {
public:
void run() {
Value value = Value(true);
ASSERT_EQUALS( true, value.getBool() );
ASSERT_EQUALS( mongo::Bool, value.getType() );
assertRoundTrips( value );
}
};
/** False value. */
class False {
public:
void run() {
Value value = Value(false);
ASSERT_EQUALS( false, value.getBool() );
ASSERT_EQUALS( mongo::Bool, value.getType() );
assertRoundTrips( value );
}
};
/** -1 value. */
class MinusOne {
public:
void run() {
Value value = Value(-1);
ASSERT_EQUALS( -1, value.getInt() );
ASSERT_EQUALS( NumberInt, value.getType() );
assertRoundTrips( value );
}
};
/** 0 value. */
class Zero {
public:
void run() {
Value value = Value(0);
ASSERT_EQUALS( 0, value.getInt() );
ASSERT_EQUALS( NumberInt, value.getType() );
assertRoundTrips( value );
}
};
/** 1 value. */
class One {
public:
void run() {
Value value = Value(1);
ASSERT_EQUALS( 1, value.getInt() );
ASSERT_EQUALS( NumberInt, value.getType() );
assertRoundTrips( value );
}
};
namespace Coerce {
class ToBoolBase {
public:
virtual ~ToBoolBase() {
}
void run() {
ASSERT_EQUALS( expected(), value().coerceToBool() );
}
protected:
virtual Value value() = 0;
virtual bool expected() = 0;
};
class ToBoolTrue : public ToBoolBase {
bool expected() { return true; }
};
class ToBoolFalse : public ToBoolBase {
bool expected() { return false; }
};
/** Coerce 0 to bool. */
class ZeroIntToBool : public ToBoolFalse {
Value value() { return Value( 0 ); }
};
/** Coerce -1 to bool. */
class NonZeroIntToBool : public ToBoolTrue {
Value value() { return Value( -1 ); }
};
/** Coerce 0LL to bool. */
class ZeroLongToBool : public ToBoolFalse {
Value value() { return Value( 0LL ); }
};
/** Coerce 5LL to bool. */
class NonZeroLongToBool : public ToBoolTrue {
Value value() { return Value( 5LL ); }
};
/** Coerce 0.0 to bool. */
class ZeroDoubleToBool : public ToBoolFalse {
Value value() { return Value( 0 ); }
};
/** Coerce -1.3 to bool. */
class NonZeroDoubleToBool : public ToBoolTrue {
Value value() { return Value( -1.3 ); }
};
/** Coerce "" to bool. */
class StringToBool : public ToBoolTrue {
Value value() { return Value( "" ); }
};
/** Coerce {} to bool. */
class ObjectToBool : public ToBoolTrue {
Value value() {
return Value( mongo::Document() );
}
};
/** Coerce [] to bool. */
class ArrayToBool : public ToBoolTrue {
Value value() {
return Value( vector() );
}
};
/** Coerce Date(0) to bool. */
class DateToBool : public ToBoolTrue {
Value value() { return Value(Date_t(0)); }
};
/** Coerce js literal regex to bool. */
class RegexToBool : public ToBoolTrue {
Value value() { return fromBson( fromjson( "{''://}" ) ); }
};
/** Coerce true to bool. */
class TrueToBool : public ToBoolTrue {
Value value() { return fromBson( BSON( "" << true ) ); }
};
/** Coerce false to bool. */
class FalseToBool : public ToBoolFalse {
Value value() { return fromBson( BSON( "" << false ) ); }
};
/** Coerce null to bool. */
class NullToBool : public ToBoolFalse {
Value value() { return Value(BSONNULL); }
};
/** Coerce undefined to bool. */
class UndefinedToBool : public ToBoolFalse {
Value value() { return Value(BSONUndefined); }
};
class ToIntBase {
public:
virtual ~ToIntBase() {
}
void run() {
if (asserts())
ASSERT_THROWS( value().coerceToInt(), UserException );
else
ASSERT_EQUALS( expected(), value().coerceToInt() );
}
protected:
virtual Value value() = 0;
virtual int expected() { return 0; }
virtual bool asserts() { return false; }
};
/** Coerce -5 to int. */
class IntToInt : public ToIntBase {
Value value() { return Value( -5 ); }
int expected() { return -5; }
};
/** Coerce long to int. */
class LongToInt : public ToIntBase {
Value value() { return Value( 0xff00000007LL ); }
int expected() { return 7; }
};
/** Coerce 9.8 to int. */
class DoubleToInt : public ToIntBase {
Value value() { return Value( 9.8 ); }
int expected() { return 9; }
};
/** Coerce null to int. */
class NullToInt : public ToIntBase {
Value value() { return Value(BSONNULL); }
bool asserts() { return true; }
};
/** Coerce undefined to int. */
class UndefinedToInt : public ToIntBase {
Value value() { return Value(BSONUndefined); }
bool asserts() { return true; }
};
/** Coerce "" to int unsupported. */
class StringToInt {
public:
void run() {
ASSERT_THROWS( Value( "" ).coerceToInt(), UserException );
}
};
class ToLongBase {
public:
virtual ~ToLongBase() {
}
void run() {
if (asserts())
ASSERT_THROWS( value().coerceToLong(), UserException );
else
ASSERT_EQUALS( expected(), value().coerceToLong() );
}
protected:
virtual Value value() = 0;
virtual long long expected() { return 0; }
virtual bool asserts() { return false; }
};
/** Coerce -5 to long. */
class IntToLong : public ToLongBase {
Value value() { return Value( -5 ); }
long long expected() { return -5; }
};
/** Coerce long to long. */
class LongToLong : public ToLongBase {
Value value() { return Value( 0xff00000007LL ); }
long long expected() { return 0xff00000007LL; }
};
/** Coerce 9.8 to long. */
class DoubleToLong : public ToLongBase {
Value value() { return Value( 9.8 ); }
long long expected() { return 9; }
};
/** Coerce null to long. */
class NullToLong : public ToLongBase {
Value value() { return Value(BSONNULL); }
bool asserts() { return true; }
};
/** Coerce undefined to long. */
class UndefinedToLong : public ToLongBase {
Value value() { return Value(BSONUndefined); }
bool asserts() { return true; }
};
/** Coerce string to long unsupported. */
class StringToLong {
public:
void run() {
ASSERT_THROWS( Value( "" ).coerceToLong(), UserException );
}
};
class ToDoubleBase {
public:
virtual ~ToDoubleBase() {
}
void run() {
if (asserts())
ASSERT_THROWS( value().coerceToDouble(), UserException );
else
ASSERT_EQUALS( expected(), value().coerceToDouble() );
}
protected:
virtual Value value() = 0;
virtual double expected() { return 0; }
virtual bool asserts() { return false; }
};
/** Coerce -5 to double. */
class IntToDouble : public ToDoubleBase {
Value value() { return Value( -5 ); }
double expected() { return -5; }
};
/** Coerce long to double. */
class LongToDouble : public ToDoubleBase {
Value value() {
// A long that cannot be exactly represented as a double.
return Value( static_cast( 0x8fffffffffffffffLL ) );
}
double expected() { return static_cast( 0x8fffffffffffffffLL ); }
};
/** Coerce double to double. */
class DoubleToDouble : public ToDoubleBase {
Value value() { return Value( 9.8 ); }
double expected() { return 9.8; }
};
/** Coerce null to double. */
class NullToDouble : public ToDoubleBase {
Value value() { return Value(BSONNULL); }
bool asserts() { return true; }
};
/** Coerce undefined to double. */
class UndefinedToDouble : public ToDoubleBase {
Value value() { return Value(BSONUndefined); }
bool asserts() { return true; }
};
/** Coerce string to double unsupported. */
class StringToDouble {
public:
void run() {
ASSERT_THROWS( Value( "" ).coerceToDouble(), UserException );
}
};
class ToDateBase {
public:
virtual ~ToDateBase() {
}
void run() {
ASSERT_EQUALS( expected(), value().coerceToDate() );
}
protected:
virtual Value value() = 0;
virtual long long expected() = 0;
};
/** Coerce date to date. */
class DateToDate : public ToDateBase {
Value value() { return Value(Date_t(888)); }
long long expected() { return 888; }
};
/**
* Convert timestamp to date. This extracts the time portion of the timestamp, which
* is different from BSON behavior of interpreting all bytes as a date.
*/
class TimestampToDate : public ToDateBase {
Value value() {
return Value( Timestamp( 777, 666 ) );
}
long long expected() { return 777 * 1000; }
};
/** Coerce string to date unsupported. */
class StringToDate {
public:
void run() {
ASSERT_THROWS( Value( "" ).coerceToDate(), UserException );
}
};
class ToStringBase {
public:
virtual ~ToStringBase() {
}
void run() {
ASSERT_EQUALS( expected(), value().coerceToString() );
}
protected:
virtual Value value() = 0;
virtual string expected() { return ""; }
};
/** Coerce -0.2 to string. */
class DoubleToString : public ToStringBase {
Value value() { return Value( -0.2 ); }
string expected() { return "-0.2"; }
};
/** Coerce -4 to string. */
class IntToString : public ToStringBase {
Value value() { return Value( -4 ); }
string expected() { return "-4"; }
};
/** Coerce 10000LL to string. */
class LongToString : public ToStringBase {
Value value() { return Value( 10000LL ); }
string expected() { return "10000"; }
};
/** Coerce string to string. */
class StringToString : public ToStringBase {
Value value() { return Value( "fO_o" ); }
string expected() { return "fO_o"; }
};
/** Coerce timestamp to string. */
class TimestampToString : public ToStringBase {
Value value() {
return Value( Timestamp( 1, 2 ) );
}
string expected() { return Timestamp( 1, 2 ).toStringPretty(); }
};
/** Coerce date to string. */
class DateToString : public ToStringBase {
Value value() { return Value(Date_t(1234567890LL*1000)); }
string expected() { return "2009-02-13T23:31:30"; } // from js
};
/** Coerce null to string. */
class NullToString : public ToStringBase {
Value value() { return Value(BSONNULL); }
};
/** Coerce undefined to string. */
class UndefinedToString : public ToStringBase {
Value value() { return Value(BSONUndefined); }
};
/** Coerce document to string unsupported. */
class DocumentToString {
public:
void run() {
ASSERT_THROWS( Value
( mongo::Document() ).coerceToString(),
UserException );
}
};
/** Coerce timestamp to timestamp. */
class TimestampToTimestamp {
public:
void run() {
Value value = Value( Timestamp( 1010 ) );
ASSERT( Timestamp( 1010 ) == value.coerceToTimestamp() );
}
};
/** Coerce date to timestamp unsupported. */
class DateToTimestamp {
public:
void run() {
ASSERT_THROWS( Value(Date_t(1010)).coerceToTimestamp(),
UserException );
}
};
} // namespace Coerce
/** Get the "widest" of two numeric types. */
class GetWidestNumeric {
public:
void run() {
using mongo::Undefined;
// Numeric types.
assertWidest( NumberInt, NumberInt, NumberInt );
assertWidest( NumberLong, NumberInt, NumberLong );
assertWidest( NumberDouble, NumberInt, NumberDouble );
assertWidest( NumberLong, NumberLong, NumberLong );
assertWidest( NumberDouble, NumberLong, NumberDouble );
assertWidest( NumberDouble, NumberDouble, NumberDouble );
// Missing value and numeric types (result Undefined).
assertWidest( Undefined, NumberInt, Undefined );
assertWidest( Undefined, NumberInt, Undefined );
assertWidest( Undefined, NumberLong, jstNULL );
assertWidest( Undefined, NumberLong, Undefined );
assertWidest( Undefined, NumberDouble, jstNULL );
assertWidest( Undefined, NumberDouble, Undefined );
// Missing value types (result Undefined).
assertWidest( Undefined, jstNULL, jstNULL );
assertWidest( Undefined, jstNULL, Undefined );
assertWidest( Undefined, Undefined, Undefined );
// Other types (result Undefined).
assertWidest( Undefined, NumberInt, mongo::Bool );
assertWidest( Undefined, mongo::String, NumberDouble );
}
private:
void assertWidest( BSONType expectedWidest, BSONType a, BSONType b ) {
ASSERT_EQUALS( expectedWidest, Value::getWidestNumeric( a, b ) );
ASSERT_EQUALS( expectedWidest, Value::getWidestNumeric( b, a ) );
}
};
/** Add a Value to a BSONObj. */
class AddToBsonObj {
public:
void run() {
BSONObjBuilder bob;
Value( 4.4 ).addToBsonObj( &bob, "a" );
Value( 22 ).addToBsonObj( &bob, "b" );
Value( "astring" ).addToBsonObj( &bob, "c" );
ASSERT_EQUALS( BSON( "a" << 4.4 << "b" << 22 << "c" << "astring" ), bob.obj() );
}
};
/** Add a Value to a BSONArray. */
class AddToBsonArray {
public:
void run() {
BSONArrayBuilder bab;
Value( 4.4 ).addToBsonArray( &bab );
Value( 22 ).addToBsonArray( &bab );
Value( "astring" ).addToBsonArray( &bab );
ASSERT_EQUALS( BSON_ARRAY( 4.4 << 22 << "astring" ), bab.arr() );
}
};
/** Value comparator. */
class Compare {
public:
void run() {
BSONObjBuilder undefinedBuilder;
undefinedBuilder.appendUndefined( "" );
BSONObj undefined = undefinedBuilder.obj();
// Undefined / null.
assertComparison( 0, undefined, undefined );
assertComparison( -1, undefined, BSON( "" << BSONNULL ) );
assertComparison( 0, BSON( "" << BSONNULL ), BSON( "" << BSONNULL ) );
// Undefined / null with other types.
assertComparison( -1, undefined, BSON( "" << 1 ) );
assertComparison( -1, undefined, BSON( "" << "bar" ) );
assertComparison( -1, BSON( "" << BSONNULL ), BSON( "" << -1 ) );
assertComparison( -1, BSON( "" << BSONNULL ), BSON( "" << "bar" ) );
// Numeric types.
assertComparison( 0, 5, 5LL );
assertComparison( 0, -2, -2.0 );
assertComparison( 0, 90LL, 90.0 );
assertComparison( -1, 5, 6LL );
assertComparison( -1, -2, 2.1 );
assertComparison( 1, 90LL, 89.999 );
assertComparison( -1, 90, 90.1 );
assertComparison( 0, numeric_limits::quiet_NaN(),
numeric_limits::signaling_NaN() );
assertComparison( -1, numeric_limits::quiet_NaN(), 5 );
// strings compare between numbers and objects
assertComparison( 1, "abc", 90 );
assertComparison( -1, "abc", BSON( "a" << "b" ) );
// String comparison.
assertComparison( -1, "", "a" );
assertComparison( 0, "a", "a" );
assertComparison( -1, "a", "b" );
assertComparison( -1, "aa", "b" );
assertComparison( 1, "bb", "b" );
assertComparison( 1, "bb", "b" );
assertComparison( 1, "b-", "b" );
assertComparison( -1, "b-", "ba" );
// With a null character.
assertComparison( 1, string( "a\0", 2 ), "a" );
// Object.
assertComparison( 0, fromjson( "{'':{}}" ), fromjson( "{'':{}}" ) );
assertComparison( 0, fromjson( "{'':{x:1}}" ), fromjson( "{'':{x:1}}" ) );
assertComparison( -1, fromjson( "{'':{}}" ), fromjson( "{'':{x:1}}" ) );
assertComparison( -1, fromjson( "{'':{'z': 1}}" ), fromjson( "{'':{'a': 'a'}}") );
// Array.
assertComparison( 0, fromjson( "{'':[]}" ), fromjson( "{'':[]}" ) );
assertComparison( -1, fromjson( "{'':[0]}" ), fromjson( "{'':[1]}" ) );
assertComparison( -1, fromjson( "{'':[0,0]}" ), fromjson( "{'':[1]}" ) );
assertComparison( -1, fromjson( "{'':[0]}" ), fromjson( "{'':[0,0]}" ) );
assertComparison( -1, fromjson( "{'':[0]}" ), fromjson( "{'':['']}" ) );
// OID.
assertComparison( 0, OID( "abcdefabcdefabcdefabcdef" ),
OID( "abcdefabcdefabcdefabcdef" ) );
assertComparison( 1, OID( "abcdefabcdefabcdefabcdef" ),
OID( "010101010101010101010101" ) );
// Bool.
assertComparison( 0, true, true );
assertComparison( 0, false, false );
assertComparison( 1, true, false );
// Date.
assertComparison( 0, Date_t( 555 ), Date_t( 555 ) );
assertComparison( 1, Date_t( 555 ), Date_t( 554 ) );
// Negative date.
assertComparison( 1, Date_t( 0 ), Date_t( -1 ) );
// Regex.
assertComparison( 0, fromjson( "{'':/a/}" ), fromjson( "{'':/a/}" ) );
assertComparison( -1, fromjson( "{'':/a/}" ), fromjson( "{'':/a/i}" ) );
assertComparison( -1, fromjson( "{'':/a/}" ), fromjson( "{'':/aa/}" ) );
// Timestamp.
assertComparison( 0, Timestamp( 1234 ), Timestamp( 1234 ) );
assertComparison( -1, Timestamp( 4 ), Timestamp( 1234 ) );
// Cross-type comparisons. Listed in order of canonical types.
assertComparison(-1, Value(mongo::MINKEY), Value());
assertComparison(0, Value(), Value());
assertComparison(0, Value(), Value(BSONUndefined));
assertComparison(-1, Value(BSONUndefined), Value(BSONNULL));
assertComparison(-1, Value(BSONNULL), Value(1));
assertComparison(0, Value(1), Value(1LL));
assertComparison(0, Value(1), Value(1.0));
assertComparison(-1, Value(1), Value("string"));
assertComparison(0, Value("string"), Value(BSONSymbol("string")));
assertComparison(-1, Value("string"), Value(mongo::Document()));
assertComparison(-1, Value(mongo::Document()), Value(vector()));
assertComparison(-1, Value(vector()), Value(BSONBinData("", 0, MD5Type)));
assertComparison(-1, Value(BSONBinData("", 0, MD5Type)), Value(mongo::OID()));
assertComparison(-1, Value(mongo::OID()), Value(false));
assertComparison(-1, Value(false), Value(Date_t(0)));
assertComparison(-1, Value(Date_t(0)), Value(Timestamp()));
assertComparison(-1, Value(Timestamp()), Value(BSONRegEx("")));
assertComparison(-1, Value(BSONRegEx("")), Value(BSONDBRef("", mongo::OID())));
assertComparison(-1, Value(BSONDBRef("", mongo::OID())), Value(BSONCode("")));
assertComparison(-1, Value(BSONCode("")), Value(BSONCodeWScope("", BSONObj())));
assertComparison(-1, Value(BSONCodeWScope("", BSONObj())), Value(mongo::MAXKEY));
}
private:
template
void assertComparison( int expectedResult, const T& a, const U& b ) {
assertComparison( expectedResult, BSON( "" << a ), BSON( "" << b ) );
}
void assertComparison( int expectedResult, const Timestamp& a, const Timestamp& b ) {
BSONObjBuilder first;
first.append( "", a );
BSONObjBuilder second;
second.append( "", b );
assertComparison( expectedResult, first.obj(), second.obj() );
}
int sign(int cmp) {
if (cmp == 0) return 0;
else if (cmp < 0) return -1;
else return 1;
}
int cmp( const Value& a, const Value& b ) {
return sign(Value::compare(a, b));
}
void assertComparison( int expectedResult, const BSONObj& a, const BSONObj& b ) {
assertComparison(expectedResult, fromBson(a), fromBson(b));
}
void assertComparison(int expectedResult, const Value& a, const Value& b) {
mongo::unittest::log() <<
"testing " << a.toString() << " and " << b.toString() << endl;
// reflexivity
ASSERT_EQUALS(0, cmp(a, a));
ASSERT_EQUALS(0, cmp(b, b));
// symmetry
ASSERT_EQUALS( expectedResult, cmp( a, b ) );
ASSERT_EQUALS( -expectedResult, cmp( b, a ) );
if ( expectedResult == 0 ) {
// equal values must hash equally.
ASSERT_EQUALS( hash( a ), hash( b ) );
}
else {
// unequal values must hash unequally.
// (not true in general but we should error if it fails in any of these cases)
ASSERT_NOT_EQUALS( hash( a ), hash( b ) );
}
// same as BSON
ASSERT_EQUALS(expectedResult, sign(toBson(a).firstElement().woCompare(
toBson(b).firstElement())));
}
size_t hash(const Value& v) {
size_t seed = 0xf00ba6;
v.hash_combine( seed );
return seed;
}
};
class SubFields {
public:
void run() {
const Value val = fromBson(fromjson(
"{'': {a: [{x:1, b:[1, {y:1, c:1234, z:1}, 1]}]}}"));
// ^ this outer object is removed by fromBson
ASSERT(val.getType() == mongo::Object);
ASSERT(val[999].missing());
ASSERT(val["missing"].missing());
ASSERT(val["a"].getType() == mongo::Array);
ASSERT(val["a"][999].missing());
ASSERT(val["a"]["missing"].missing());
ASSERT(val["a"][0].getType() == mongo::Object);
ASSERT(val["a"][0][999].missing());
ASSERT(val["a"][0]["missing"].missing());
ASSERT(val["a"][0]["b"].getType() == mongo::Array);
ASSERT(val["a"][0]["b"][999].missing());
ASSERT(val["a"][0]["b"]["missing"].missing());
ASSERT(val["a"][0]["b"][1].getType() == mongo::Object);
ASSERT(val["a"][0]["b"][1][999].missing());
ASSERT(val["a"][0]["b"][1]["missing"].missing());
ASSERT(val["a"][0]["b"][1]["c"].getType() == mongo::NumberInt);
ASSERT_EQUALS(val["a"][0]["b"][1]["c"].getInt(), 1234);
}
};
class SerializationOfMissingForSorter {
// Can't be tested in AllTypesDoc since missing values are omitted when adding to BSON.
public:
void run() {
const Value missing;
const Value arrayOfMissing = Value(vector(10));
BufBuilder bb;
missing.serializeForSorter(bb);
arrayOfMissing.serializeForSorter(bb);
BufReader reader(bb.buf(), bb.len());
ASSERT_EQUALS(
missing,
Value::deserializeForSorter(reader, Value::SorterDeserializeSettings()));
ASSERT_EQUALS(
arrayOfMissing,
Value::deserializeForSorter(reader, Value::SorterDeserializeSettings()));
}
};
} // namespace Value
class All : public Suite {
public:
All() : Suite( "document" ) {
}
void setupTests() {
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
}
};
SuiteInstance myall;
} // namespace DocumentTests