diff options
author | Aaron <aaron@10gen.com> | 2012-07-22 21:40:19 -0700 |
---|---|---|
committer | Aaron <aaron@10gen.com> | 2012-07-24 12:08:50 -0700 |
commit | 297a506a2efc5eb30cfd490f3941644d9c2538d4 (patch) | |
tree | 049a63ec3a19b95f16b5b5b5f70f31bf7b2c524a /src | |
parent | d8e2c96a504a77f732b8f59ad956a6e033c5f1fd (diff) | |
download | mongo-297a506a2efc5eb30cfd490f3941644d9c2538d4.tar.gz |
QA-106 Unit tests and minor comment fixes for Document, Value, and related classes.
Diffstat (limited to 'src')
-rwxr-xr-x | src/mongo/db/pipeline/document.h | 2 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/value.cpp | 2 | ||||
-rw-r--r-- | src/mongo/dbtests/documenttests.cpp | 1256 |
3 files changed, 1258 insertions, 2 deletions
diff --git a/src/mongo/db/pipeline/document.h b/src/mongo/db/pipeline/document.h index 1e5cd07ea39..58707524dc0 100755 --- a/src/mongo/db/pipeline/document.h +++ b/src/mongo/db/pipeline/document.h @@ -97,7 +97,7 @@ namespace mongo { Set the given field to be at the specified position in the Document. This will replace any field that is currently in that position. The index must be within the current range of field - indices. + indices, otherwise behavior is undefined. pValue.get() may be NULL, in which case the field will be removed. fieldName is ignored in this case. diff --git a/src/mongo/db/pipeline/value.cpp b/src/mongo/db/pipeline/value.cpp index d67d6bef7d0..771d3d34d2f 100755 --- a/src/mongo/db/pipeline/value.cpp +++ b/src/mongo/db/pipeline/value.cpp @@ -1024,7 +1024,7 @@ namespace mongo { } } - /* NOTREACHED */ + // Reachable, but callers must subsequently err out in this case. return Undefined; } diff --git a/src/mongo/dbtests/documenttests.cpp b/src/mongo/dbtests/documenttests.cpp new file mode 100644 index 00000000000..6721b3c5274 --- /dev/null +++ b/src/mongo/dbtests/documenttests.cpp @@ -0,0 +1,1256 @@ +// 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 <http://www.gnu.org/licenses/>. + */ + +#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>& document ) { + BSONObjBuilder bob; + document->toBson( &bob ); + return bob.obj(); + } + + intrusive_ptr<Document> fromBson( BSONObj obj ) { + return Document::createFromBsonObj( &obj ); + } + + void assertRoundTrips( const intrusive_ptr<Document>& document1 ) { + BSONObj obj1 = toBson( document1 ); + intrusive_ptr<Document> 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 = Document::create(); + ASSERT_EQUALS( 0U, document->getFieldCount() ); + assertRoundTrips( document ); + } + }; + + /** Create a Document from a BSONObj. */ + class CreateFromBsonObj { + public: + void run() { + intrusive_ptr<Document> 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 = 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> 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> 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> 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( NULL, 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> document = fromBson( BSON( "a" << BSON( "b" << 1 ) ) ); + intrusive_ptr<Document> 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> document = + fromBson( fromjson( "{a:1,b:['ra',4],c:{z:1},d:'lal'}" ) ); + intrusive_ptr<Document> clonedDocument = document->clone(); + ASSERT_EQUALS( 0, Document::compare( document, clonedDocument ) ); + } + }; + + /** FieldIterator for an empty Document. */ + class FieldIteratorEmpty { + public: + void run() { + scoped_ptr<FieldIterator> iterator( Document::create()->createFieldIterator() ); + ASSERT( !iterator->more() ); + } + }; + + /** FieldIterator for a single field Document. */ + class FieldIteratorSingle { + public: + void run() { + scoped_ptr<FieldIterator> 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<FieldIterator> 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<const Value>& value ) { + BSONObjBuilder bob; + value->addToBsonObj( &bob, "" ); + return bob.obj(); + } + + intrusive_ptr<const Value> fromBson( const BSONObj& obj ) { + BSONElement element = obj.firstElement(); + return Value::createFromBsonElement( &element ); + } + + void assertRoundTrips( const intrusive_ptr<const Value>& value1 ) { + BSONObj obj1 = toBson( value1 ); + intrusive_ptr<const Value> value2 = fromBson( obj1 ); + BSONObj obj2 = toBson( value2 ); + ASSERT_EQUALS( obj1, obj2 ); + ASSERT( value1 == value2 ); + } + + /** Int type. */ + class Int { + public: + void run() { + intrusive_ptr<const Value> 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<const Value> 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<const Value> 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<const Value> 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<const Value> value = fromBson( objWithNull ); + ASSERT_EQUALS( withNull, value->getString() ); + assertRoundTrips( value ); + } + }; + + /** Date type. */ + class Date { + public: + void run() { + intrusive_ptr<const Value> value = Value::createDate( Date_t( 999 ) ); + ASSERT_EQUALS( Date_t( 999 ), value->getDate() ); + ASSERT_EQUALS( mongo::Date, value->getType() ); + assertRoundTrips( value ); + } + }; + + /** Timestamp type. */ + class Timestamp { + public: + void run() { + intrusive_ptr<const Value> 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<mongo::Document> document = mongo::Document::create(); + intrusive_ptr<const Value> 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<mongo::Document> document = mongo::Document::create(); + document->addField( "a", Value::createInt( 5 ) ); + document->addField( "apple", Value::createString( "rrr" ) ); + document->addField( "banana", Value::createDouble( -.3 ) ); + intrusive_ptr<const Value> 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<intrusive_ptr<const Value> > array; + intrusive_ptr<const Value> value = Value::createArray( array ); + intrusive_ptr<ValueIterator> 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<intrusive_ptr<const Value> > array; + array.push_back( Value::createInt( 5 ) ); + array.push_back( Value::createString( "lala" ) ); + array.push_back( Value::createDouble( 3.14 ) ); + intrusive_ptr<const Value> value = Value::createArray( array ); + intrusive_ptr<ValueIterator> 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<const 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() { + intrusive_ptr<const 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() { + intrusive_ptr<const Value> 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<const Value> 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<const Value> value = Value::getUndefined(); + ASSERT_EQUALS( mongo::Undefined, value->getType() ); + assertRoundTrips( value ); + } + }; + + /** Null type. */ + class Null { + public: + void run() { + intrusive_ptr<const Value> value = Value::getNull(); + ASSERT_EQUALS( jstNULL, value->getType() ); + assertRoundTrips( value ); + } + }; + + /** True value. */ + class True { + public: + void run() { + intrusive_ptr<const Value> 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<const Value> 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<const Value> value = Value::getMinusOne(); + ASSERT_EQUALS( -1, value->getInt() ); + ASSERT_EQUALS( NumberInt, value->getType() ); + assertRoundTrips( value ); + } + }; + + /** 0 value. */ + class Zero { + public: + void run() { + intrusive_ptr<const Value> value = Value::getZero(); + ASSERT_EQUALS( 0, value->getInt() ); + ASSERT_EQUALS( NumberInt, value->getType() ); + assertRoundTrips( value ); + } + }; + + /** 1 value. */ + class One { + public: + void run() { + intrusive_ptr<const Value> 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<const 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 { + intrusive_ptr<const Value> value() { return Value::createInt( 0 ); } + }; + + /** Coerce -1 to bool. */ + class NonZeroIntToBool : public ToBoolTrue { + intrusive_ptr<const Value> value() { return Value::createInt( -1 ); } + }; + + /** Coerce 0LL to bool. */ + class ZeroLongToBool : public ToBoolFalse { + intrusive_ptr<const Value> value() { return Value::createLong( 0 ); } + }; + + /** Coerce 5LL to bool. */ + class NonZeroLongToBool : public ToBoolTrue { + intrusive_ptr<const Value> value() { return Value::createLong( 5 ); } + }; + + /** Coerce 0.0 to bool. */ + class ZeroDoubleToBool : public ToBoolFalse { + intrusive_ptr<const Value> value() { return Value::createDouble( 0 ); } + }; + + /** Coerce -1.3 to bool. */ + class NonZeroDoubleToBool : public ToBoolTrue { + intrusive_ptr<const Value> value() { return Value::createDouble( -1.3 ); } + }; + + /** Coerce "" to bool. */ + class StringToBool : public ToBoolTrue { + intrusive_ptr<const Value> value() { return Value::createString( "" ); } + }; + + /** Coerce {} to bool. */ + class ObjectToBool : public ToBoolTrue { + intrusive_ptr<const Value> value() { + return Value::createDocument( mongo::Document::create() ); + } + }; + + /** Coerce [] to bool. */ + class ArrayToBool : public ToBoolTrue { + intrusive_ptr<const Value> value() { + return Value::createArray( vector<intrusive_ptr<const Value> >() ); + } + }; + + /** Coerce Date_t() to bool. */ + class DateToBool : public ToBoolTrue { + intrusive_ptr<const Value> value() { return Value::createDate( Date_t() ); } + }; + + /** Coerce // to bool. */ + class RegexToBool : public ToBoolTrue { + intrusive_ptr<const Value> value() { return fromBson( fromjson( "{''://}" ) ); } + }; + + /** Coerce true to bool. */ + class TrueToBool : public ToBoolTrue { + intrusive_ptr<const Value> value() { return fromBson( BSON( "" << true ) ); } + }; + + /** Coerce false to bool. */ + class FalseToBool : public ToBoolFalse { + intrusive_ptr<const Value> value() { return fromBson( BSON( "" << false ) ); } + }; + + /** Coerce null to bool. */ + class NullToBool : public ToBoolFalse { + intrusive_ptr<const Value> value() { return Value::getNull(); } + }; + + /** Coerce undefined to bool. */ + class UndefinedToBool : public ToBoolFalse { + intrusive_ptr<const Value> value() { return Value::getUndefined(); } + }; + + class ToIntBase { + public: + virtual ~ToIntBase() { + } + void run() { + ASSERT_EQUALS( expected(), value()->coerceToInt() ); + } + protected: + virtual intrusive_ptr<const Value> value() = 0; + virtual int expected() { return 0; } + }; + + /** Coerce -5 to int. */ + class IntToInt : public ToIntBase { + intrusive_ptr<const Value> value() { return Value::createInt( -5 ); } + int expected() { return -5; } + }; + + /** Coerce long to int. */ + class LongToInt : public ToIntBase { + intrusive_ptr<const Value> value() { return Value::createLong( 0xff00000007LL ); } + int expected() { return 7; } + }; + + /** Coerce 9.8 to int. */ + class DoubleToInt : public ToIntBase { + intrusive_ptr<const Value> value() { return Value::createDouble( 9.8 ); } + int expected() { return 9; } + }; + + /** Coerce null to int. */ + class NullToInt : public ToIntBase { + intrusive_ptr<const Value> value() { return Value::getNull(); } + }; + + /** Coerce undefined to int. */ + class UndefinedToInt : public ToIntBase { + intrusive_ptr<const Value> 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<const Value> value() = 0; + virtual long long expected() { return 0; } + }; + + /** Coerce -5 to long. */ + class IntToLong : public ToLongBase { + intrusive_ptr<const Value> value() { return Value::createInt( -5 ); } + long long expected() { return -5; } + }; + + /** Coerce long to long. */ + class LongToLong : public ToLongBase { + intrusive_ptr<const Value> value() { return Value::createLong( 0xff00000007LL ); } + long long expected() { return 0xff00000007LL; } + }; + + /** Coerce 9.8 to long. */ + class DoubleToLong : public ToLongBase { + intrusive_ptr<const Value> value() { return Value::createDouble( 9.8 ); } + long long expected() { return 9; } + }; + + /** Coerce null to long. */ + class NullToLong : public ToLongBase { + intrusive_ptr<const Value> value() { return Value::getNull(); } + }; + + /** Coerce undefined to long. */ + class UndefinedToLong : public ToLongBase { + intrusive_ptr<const Value> 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<const Value> value() = 0; + virtual double expected() { return 0; } + }; + + /** Coerce -5 to double. */ + class IntToDouble : public ToDoubleBase { + intrusive_ptr<const Value> value() { return Value::createInt( -5 ); } + double expected() { return -5; } + }; + + /** Coerce long to double. */ + class LongToDouble : public ToDoubleBase { + intrusive_ptr<const Value> 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<const Value> value() { return Value::createDouble( 9.8 ); } + double expected() { return 9.8; } + }; + + /** Coerce null to double. */ + class NullToDouble : public ToDoubleBase { + intrusive_ptr<const Value> value() { return Value::getNull(); } + }; + + /** Coerce undefined to double. */ + class UndefinedToDouble : public ToDoubleBase { + intrusive_ptr<const Value> 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<const Value> value() = 0; + virtual Date_t expected() = 0; + }; + + /** Coerce date to date. */ + class DateToDate : public ToDateBase { + intrusive_ptr<const Value> value() { return Value::createDate( Date_t( 888 ) ); } + Date_t expected() { return Date_t( 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<const Value> value() { + return Value::createTimestamp( OpTime( 777, 666 ) ); + } + Date_t expected() { return Date_t( 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<const Value> value() = 0; + virtual string expected() { return ""; } + }; + + /** Coerce -0.2 to string. */ + class DoubleToString : public ToStringBase { + intrusive_ptr<const Value> value() { return Value::createDouble( -0.2 ); } + string expected() { return "-0.2"; } + }; + + /** Coerce -4 to string. */ + class IntToString : public ToStringBase { + intrusive_ptr<const Value> value() { return Value::createInt( -4 ); } + string expected() { return "-4"; } + }; + + /** Coerce 10000LL to string. */ + class LongToString : public ToStringBase { + intrusive_ptr<const Value> value() { return Value::createLong( 10000 ); } + string expected() { return "10000"; } + }; + + /** Coerce string to string. */ + class StringToString : public ToStringBase { + intrusive_ptr<const Value> value() { return Value::createString( "fO_o" ); } + string expected() { return "fO_o"; } + }; + + /** Coerce timestamp to string. */ + class TimestampToString : public ToStringBase { + intrusive_ptr<const Value> value() { + return Value::createTimestamp( OpTime( 1, 2 ) ); + } + string expected() { return OpTime( 1, 2 ).toStringPretty(); } + }; + + /** Coerce date to string. */ + class DateToString : public ToStringBase { + intrusive_ptr<const Value> value() { return Value::createDate( Date_t( 12345 ) ); } + string expected() { return Date_t( 12345 ).toString(); } + }; + + /** Coerce null to string. */ + class NullToString : public ToStringBase { + intrusive_ptr<const Value> value() { return Value::getNull(); } + }; + + /** Coerce undefined to string. */ + class UndefinedToString : public ToStringBase { + intrusive_ptr<const Value> 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<const Value> value = Value::createTimestamp( OpTime( 1010 ) ); + ASSERT( OpTime( 1010 ) == value->coerceToTimestamp() ); + } + }; + + /** Coerce date to timestamp unsupported. */ + class DateToTimestamp { + public: + void run() { + ASSERT_THROWS( Value::createDate( 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. + 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<double>::quiet_NaN(), + numeric_limits<double>::signaling_NaN() ); + if ( 0 ) { // SERVER-6126 + assertComparison( -1, numeric_limits<double>::quiet_NaN(), 5 ); + } + + // Otherwise comparing different types is not supported. + ASSERT_THROWS( assertComparison( NULL, 90, "abc" ), UserException ); + ASSERT_THROWS( assertComparison( NULL, 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( NULL, 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<class T,class U> + 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<Document::Create>(); + add<Document::CreateFromBsonObj>(); + add<Document::AddField>(); + add<Document::GetValue>(); + add<Document::SetField>(); + add<Document::GetFieldIndex>(); + add<Document::Compare>(); + add<Document::CompareNamedNull>(); + add<Document::Clone>(); + add<Document::CloneMultipleFields>(); + add<Document::FieldIteratorEmpty>(); + add<Document::FieldIteratorSingle>(); + add<Document::FieldIteratorMultiple>(); + + add<Value::Int>(); + add<Value::Long>(); + add<Value::Double>(); + add<Value::String>(); + if ( 0 ) { // SERVER-6556 + add<Value::StringWithNull>(); + } + add<Value::Date>(); + add<Value::Timestamp>(); + add<Value::EmptyDocument>(); + add<Value::EmptyArray>(); + add<Value::Array>(); + add<Value::Oid>(); + add<Value::Bool>(); + add<Value::Regex>(); + if ( 0 ) { + add<Value::Symbol>(); + } + add<Value::Undefined>(); + add<Value::Null>(); + add<Value::True>(); + add<Value::False>(); + add<Value::MinusOne>(); + add<Value::Zero>(); + add<Value::One>(); + + add<Value::Coerce::ZeroIntToBool>(); + add<Value::Coerce::NonZeroIntToBool>(); + add<Value::Coerce::ZeroLongToBool>(); + add<Value::Coerce::NonZeroLongToBool>(); + add<Value::Coerce::ZeroDoubleToBool>(); + add<Value::Coerce::NonZeroDoubleToBool>(); + add<Value::Coerce::StringToBool>(); + add<Value::Coerce::ObjectToBool>(); + add<Value::Coerce::ArrayToBool>(); + add<Value::Coerce::DateToBool>(); + add<Value::Coerce::RegexToBool>(); + add<Value::Coerce::TrueToBool>(); + add<Value::Coerce::FalseToBool>(); + add<Value::Coerce::NullToBool>(); + add<Value::Coerce::UndefinedToBool>(); + add<Value::Coerce::IntToInt>(); + add<Value::Coerce::LongToInt>(); + add<Value::Coerce::DoubleToInt>(); + add<Value::Coerce::NullToInt>(); + add<Value::Coerce::UndefinedToInt>(); + add<Value::Coerce::StringToInt>(); + add<Value::Coerce::IntToLong>(); + add<Value::Coerce::LongToLong>(); + add<Value::Coerce::DoubleToLong>(); + add<Value::Coerce::NullToLong>(); + add<Value::Coerce::UndefinedToLong>(); + add<Value::Coerce::StringToLong>(); + add<Value::Coerce::IntToDouble>(); + add<Value::Coerce::LongToDouble>(); + add<Value::Coerce::DoubleToDouble>(); + add<Value::Coerce::NullToDouble>(); + add<Value::Coerce::UndefinedToDouble>(); + add<Value::Coerce::StringToDouble>(); + add<Value::Coerce::DateToDate>(); + add<Value::Coerce::TimestampToDate>(); + add<Value::Coerce::StringToDate>(); + add<Value::Coerce::DoubleToString>(); + add<Value::Coerce::IntToString>(); + add<Value::Coerce::LongToString>(); + add<Value::Coerce::StringToString>(); + add<Value::Coerce::TimestampToString>(); + add<Value::Coerce::DateToString>(); + add<Value::Coerce::NullToString>(); + add<Value::Coerce::UndefinedToString>(); + add<Value::Coerce::DocumentToString>(); + add<Value::Coerce::TimestampToTimestamp>(); + add<Value::Coerce::DateToTimestamp>(); + + add<Value::GetWidestNumeric>(); + add<Value::AddToBsonObj>(); + add<Value::AddToBsonArray>(); + add<Value::Compare>(); + } + } myall; + +} // namespace DocumentTests |