summaryrefslogtreecommitdiff
path: root/src/mongo/dbtests/documenttests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/dbtests/documenttests.cpp')
-rw-r--r--src/mongo/dbtests/documenttests.cpp1256
1 files changed, 1256 insertions, 0 deletions
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