// 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 . */ #include "pch.h" #include "mongo/db/pipeline/document.h" #include "mongo/db/pipeline/value.h" #include "dbtests.h" namespace DocumentTests { namespace Document { using mongo::Document; BSONObj toBson( const intrusive_ptr& document ) { BSONObjBuilder bob; document->toBson( &bob ); return bob.obj(); } intrusive_ptr fromBson( BSONObj obj ) { return Document::createFromBsonObj( &obj ); } void assertRoundTrips( const intrusive_ptr& document1 ) { BSONObj obj1 = toBson( document1 ); intrusive_ptr document2 = fromBson( obj1 ); BSONObj obj2 = toBson( document2 ); ASSERT_EQUALS( obj1, obj2 ); ASSERT_EQUALS( 0, Document::compare( document1, document2 ) ); } /** Create a Document. */ class Create { public: void run() { intrusive_ptr document = Document::create(); ASSERT_EQUALS( 0U, document->getFieldCount() ); assertRoundTrips( document ); } }; /** Create a Document from a BSONObj. */ class CreateFromBsonObj { public: void run() { intrusive_ptr document = fromBson( BSONObj() ); ASSERT_EQUALS( 0U, document->getFieldCount() ); document = fromBson( BSON( "a" << 1 << "b" << "q" ) ); ASSERT_EQUALS( 2U, document->getFieldCount() ); ASSERT_EQUALS( "a", document->getField( 0 ).first ); ASSERT_EQUALS( 1, document->getField( 0 ).second->getInt() ); ASSERT_EQUALS( "b", document->getField( 1 ).first ); ASSERT_EQUALS( "q", document->getField( 1 ).second->getString() ); assertRoundTrips( document ); } }; /** Add Document fields. */ class AddField { public: void run() { intrusive_ptr document = Document::create(); document->addField( "foo", Value::createInt( 1 ) ); ASSERT_EQUALS( 1U, document->getFieldCount() ); ASSERT_EQUALS( 1, document->getValue( "foo" )->getInt() ); document->addField( "bar", Value::createInt( 99 ) ); ASSERT_EQUALS( 2U, document->getFieldCount() ); ASSERT_EQUALS( 99, document->getValue( "bar" )->getInt() ); // No assertion is triggered by a duplicate field name. document->addField( "a", Value::createInt( 5 ) ); ASSERT_EQUALS( 3U, document->getFieldCount() ); assertRoundTrips( document ); } }; /** Get Document values. */ class GetValue { public: void run() { intrusive_ptr document = fromBson( BSON( "a" << 1 << "b" << 2.2 ) ); ASSERT_EQUALS( 1, document->getValue( "a" )->getInt() ); ASSERT_EQUALS( 1, document->getField( "a" )->getInt() ); ASSERT_EQUALS( 2.2, document->getValue( "b" )->getDouble() ); ASSERT_EQUALS( 2.2, document->getField( "b" )->getDouble() ); // Missing field. ASSERT( !document->getValue( "c" ) ); ASSERT( !document->getField( "c" ) ); assertRoundTrips( document ); } }; /** Get Document fields. */ class SetField { public: void run() { intrusive_ptr document = fromBson( BSON( "a" << 1 << "b" << 2.2 << "c" << 99 ) ); // Set the first field. document->setField( 0, "new", Value::createString( "foo" ) ); ASSERT_EQUALS( 3U, document->getFieldCount() ); ASSERT( !document->getValue( "a" ) ); ASSERT_EQUALS( "foo", document->getValue( "new" )->getString() ); ASSERT_EQUALS( "new", document->getField( 0 ).first ); ASSERT_EQUALS( "foo", document->getField( 0 ).second->getString() ); assertRoundTrips( document ); // Set the second field. document->setField( 1, "newer", Value::createString( "bar" ) ); ASSERT_EQUALS( 3U, document->getFieldCount() ); ASSERT( !document->getValue( "b" ) ); ASSERT_EQUALS( "bar", document->getValue( "newer" )->getString() ); ASSERT_EQUALS( "newer", document->getField( 1 ).first ); ASSERT_EQUALS( "bar", document->getField( 1 ).second->getString() ); assertRoundTrips( document ); // Remove the second field. document->setField( 1, "n/a", NULL ); ASSERT_EQUALS( 2U, document->getFieldCount() ); ASSERT( !document->getValue( "newer" ) ); ASSERT_EQUALS( "c", document->getField( 1 ).first ); ASSERT_EQUALS( 99, document->getField( 1 ).second->getInt() ); assertRoundTrips( document ); // Remove the first field. document->setField( 0, "n/a", NULL ); ASSERT_EQUALS( 1U, document->getFieldCount() ); ASSERT( !document->getValue( "new" ) ); ASSERT_EQUALS( "c", document->getField( 0 ).first ); ASSERT_EQUALS( 99, document->getField( 0 ).second->getInt() ); assertRoundTrips( document ); } }; /** Get Document field indexes. */ class GetFieldIndex { public: void run() { intrusive_ptr document = fromBson( BSON( "a" << 1 << "b" << 2.2 << "c" << 99 ) ); ASSERT_EQUALS( 0U, document->getFieldIndex( "a" ) ); ASSERT_EQUALS( 2U, document->getFieldIndex( "c" ) ); ASSERT_EQUALS( 3U, document->getFieldIndex( "missing" ) ); assertRoundTrips( document ); } }; /** 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 ) ); ASSERT_THROWS( assertComparison( 0, BSON( "a" << 1 ), BSON( "a" << "foo" ) ), UserException ); } 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; fromBson( obj )->hash_combine( seed ); return seed; } }; /** Comparison based on a null field's name. Differs from BSONObj comparison behavior. */ class CompareNamedNull { public: void run() { BSONObj obj1 = BSON( "z" << BSONNULL ); BSONObj obj2 = BSON( "a" << 1 ); // Comparsion with type precedence. ASSERT( obj1.woCompare( obj2 ) < 0 ); // Comparison with field name precedence. ASSERT( Document::compare( fromBson( obj1 ), fromBson( obj2 ) ) > 0 ); } }; /** Shallow copy clone of a single field Document. */ class Clone { public: void run() { intrusive_ptr document = fromBson( BSON( "a" << BSON( "b" << 1 ) ) ); intrusive_ptr clonedDocument = document->clone(); // Check equality. ASSERT_EQUALS( 0, Document::compare( document, clonedDocument ) ); // Check pointer equality of sub document. ASSERT_EQUALS( document->getValue( "a" )->getDocument(), clonedDocument->getValue( "a" )->getDocument() ); // Rename field in clone and ensure the original document's field is unchanged. clonedDocument->setField( 0, "renamed", clonedDocument->getValue( "a" ) ); ASSERT_EQUALS( "a", document->getField( 0 ).first ); // Drop the field in the clone and ensure the original document is unchanged. clonedDocument->setField( 0, "renamed", NULL ); ASSERT_EQUALS( 0U, clonedDocument->getFieldCount() ); ASSERT_EQUALS( BSON( "a" << BSON( "b" << 1 ) ), toBson( document ) ); } }; /** Shallow copy clone of a multi field Document. */ class CloneMultipleFields { public: void run() { intrusive_ptr document = fromBson( fromjson( "{a:1,b:['ra',4],c:{z:1},d:'lal'}" ) ); intrusive_ptr clonedDocument = document->clone(); ASSERT_EQUALS( 0, Document::compare( document, clonedDocument ) ); } }; /** FieldIterator for an empty Document. */ class FieldIteratorEmpty { public: void run() { scoped_ptr iterator( Document::create()->createFieldIterator() ); ASSERT( !iterator->more() ); } }; /** FieldIterator for a single field Document. */ class FieldIteratorSingle { public: void run() { scoped_ptr iterator ( fromBson( BSON( "a" << 1 ) )->createFieldIterator() ); ASSERT( iterator->more() ); Document::FieldPair field = iterator->next(); ASSERT_EQUALS( "a", field.first ); ASSERT_EQUALS( 1, field.second->getInt() ); ASSERT( !iterator->more() ); } }; /** FieldIterator for a multiple field Document. */ class FieldIteratorMultiple { public: void run() { scoped_ptr iterator ( fromBson( BSON( "a" << 1 << "b" << 5.6 << "c" << "z" ) )-> createFieldIterator() ); ASSERT( iterator->more() ); Document::FieldPair field = iterator->next(); ASSERT_EQUALS( "a", field.first ); ASSERT_EQUALS( 1, field.second->getInt() ); ASSERT( iterator->more() ); field = iterator->next(); ASSERT_EQUALS( "b", field.first ); ASSERT_EQUALS( 5.6, field.second->getDouble() ); ASSERT( iterator->more() ); field = iterator->next(); ASSERT_EQUALS( "c", field.first ); ASSERT_EQUALS( "z", field.second->getString() ); ASSERT( !iterator->more() ); } }; } // namespace Document namespace Value { using mongo::Value; BSONObj toBson( const intrusive_ptr& value ) { BSONObjBuilder bob; value->addToBsonObj( &bob, "" ); return bob.obj(); } intrusive_ptr fromBson( const BSONObj& obj ) { BSONElement element = obj.firstElement(); return Value::createFromBsonElement( &element ); } void assertRoundTrips( const intrusive_ptr& value1 ) { BSONObj obj1 = toBson( value1 ); intrusive_ptr value2 = fromBson( obj1 ); BSONObj obj2 = toBson( value2 ); ASSERT_EQUALS( obj1, obj2 ); ASSERT( value1 == value2 ); } /** Int type. */ class Int { public: void run() { intrusive_ptr value = Value::createInt( 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() { intrusive_ptr value = Value::createLong( 99 ); ASSERT_EQUALS( 99, value->getLong() ); ASSERT_EQUALS( 99, value->getDouble() ); ASSERT_EQUALS( NumberLong, value->getType() ); assertRoundTrips( value ); } }; /** Double type. */ class Double { public: void run() { intrusive_ptr value = Value::createDouble( 5.5 ); ASSERT_EQUALS( 5.5, value->getDouble() ); ASSERT_EQUALS( NumberDouble, value->getType() ); assertRoundTrips( value ); } }; /** String type. */ class String { public: void run() { intrusive_ptr value = Value::createString( "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() ); intrusive_ptr value = fromBson( objWithNull ); ASSERT_EQUALS( withNull, value->getString() ); assertRoundTrips( value ); } }; /** Date type. */ class Date { public: void run() { intrusive_ptr value = Value::createDate(999); ASSERT_EQUALS( 999, value->getDate() ); ASSERT_EQUALS( mongo::Date, value->getType() ); assertRoundTrips( value ); } }; /** Timestamp type. */ class Timestamp { public: void run() { intrusive_ptr value = Value::createTimestamp( OpTime( 777 ) ); ASSERT( OpTime( 777 ) == value->getTimestamp() ); ASSERT_EQUALS( mongo::Timestamp, value->getType() ); assertRoundTrips( value ); } }; /** Document with no fields. */ class EmptyDocument { public: void run() { intrusive_ptr document = mongo::Document::create(); intrusive_ptr value = Value::createDocument( document ); ASSERT_EQUALS( document, value->getDocument() ); ASSERT_EQUALS( Object, value->getType() ); assertRoundTrips( value ); } }; /** Document type. */ class Document { public: void run() { intrusive_ptr document = mongo::Document::create(); document->addField( "a", Value::createInt( 5 ) ); document->addField( "apple", Value::createString( "rrr" ) ); document->addField( "banana", Value::createDouble( -.3 ) ); intrusive_ptr value = Value::createDocument( document ); // Check document pointers are equal. ASSERT_EQUALS( document, value->getDocument() ); // Check document contents. ASSERT_EQUALS( 5, document->getValue( "a" )->getInt() ); ASSERT_EQUALS( "rrr", document->getValue( "apple" )->getString() ); ASSERT_EQUALS( -.3, document->getValue( "banana" )->getDouble() ); ASSERT_EQUALS( Object, value->getType() ); assertRoundTrips( value ); } }; /** Array with no elements. */ class EmptyArray { public: void run() { vector > array; intrusive_ptr value = Value::createArray( array ); intrusive_ptr arrayIterator = value->getArray(); ASSERT( !arrayIterator->more() ); 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::createInt( 5 ) ); array.push_back( Value::createString( "lala" ) ); array.push_back( Value::createDouble( 3.14 ) ); intrusive_ptr value = Value::createArray( array ); intrusive_ptr arrayIterator = value->getArray(); ASSERT( arrayIterator->more() ); ASSERT_EQUALS( 5, arrayIterator->next()->getInt() ); ASSERT_EQUALS( "lala", arrayIterator->next()->getString() ); ASSERT_EQUALS( 3.14, arrayIterator->next()->getDouble() ); ASSERT( !arrayIterator->more() ); ASSERT_EQUALS( mongo::Array, value->getType() ); ASSERT_EQUALS( 3U, value->getArrayLength() ); assertRoundTrips( value ); } }; /** Oid type. */ class Oid { public: void run() { intrusive_ptr 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() { intrusive_ptr value = fromBson( BSON( "" << true ) ); ASSERT_EQUALS( true, value->getBool() ); ASSERT_EQUALS( mongo::Bool, value->getType() ); assertRoundTrips( value ); } }; /** Regex type. */ class Regex { public: void run() { intrusive_ptr value = fromBson( fromjson( "{'':/abc/}" ) ); ASSERT_EQUALS( "abc", value->getRegex() ); ASSERT_EQUALS( RegEx, value->getType() ); if ( 0 ) { // SERVER-6470 assertRoundTrips( value ); } } }; /** Symbol type (currently unsupported). */ class Symbol { public: void run() { BSONObjBuilder bob; bob.appendSymbol( "", "FOOBAR" ); intrusive_ptr value = fromBson( bob.obj() ); ASSERT_EQUALS( "FOOBAR", value->getSymbol() ); ASSERT_EQUALS( mongo::Symbol, value->getType() ); assertRoundTrips( value ); } }; /** Undefined type. */ class Undefined { public: void run() { intrusive_ptr value = Value::getUndefined(); ASSERT_EQUALS( mongo::Undefined, value->getType() ); assertRoundTrips( value ); } }; /** Null type. */ class Null { public: void run() { intrusive_ptr value = Value::getNull(); ASSERT_EQUALS( jstNULL, value->getType() ); assertRoundTrips( value ); } }; /** True value. */ class True { public: void run() { intrusive_ptr value = Value::getTrue(); ASSERT_EQUALS( true, value->getBool() ); ASSERT_EQUALS( mongo::Bool, value->getType() ); assertRoundTrips( value ); } }; /** False value. */ class False { public: void run() { intrusive_ptr value = Value::getFalse(); ASSERT_EQUALS( false, value->getBool() ); ASSERT_EQUALS( mongo::Bool, value->getType() ); assertRoundTrips( value ); } }; /** -1 value. */ class MinusOne { public: void run() { intrusive_ptr value = Value::getMinusOne(); ASSERT_EQUALS( -1, value->getInt() ); ASSERT_EQUALS( NumberInt, value->getType() ); assertRoundTrips( value ); } }; /** 0 value. */ class Zero { public: void run() { intrusive_ptr value = Value::getZero(); ASSERT_EQUALS( 0, value->getInt() ); ASSERT_EQUALS( NumberInt, value->getType() ); assertRoundTrips( value ); } }; /** 1 value. */ class One { public: void run() { intrusive_ptr value = Value::getOne(); 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 intrusive_ptr 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 { intrusive_ptr value() { return Value::createInt( 0 ); } }; /** Coerce -1 to bool. */ class NonZeroIntToBool : public ToBoolTrue { intrusive_ptr value() { return Value::createInt( -1 ); } }; /** Coerce 0LL to bool. */ class ZeroLongToBool : public ToBoolFalse { intrusive_ptr value() { return Value::createLong( 0 ); } }; /** Coerce 5LL to bool. */ class NonZeroLongToBool : public ToBoolTrue { intrusive_ptr value() { return Value::createLong( 5 ); } }; /** Coerce 0.0 to bool. */ class ZeroDoubleToBool : public ToBoolFalse { intrusive_ptr value() { return Value::createDouble( 0 ); } }; /** Coerce -1.3 to bool. */ class NonZeroDoubleToBool : public ToBoolTrue { intrusive_ptr value() { return Value::createDouble( -1.3 ); } }; /** Coerce "" to bool. */ class StringToBool : public ToBoolTrue { intrusive_ptr value() { return Value::createString( "" ); } }; /** Coerce {} to bool. */ class ObjectToBool : public ToBoolTrue { intrusive_ptr value() { return Value::createDocument( mongo::Document::create() ); } }; /** Coerce [] to bool. */ class ArrayToBool : public ToBoolTrue { intrusive_ptr value() { return Value::createArray( vector >() ); } }; /** Coerce Date(0) to bool. */ class DateToBool : public ToBoolTrue { intrusive_ptr value() { return Value::createDate(0); } }; /** Coerce // to bool. */ class RegexToBool : public ToBoolTrue { intrusive_ptr value() { return fromBson( fromjson( "{''://}" ) ); } }; /** Coerce true to bool. */ class TrueToBool : public ToBoolTrue { intrusive_ptr value() { return fromBson( BSON( "" << true ) ); } }; /** Coerce false to bool. */ class FalseToBool : public ToBoolFalse { intrusive_ptr value() { return fromBson( BSON( "" << false ) ); } }; /** Coerce null to bool. */ class NullToBool : public ToBoolFalse { intrusive_ptr value() { return Value::getNull(); } }; /** Coerce undefined to bool. */ class UndefinedToBool : public ToBoolFalse { intrusive_ptr value() { return Value::getUndefined(); } }; class ToIntBase { public: virtual ~ToIntBase() { } void run() { ASSERT_EQUALS( expected(), value()->coerceToInt() ); } protected: virtual intrusive_ptr value() = 0; virtual int expected() { return 0; } }; /** Coerce -5 to int. */ class IntToInt : public ToIntBase { intrusive_ptr value() { return Value::createInt( -5 ); } int expected() { return -5; } }; /** Coerce long to int. */ class LongToInt : public ToIntBase { intrusive_ptr value() { return Value::createLong( 0xff00000007LL ); } int expected() { return 7; } }; /** Coerce 9.8 to int. */ class DoubleToInt : public ToIntBase { intrusive_ptr value() { return Value::createDouble( 9.8 ); } int expected() { return 9; } }; /** Coerce null to int. */ class NullToInt : public ToIntBase { intrusive_ptr value() { return Value::getNull(); } }; /** Coerce undefined to int. */ class UndefinedToInt : public ToIntBase { intrusive_ptr value() { return Value::getUndefined(); } }; /** Coerce "" to int unsupported. */ class StringToInt { public: void run() { ASSERT_THROWS( Value::createString( "" )->coerceToInt(), UserException ); } }; class ToLongBase { public: virtual ~ToLongBase() { } void run() { ASSERT_EQUALS( expected(), value()->coerceToLong() ); } protected: virtual intrusive_ptr value() = 0; virtual long long expected() { return 0; } }; /** Coerce -5 to long. */ class IntToLong : public ToLongBase { intrusive_ptr value() { return Value::createInt( -5 ); } long long expected() { return -5; } }; /** Coerce long to long. */ class LongToLong : public ToLongBase { intrusive_ptr value() { return Value::createLong( 0xff00000007LL ); } long long expected() { return 0xff00000007LL; } }; /** Coerce 9.8 to long. */ class DoubleToLong : public ToLongBase { intrusive_ptr value() { return Value::createDouble( 9.8 ); } long long expected() { return 9; } }; /** Coerce null to long. */ class NullToLong : public ToLongBase { intrusive_ptr value() { return Value::getNull(); } }; /** Coerce undefined to long. */ class UndefinedToLong : public ToLongBase { intrusive_ptr value() { return Value::getUndefined(); } }; /** Coerce string to long unsupported. */ class StringToLong { public: void run() { ASSERT_THROWS( Value::createString( "" )->coerceToLong(), UserException ); } }; class ToDoubleBase { public: virtual ~ToDoubleBase() { } void run() { ASSERT_EQUALS( expected(), value()->coerceToDouble() ); } protected: virtual intrusive_ptr value() = 0; virtual double expected() { return 0; } }; /** Coerce -5 to double. */ class IntToDouble : public ToDoubleBase { intrusive_ptr value() { return Value::createInt( -5 ); } double expected() { return -5; } }; /** Coerce long to double. */ class LongToDouble : public ToDoubleBase { intrusive_ptr value() { // A long that cannot be exactly represented as a double. return Value::createDouble( 0x8fffffffffffffffLL ); } double expected() { return (double)0x8fffffffffffffffLL; } }; /** Coerce double to double. */ class DoubleToDouble : public ToDoubleBase { intrusive_ptr value() { return Value::createDouble( 9.8 ); } double expected() { return 9.8; } }; /** Coerce null to double. */ class NullToDouble : public ToDoubleBase { intrusive_ptr value() { return Value::getNull(); } }; /** Coerce undefined to double. */ class UndefinedToDouble : public ToDoubleBase { intrusive_ptr value() { return Value::getUndefined(); } }; /** Coerce string to double unsupported. */ class StringToDouble { public: void run() { ASSERT_THROWS( Value::createString( "" )->coerceToDouble(), UserException ); } }; class ToDateBase { public: virtual ~ToDateBase() { } void run() { ASSERT_EQUALS( expected(), value()->coerceToDate() ); } protected: virtual intrusive_ptr value() = 0; virtual long long expected() = 0; }; /** Coerce date to date. */ class DateToDate : public ToDateBase { intrusive_ptr value() { return Value::createDate(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 { intrusive_ptr value() { return Value::createTimestamp( OpTime( 777, 666 ) ); } long long expected() { return 777 * 1000; } }; /** Coerce string to date unsupported. */ class StringToDate { public: void run() { ASSERT_THROWS( Value::createString( "" )->coerceToDate(), UserException ); } }; class ToStringBase { public: virtual ~ToStringBase() { } void run() { ASSERT_EQUALS( expected(), value()->coerceToString() ); } protected: virtual intrusive_ptr value() = 0; virtual string expected() { return ""; } }; /** Coerce -0.2 to string. */ class DoubleToString : public ToStringBase { intrusive_ptr value() { return Value::createDouble( -0.2 ); } string expected() { return "-0.2"; } }; /** Coerce -4 to string. */ class IntToString : public ToStringBase { intrusive_ptr value() { return Value::createInt( -4 ); } string expected() { return "-4"; } }; /** Coerce 10000LL to string. */ class LongToString : public ToStringBase { intrusive_ptr value() { return Value::createLong( 10000 ); } string expected() { return "10000"; } }; /** Coerce string to string. */ class StringToString : public ToStringBase { intrusive_ptr value() { return Value::createString( "fO_o" ); } string expected() { return "fO_o"; } }; /** Coerce timestamp to string. */ class TimestampToString : public ToStringBase { intrusive_ptr value() { return Value::createTimestamp( OpTime( 1, 2 ) ); } string expected() { return OpTime( 1, 2 ).toStringPretty(); } }; /** Coerce date to string. */ class DateToString : public ToStringBase { intrusive_ptr value() { return Value::createDate(1234567890LL*1000); } string expected() { return "2009-02-13T23:31:30"; } // from js }; /** Coerce null to string. */ class NullToString : public ToStringBase { intrusive_ptr value() { return Value::getNull(); } }; /** Coerce undefined to string. */ class UndefinedToString : public ToStringBase { intrusive_ptr value() { return Value::getUndefined(); } }; /** Coerce document to string unsupported. */ class DocumentToString { public: void run() { ASSERT_THROWS( Value::createDocument ( mongo::Document::create() )->coerceToString(), UserException ); } }; /** Coerce timestamp to timestamp. */ class TimestampToTimestamp { public: void run() { intrusive_ptr value = Value::createTimestamp( OpTime( 1010 ) ); ASSERT( OpTime( 1010 ) == value->coerceToTimestamp() ); } }; /** Coerce date to timestamp unsupported. */ class DateToTimestamp { public: void run() { ASSERT_THROWS( Value::createDate(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. assertWidest( NumberInt, NumberInt, jstNULL ); assertWidest( NumberInt, NumberInt, Undefined ); assertWidest( NumberLong, NumberLong, jstNULL ); assertWidest( NumberLong, NumberLong, Undefined ); assertWidest( NumberDouble, NumberDouble, jstNULL ); assertWidest( NumberDouble, 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::createDouble( 4.4 )->addToBsonObj( &bob, "a" ); Value::createInt( 22 )->addToBsonObj( &bob, "b" ); Value::createString( "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::createDouble( 4.4 )->addToBsonArray( &bab ); Value::createInt( 22 )->addToBsonArray( &bab ); Value::createString( "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() ); if ( 0 ) { // SERVER-6126 assertComparison( -1, numeric_limits::quiet_NaN(), 5 ); } // Otherwise comparing different types is not supported. ASSERT_THROWS( assertComparison( 0, 90, "abc" ), UserException ); ASSERT_THROWS( assertComparison( 0, 90, BSON( "a" << "b" ) ), UserException ); // 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" ); if ( 0 ) { // 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}}" ) ); // 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]}" ) ); // Assertion on nested type mismatch. ASSERT_THROWS( assertComparison( 0, fromjson( "{'':[0]}" ), fromjson( "{'':['']}" ) ), UserException ); // 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( 0, fromjson( "{'':/a/}" ), // Regex options are ignored. fromjson( "{'':/a/i}" ) ); assertComparison( -1, fromjson( "{'':/a/}" ), fromjson( "{'':/aa/}" ) ); // Timestamp. assertComparison( 0, OpTime( 1234 ), OpTime( 1234 ) ); assertComparison( -1, OpTime( 4 ), OpTime( 1234 ) ); } private: template void assertComparison( int expectedResult, const T& a, const U& b ) { assertComparison( expectedResult, BSON( "" << a ), BSON( "" << b ) ); } void assertComparison( int expectedResult, const OpTime& a, const OpTime& b ) { BSONObjBuilder first; first.appendTimestamp( "", a.asDate() ); BSONObjBuilder second; second.appendTimestamp( "", b.asDate() ); assertComparison( expectedResult, first.obj(), second.obj() ); } int cmp( const BSONObj& a, const BSONObj& b ) { int result = Value::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 ) { // Equal values must hash equally. ASSERT_EQUALS( hash( a ), hash( b ) ); } } size_t hash( const BSONObj& obj ) { size_t seed = 0xf00ba6; fromBson( obj )->hash_combine( seed ); return seed; } }; } // 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(); if ( 0 ) { 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(); } } myall; } // namespace DocumentTests