// expressiontests.cpp : Unit tests for Expression classes. /** * Copyright (C) 2012 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #define MONGO_PCH_WHITELISTED #include "mongo/platform/basic.h" #include "mongo/pch.h" #undef MONGO_PCH_WHITELISTED #include "mongo/db/pipeline/document.h" #include "mongo/db/pipeline/expression.h" #include "mongo/dbtests/dbtests.h" namespace ExpressionTests { using boost::intrusive_ptr; /** Convert BSONObj to a BSONObj with our $const wrappings. */ static BSONObj constify(const BSONObj& obj, bool parentIsArray=false) { BSONObjBuilder bob; for (BSONObjIterator itr(obj); itr.more(); itr.next()) { BSONElement elem = *itr; if (elem.type() == Object) { bob << elem.fieldName() << constify(elem.Obj(), false); } else if (elem.type() == Array && !parentIsArray) { // arrays within arrays are treated as constant values by the real parser bob << elem.fieldName() << BSONArray(constify(elem.Obj(), true)); } else if (str::equals(elem.fieldName(), "$const") || (elem.type() == mongo::String && elem.valuestrsafe()[0] == '$')) { bob.append(elem); } else { bob.append(elem.fieldName(), BSON("$const" << elem)); } } return bob.obj(); } /** Check binary equality, ensuring use of the same numeric types. */ static void assertBinaryEqual( const BSONObj& expected, const BSONObj& actual ) { ASSERT_EQUALS( expected, actual ); ASSERT( expected.binaryEqual( actual ) ); } /** Convert Value to a wrapped BSONObj with an empty string field name. */ static BSONObj toBson( const Value& value ) { BSONObjBuilder bob; value.addToBsonObj( &bob, "" ); return bob.obj(); } /** Convert Expression to BSON. */ static BSONObj expressionToBson( const intrusive_ptr& expression ) { return BSON("" << expression->serialize(false)).firstElement().embeddedObject().getOwned(); } /** Convert Document to BSON. */ static BSONObj toBson( const Document& document ) { return document.toBson(); } /** Create a Document from a BSONObj. */ Document fromBson( BSONObj obj ) { return Document(obj); } /** Create a Value from a BSONObj. */ Value valueFromBson( BSONObj obj ) { BSONElement element = obj.firstElement(); return Value( element ); } namespace Add { class ExpectedResultBase { public: virtual ~ExpectedResultBase() {} void run() { intrusive_ptr expression = new ExpressionAdd(); populateOperands( expression ); ASSERT_EQUALS( expectedResult(), toBson( expression->evaluate( Document() ) ) ); } protected: virtual void populateOperands( intrusive_ptr& expression ) = 0; virtual BSONObj expectedResult() = 0; }; /** $add with a NULL Document pointer, as called by ExpressionNary::optimize(). */ class NullDocument { public: void run() { intrusive_ptr expression = new ExpressionAdd(); expression->addOperand( ExpressionConstant::create( Value( 2 ) ) ); ASSERT_EQUALS( BSON( "" << 2 ), toBson( expression->evaluate( Document() ) ) ); } }; /** $add without operands. */ class NoOperands : public ExpectedResultBase { void populateOperands( intrusive_ptr& expression ) {} virtual BSONObj expectedResult() { return BSON( "" << 0 ); } }; /** String type unsupported. */ class String { public: void run() { intrusive_ptr expression = new ExpressionAdd(); expression->addOperand( ExpressionConstant::create( Value( "a" ) ) ); ASSERT_THROWS( expression->evaluate( Document() ), UserException ); } }; /** Bool type unsupported. */ class Bool { public: void run() { intrusive_ptr expression = new ExpressionAdd(); expression->addOperand( ExpressionConstant::create( Value(true) ) ); ASSERT_THROWS( expression->evaluate( Document() ), UserException ); } }; class SingleOperandBase : public ExpectedResultBase { void populateOperands( intrusive_ptr& expression ) { expression->addOperand( ExpressionConstant::create( valueFromBson( operand() ) ) ); } BSONObj expectedResult() { return operand(); } protected: virtual BSONObj operand() = 0; }; /** Single int argument. */ class Int : public SingleOperandBase { BSONObj operand() { return BSON( "" << 1 ); } }; /** Single long argument. */ class Long : public SingleOperandBase { BSONObj operand() { return BSON( "" << 5555LL ); } }; /** Single double argument. */ class Double : public SingleOperandBase { BSONObj operand() { return BSON( "" << 99.99 ); } }; /** Single Date argument. */ class Date : public SingleOperandBase { BSONObj operand() { return BSON( "" << Date_t(12345) ); } }; /** Single null argument. */ class Null : public SingleOperandBase { BSONObj operand() { return BSON( "" << BSONNULL ); } BSONObj expectedResult() { return BSON( "" << BSONNULL ); } }; /** Single undefined argument. */ class Undefined : public SingleOperandBase { BSONObj operand() { return fromjson( "{'':undefined}" ); } BSONObj expectedResult() { return BSON( "" << BSONNULL ); } }; class TwoOperandBase : public ExpectedResultBase { public: TwoOperandBase() : _reverse() { } void run() { ExpectedResultBase::run(); // Now add the operands in the reverse direction. _reverse = true; ExpectedResultBase::run(); } protected: void populateOperands( intrusive_ptr& expression ) { expression->addOperand( ExpressionConstant::create ( valueFromBson( _reverse ? operand2() : operand1() ) ) ); expression->addOperand( ExpressionConstant::create ( valueFromBson( _reverse ? operand1() : operand2() ) ) ); } virtual BSONObj operand1() = 0; virtual BSONObj operand2() = 0; private: bool _reverse; }; /** Add two ints. */ class IntInt : public TwoOperandBase { BSONObj operand1() { return BSON( "" << 1 ); } BSONObj operand2() { return BSON( "" << 5 ); } BSONObj expectedResult() { return BSON( "" << 6 ); } }; /** Adding two large ints produces a long, not an overflowed int. */ class IntIntNoOverflow : public TwoOperandBase { BSONObj operand1() { return BSON( "" << numeric_limits::max() ); } BSONObj operand2() { return BSON( "" << numeric_limits::max() ); } BSONObj expectedResult() { return BSON( "" << ( (long long)( numeric_limits::max() ) + numeric_limits::max() ) ); } }; /** Adding an int and a long produces a long. */ class IntLong : public TwoOperandBase { BSONObj operand1() { return BSON( "" << 1 ); } BSONObj operand2() { return BSON( "" << 9LL ); } BSONObj expectedResult() { return BSON( "" << 10LL ); } }; /** Adding an int and a long overflows. */ class IntLongOverflow : public TwoOperandBase { BSONObj operand1() { return BSON( "" << numeric_limits::max() ); } BSONObj operand2() { return BSON( "" << numeric_limits::max() ); } BSONObj expectedResult() { return BSON( "" << ( numeric_limits::max() + numeric_limits::max() ) ); } }; /** Adding an int and a double produces a double. */ class IntDouble : public TwoOperandBase { BSONObj operand1() { return BSON( "" << 9 ); } BSONObj operand2() { return BSON( "" << 1.1 ); } BSONObj expectedResult() { return BSON( "" << 10.1 ); } }; /** Adding an int and a Date produces a Date. */ class IntDate : public TwoOperandBase { BSONObj operand1() { return BSON( "" << 6 ); } BSONObj operand2() { return BSON( "" << Date_t(123450) ); } BSONObj expectedResult() { return BSON( "" << Date_t(123456) ); } }; /** Adding a long and a double produces a double. */ class LongDouble : public TwoOperandBase { BSONObj operand1() { return BSON( "" << 9LL ); } BSONObj operand2() { return BSON( "" << 1.1 ); } BSONObj expectedResult() { return BSON( "" << 10.1 ); } }; /** Adding a long and a double does not overflow. */ class LongDoubleNoOverflow : public TwoOperandBase { BSONObj operand1() { return BSON( "" << numeric_limits::max() ); } BSONObj operand2() { return BSON( "" << double( numeric_limits::max() ) ); } BSONObj expectedResult() { return BSON( "" << numeric_limits::max() + double( numeric_limits::max() ) ); } }; /** Adding an int and null. */ class IntNull : public TwoOperandBase { BSONObj operand1() { return BSON( "" << 1 ); } BSONObj operand2() { return BSON( "" << BSONNULL ); } BSONObj expectedResult() { return BSON( "" << BSONNULL ); } }; /** Adding a long and undefined. */ class LongUndefined : public TwoOperandBase { BSONObj operand1() { return BSON( "" << 5LL ); } BSONObj operand2() { return fromjson( "{'':undefined}" ); } BSONObj expectedResult() { return BSON( "" << BSONNULL ); } }; } // namespace Add namespace And { class ExpectedResultBase { public: virtual ~ExpectedResultBase() { } void run() { BSONObj specObject = BSON( "" << spec() ); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) ); ASSERT_EQUALS( BSON( "" << expectedResult() ), toBson( expression->evaluate( fromBson( BSON( "a" << 1 ) ) ) ) ); intrusive_ptr optimized = expression->optimize(); ASSERT_EQUALS( BSON( "" << expectedResult() ), toBson( optimized->evaluate( fromBson( BSON( "a" << 1 ) ) ) ) ); } protected: virtual BSONObj spec() = 0; virtual bool expectedResult() = 0; }; class OptimizeBase { public: virtual ~OptimizeBase() { } void run() { BSONObj specObject = BSON( "" << spec() ); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) ); intrusive_ptr optimized = expression->optimize(); ASSERT_EQUALS( expectedOptimized(), expressionToBson( optimized ) ); } protected: virtual BSONObj spec() = 0; virtual BSONObj expectedOptimized() = 0; }; class NoOptimizeBase : public OptimizeBase { BSONObj expectedOptimized() { return constify( spec() ); } }; /** $and without operands. */ class NoOperands : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSONArray() ); } bool expectedResult() { return true; } }; /** $and passed 'true'. */ class True : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( true ) ); } bool expectedResult() { return true; } }; /** $and passed 'false'. */ class False : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( false ) ); } bool expectedResult() { return false; } }; /** $and passed 'true', 'true'. */ class TrueTrue : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( true << true ) ); } bool expectedResult() { return true; } }; /** $and passed 'true', 'false'. */ class TrueFalse : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( true << false ) ); } bool expectedResult() { return false; } }; /** $and passed 'false', 'true'. */ class FalseTrue : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( false << true ) ); } bool expectedResult() { return false; } }; /** $and passed 'false', 'false'. */ class FalseFalse : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( false << false ) ); } bool expectedResult() { return false; } }; /** $and passed 'true', 'true', 'true'. */ class TrueTrueTrue : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( true << true << true ) ); } bool expectedResult() { return true; } }; /** $and passed 'true', 'true', 'false'. */ class TrueTrueFalse : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( true << true << false ) ); } bool expectedResult() { return false; } }; /** $and passed '0', '1'. */ class ZeroOne : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 0 << 1 ) ); } bool expectedResult() { return false; } }; /** $and passed '1', '2'. */ class OneTwo : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 1 << 2 ) ); } bool expectedResult() { return true; } }; /** $and passed a field path. */ class FieldPath : public ExpectedResultBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( "$a" ) ); } bool expectedResult() { return true; } }; /** A constant expression is optimized to a constant. */ class OptimizeConstantExpression : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 1 ) ); } BSONObj expectedOptimized() { return BSON( "$const" << true ); } }; /** A non constant expression is not optimized. */ class NonConstant : public NoOptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( "$a" ) ); } }; /** An expression beginning with a single constant is optimized. */ class ConstantNonConstantTrue : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 1 << "$a" ) ); } BSONObj expectedOptimized() { return BSON( "$and" << BSON_ARRAY( "$a" ) ); } // note: using $and as serialization of ExpressionCoerceToBool rather than ExpressionAnd }; class ConstantNonConstantFalse : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 0 << "$a" ) ); } BSONObj expectedOptimized() { return BSON( "$const" << false ); } }; /** An expression with a field path and '1'. */ class NonConstantOne : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( "$a" << 1 ) ); } BSONObj expectedOptimized() { return BSON( "$and" << BSON_ARRAY( "$a" ) ); } }; /** An expression with a field path and '0'. */ class NonConstantZero : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( "$a" << 0 ) ); } BSONObj expectedOptimized() { return BSON( "$const" << false ); } }; /** An expression with two field paths and '1'. */ class NonConstantNonConstantOne : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( "$a" << "$b" << 1 ) ); } BSONObj expectedOptimized() { return BSON( "$and" << BSON_ARRAY( "$a" << "$b" ) ); } }; /** An expression with two field paths and '0'. */ class NonConstantNonConstantZero : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( "$a" << "$b" << 0 ) ); } BSONObj expectedOptimized() { return BSON( "$const" << false ); } }; /** An expression with '0', '1', and a field path. */ class ZeroOneNonConstant : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 0 << 1 << "$a" ) ); } BSONObj expectedOptimized() { return BSON( "$const" << false ); } }; /** An expression with '1', '1', and a field path. */ class OneOneNonConstant : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 1 << 1 << "$a" ) ); } BSONObj expectedOptimized() { return BSON( "$and" << BSON_ARRAY( "$a" ) ); } }; /** Nested $and expressions. */ class Nested : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 1 << BSON( "$and" << BSON_ARRAY( 1 ) ) << "$a" << "$b" ) ); } BSONObj expectedOptimized() { return BSON( "$and" << BSON_ARRAY( "$a" << "$b" ) ); } }; /** Nested $and expressions containing a nested value evaluating to false. */ class NestedZero : public OptimizeBase { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 1 << BSON( "$and" << BSON_ARRAY( BSON( "$and" << BSON_ARRAY( 0 ) ) ) ) << "$a" << "$b" ) ); } BSONObj expectedOptimized() { return BSON( "$const" << false ); } }; } // namespace And namespace CoerceToBool { /** Nested expression coerced to true. */ class EvaluateTrue { public: void run() { intrusive_ptr nested = ExpressionConstant::create( Value( 5 ) ); intrusive_ptr expression = ExpressionCoerceToBool::create( nested ); ASSERT( expression->evaluate( Document() ).getBool() ); } }; /** Nested expression coerced to false. */ class EvaluateFalse { public: void run() { intrusive_ptr nested = ExpressionConstant::create( Value( 0 ) ); intrusive_ptr expression = ExpressionCoerceToBool::create( nested ); ASSERT( !expression->evaluate( Document() ).getBool() ); } }; /** Dependencies forwarded from nested expression. */ class Dependencies { public: void run() { intrusive_ptr nested = ExpressionFieldPath::create( "a.b" ); intrusive_ptr expression = ExpressionCoerceToBool::create( nested ); DepsTracker dependencies; expression->addDependencies( &dependencies ); ASSERT_EQUALS( 1U, dependencies.fields.size() ); ASSERT_EQUALS( 1U, dependencies.fields.count( "a.b" ) ); ASSERT_EQUALS( false, dependencies.needWholeDocument ); ASSERT_EQUALS( false, dependencies.needTextScore ); } }; /** Output to BSONObj. */ class AddToBsonObj { public: void run() { intrusive_ptr expression = ExpressionCoerceToBool::create( ExpressionFieldPath::create("foo")); // serialized as $and because CoerceToBool isn't an ExpressionNary assertBinaryEqual(fromjson("{field:{$and:['$foo']}}"), toBsonObj(expression)); } private: static BSONObj toBsonObj(const intrusive_ptr& expression) { return BSON("field" << expression->serialize(false)); } }; /** Output to BSONArray. */ class AddToBsonArray { public: void run() { intrusive_ptr expression = ExpressionCoerceToBool::create( ExpressionFieldPath::create("foo")); // serialized as $and because CoerceToBool isn't an ExpressionNary assertBinaryEqual(BSON_ARRAY(fromjson("{$and:['$foo']}")), toBsonArray(expression)); } private: static BSONArray toBsonArray(const intrusive_ptr& expression) { BSONArrayBuilder bab; bab << expression->serialize(false); return bab.arr(); } }; // TODO Test optimize(), difficult because a CoerceToBool cannot be output as BSON. } // namespace CoerceToBool namespace Compare { class OptimizeBase { public: virtual ~OptimizeBase() { } void run() { BSONObj specObject = BSON( "" << spec() ); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); intrusive_ptr optimized = expression->optimize(); ASSERT_EQUALS( constify( expectedOptimized() ), expressionToBson( optimized ) ); } protected: virtual BSONObj spec() = 0; virtual BSONObj expectedOptimized() = 0; }; class FieldRangeOptimize : public OptimizeBase { BSONObj expectedOptimized() { return spec(); } }; class NoOptimize : public OptimizeBase { BSONObj expectedOptimized() { return spec(); } }; /** Check expected result for expressions depending on constants. */ class ExpectedResultBase : public OptimizeBase { public: void run() { OptimizeBase::run(); BSONObj specObject = BSON( "" << spec() ); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); // Check expression spec round trip. ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) ); // Check evaluation result. ASSERT_EQUALS( expectedResult(), toBson( expression->evaluate( Document() ) ) ); // Check that the result is the same after optimizing. intrusive_ptr optimized = expression->optimize(); ASSERT_EQUALS( expectedResult(), toBson( optimized->evaluate( Document() ) ) ); } protected: virtual BSONObj spec() = 0; virtual BSONObj expectedResult() = 0; private: virtual BSONObj expectedOptimized() { return BSON( "$const" << expectedResult().firstElement() ); } }; class ExpectedTrue : public ExpectedResultBase { BSONObj expectedResult() { return BSON( "" << true ); } }; class ExpectedFalse : public ExpectedResultBase { BSONObj expectedResult() { return BSON( "" << false ); } }; class ParseError { public: virtual ~ParseError() { } void run() { BSONObj specObject = BSON( "" << spec() ); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); ASSERT_THROWS( Expression::parseOperand(specElement, vps), UserException ); } protected: virtual BSONObj spec() = 0; }; /** $eq with first < second. */ class EqLt : public ExpectedFalse { BSONObj spec() { return BSON( "$eq" << BSON_ARRAY( 1 << 2 ) ); } }; /** $eq with first == second. */ class EqEq : public ExpectedTrue { BSONObj spec() { return BSON( "$eq" << BSON_ARRAY( 1 << 1 ) ); } }; /** $eq with first > second. */ class EqGt : public ExpectedFalse { BSONObj spec() { return BSON( "$eq" << BSON_ARRAY( 1 << 0 ) ); } }; /** $ne with first < second. */ class NeLt : public ExpectedTrue { BSONObj spec() { return BSON( "$ne" << BSON_ARRAY( 1 << 2 ) ); } }; /** $ne with first == second. */ class NeEq : public ExpectedFalse { BSONObj spec() { return BSON( "$ne" << BSON_ARRAY( 1 << 1 ) ); } }; /** $ne with first > second. */ class NeGt : public ExpectedTrue { BSONObj spec() { return BSON( "$ne" << BSON_ARRAY( 1 << 0 ) ); } }; /** $gt with first < second. */ class GtLt : public ExpectedFalse { BSONObj spec() { return BSON( "$gt" << BSON_ARRAY( 1 << 2 ) ); } }; /** $gt with first == second. */ class GtEq : public ExpectedFalse { BSONObj spec() { return BSON( "$gt" << BSON_ARRAY( 1 << 1 ) ); } }; /** $gt with first > second. */ class GtGt : public ExpectedTrue { BSONObj spec() { return BSON( "$gt" << BSON_ARRAY( 1 << 0 ) ); } }; /** $gte with first < second. */ class GteLt : public ExpectedFalse { BSONObj spec() { return BSON( "$gte" << BSON_ARRAY( 1 << 2 ) ); } }; /** $gte with first == second. */ class GteEq : public ExpectedTrue { BSONObj spec() { return BSON( "$gte" << BSON_ARRAY( 1 << 1 ) ); } }; /** $gte with first > second. */ class GteGt : public ExpectedTrue { BSONObj spec() { return BSON( "$gte" << BSON_ARRAY( 1 << 0 ) ); } }; /** $lt with first < second. */ class LtLt : public ExpectedTrue { BSONObj spec() { return BSON( "$lt" << BSON_ARRAY( 1 << 2 ) ); } }; /** $lt with first == second. */ class LtEq : public ExpectedFalse { BSONObj spec() { return BSON( "$lt" << BSON_ARRAY( 1 << 1 ) ); } }; /** $lt with first > second. */ class LtGt : public ExpectedFalse { BSONObj spec() { return BSON( "$lt" << BSON_ARRAY( 1 << 0 ) ); } }; /** $lte with first < second. */ class LteLt : public ExpectedTrue { BSONObj spec() { return BSON( "$lte" << BSON_ARRAY( 1 << 2 ) ); } }; /** $lte with first == second. */ class LteEq : public ExpectedTrue { BSONObj spec() { return BSON( "$lte" << BSON_ARRAY( 1 << 1 ) ); } }; /** $lte with first > second. */ class LteGt : public ExpectedFalse { BSONObj spec() { return BSON( "$lte" << BSON_ARRAY( 1 << 0 ) ); } }; /** $cmp with first < second. */ class CmpLt : public ExpectedResultBase { BSONObj spec() { return BSON( "$cmp" << BSON_ARRAY( 1 << 2 ) ); } BSONObj expectedResult() { return BSON( "" << -1 ); } }; /** $cmp with first == second. */ class CmpEq : public ExpectedResultBase { BSONObj spec() { return BSON( "$cmp" << BSON_ARRAY( 1 << 1 ) ); } BSONObj expectedResult() { return BSON( "" << 0 ); } }; /** $cmp with first > second. */ class CmpGt : public ExpectedResultBase { BSONObj spec() { return BSON( "$cmp" << BSON_ARRAY( 1 << 0 ) ); } BSONObj expectedResult() { return BSON( "" << 1 ); } }; /** $cmp results are bracketed to an absolute value of 1. */ class CmpBracketed : public ExpectedResultBase { BSONObj spec() { return BSON( "$cmp" << BSON_ARRAY( "z" << "a" ) ); } BSONObj expectedResult() { return BSON( "" << 1 ); } }; /** Zero operands provided. */ class ZeroOperands : public ParseError { BSONObj spec() { return BSON( "$ne" << BSONArray() ); } }; /** One operand provided. */ class OneOperand : public ParseError { BSONObj spec() { return BSON( "$eq" << BSON_ARRAY( 1 ) ); } }; /** Three operands provided. */ class ThreeOperands : public ParseError { BSONObj spec() { return BSON( "$gt" << BSON_ARRAY( 2 << 3 << 4 ) ); } }; /** Incompatible types can be compared. */ class IncompatibleTypes { public: void run() { BSONObj specObject = BSON( "" << BSON( "$ne" << BSON_ARRAY( "a" << 1 ) ) ); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); ASSERT_EQUALS(expression->evaluate(Document()), Value(true)); } }; /** * An expression depending on constants is optimized to a constant via * ExpressionNary::optimize(). */ class OptimizeConstants : public OptimizeBase { BSONObj spec() { return BSON( "$eq" << BSON_ARRAY( 1 << 1 ) ); } BSONObj expectedOptimized() { return BSON( "$const" << true ); } }; /** $cmp is not optimized. */ class NoOptimizeCmp : public NoOptimize { BSONObj spec() { return BSON( "$cmp" << BSON_ARRAY( 1 << "$a" ) ); } }; /** $ne is not optimized. */ class NoOptimizeNe : public NoOptimize { BSONObj spec() { return BSON( "$ne" << BSON_ARRAY( 1 << "$a" ) ); } }; /** No optimization is performend without a constant. */ class NoOptimizeNoConstant : public NoOptimize { BSONObj spec() { return BSON( "$ne" << BSON_ARRAY( "$a" << "$b" ) ); } }; /** No optimization is performend without an immediate field path. */ class NoOptimizeWithoutFieldPath : public NoOptimize { BSONObj spec() { return BSON( "$eq" << BSON_ARRAY( BSON( "$and" << BSON_ARRAY( "$a" ) ) << 1 ) ); } }; /** No optimization is performend without an immediate field path. */ class NoOptimizeWithoutFieldPathReverse : public NoOptimize { BSONObj spec() { return BSON( "$eq" << BSON_ARRAY( 1 << BSON( "$and" << BSON_ARRAY( "$a" ) ) ) ); } }; /** An equality expression is optimized. */ class OptimizeEq : public FieldRangeOptimize { BSONObj spec() { return BSON( "$eq" << BSON_ARRAY( "$a" << 1 ) ); } }; /** A reverse sense equality expression is optimized. */ class OptimizeEqReverse : public FieldRangeOptimize { BSONObj spec() { return BSON( "$eq" << BSON_ARRAY( 1 << "$a" ) ); } }; /** A $lt expression is optimized. */ class OptimizeLt : public FieldRangeOptimize { BSONObj spec() { return BSON( "$lt" << BSON_ARRAY( "$a" << 1 ) ); } }; /** A reverse sense $lt expression is optimized. */ class OptimizeLtReverse : public FieldRangeOptimize { BSONObj spec() { return BSON( "$lt" << BSON_ARRAY( 1 << "$a" ) ); } }; /** A $lte expression is optimized. */ class OptimizeLte : public FieldRangeOptimize { BSONObj spec() { return BSON( "$lte" << BSON_ARRAY( "$b" << 2 ) ); } }; /** A reverse sense $lte expression is optimized. */ class OptimizeLteReverse : public FieldRangeOptimize { BSONObj spec() { return BSON( "$lte" << BSON_ARRAY( 2 << "$b" ) ); } }; /** A $gt expression is optimized. */ class OptimizeGt : public FieldRangeOptimize { BSONObj spec() { return BSON( "$gt" << BSON_ARRAY( "$b" << 2 ) ); } }; /** A reverse sense $gt expression is optimized. */ class OptimizeGtReverse : public FieldRangeOptimize { BSONObj spec() { return BSON( "$gt" << BSON_ARRAY( 2 << "$b" ) ); } }; /** A $gte expression is optimized. */ class OptimizeGte : public FieldRangeOptimize { BSONObj spec() { return BSON( "$gte" << BSON_ARRAY( "$b" << 2 ) ); } }; /** A reverse sense $gte expression is optimized. */ class OptimizeGteReverse : public FieldRangeOptimize { BSONObj spec() { return BSON( "$gte" << BSON_ARRAY( 2 << "$b" ) ); } }; } // namespace Compare namespace Constant { /** Create an ExpressionConstant from a Value. */ class Create { public: void run() { intrusive_ptr expression = ExpressionConstant::create( Value( 5 ) ); assertBinaryEqual( BSON( "" << 5 ), toBson( expression->evaluate( Document() ) ) ); } }; /** Create an ExpressionConstant from a BsonElement. */ class CreateFromBsonElement { public: void run() { BSONObj spec = BSON( "IGNORED_FIELD_NAME" << "foo" ); BSONElement specElement = spec.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = ExpressionConstant::parse( specElement, vps ); assertBinaryEqual( BSON( "" << "foo" ), toBson( expression->evaluate( Document() ) ) ); } }; /** No optimization is performed. */ class Optimize { public: void run() { intrusive_ptr expression = ExpressionConstant::create( Value( 5 ) ); // An attempt to optimize returns the Expression itself. ASSERT_EQUALS( expression, expression->optimize() ); } }; /** No dependencies. */ class Dependencies { public: void run() { intrusive_ptr expression = ExpressionConstant::create( Value( 5 ) ); DepsTracker dependencies; expression->addDependencies( &dependencies ); ASSERT_EQUALS( 0U, dependencies.fields.size() ); ASSERT_EQUALS( false, dependencies.needWholeDocument ); ASSERT_EQUALS( false, dependencies.needTextScore ); } }; /** Output to BSONObj. */ class AddToBsonObj { public: void run() { intrusive_ptr expression = ExpressionConstant::create( Value( 5 ) ); // The constant is replaced with a $ expression. assertBinaryEqual( BSON( "field" << BSON( "$const" << 5 ) ), toBsonObj( expression ) ); } private: static BSONObj toBsonObj( const intrusive_ptr& expression ) { return BSON("field" << expression->serialize(false)); } }; /** Output to BSONArray. */ class AddToBsonArray { public: void run() { intrusive_ptr expression = ExpressionConstant::create( Value( 5 ) ); // The constant is copied out as is. assertBinaryEqual( constify( BSON_ARRAY( 5 ) ), toBsonArray( expression ) ); } private: static BSONObj toBsonArray( const intrusive_ptr& expression ) { BSONArrayBuilder bab; bab << expression->serialize(false); return bab.obj(); } }; } // namespace Constant namespace FieldPath { /** The provided field path does not pass validation. */ class Invalid { public: void run() { ASSERT_THROWS( ExpressionFieldPath::create( "" ), UserException ); } }; /** No optimization is performed. */ class Optimize { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a" ); // An attempt to optimize returns the Expression itself. ASSERT_EQUALS( expression, expression->optimize() ); } }; /** The field path itself is a dependency. */ class Dependencies { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); DepsTracker dependencies; expression->addDependencies( &dependencies ); ASSERT_EQUALS( 1U, dependencies.fields.size() ); ASSERT_EQUALS( 1U, dependencies.fields.count( "a.b" ) ); ASSERT_EQUALS( false, dependencies.needWholeDocument ); ASSERT_EQUALS( false, dependencies.needTextScore ); } }; /** Field path target field is missing. */ class Missing { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a" ); assertBinaryEqual( fromjson( "{}" ), toBson( expression->evaluate( Document() ) ) ); } }; /** Simple case where the target field is present. */ class Present { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a" ); assertBinaryEqual( fromjson( "{'':123}" ), toBson( expression->evaluate ( fromBson( BSON( "a" << 123 ) ) ) ) ); } }; /** Target field parent is null. */ class NestedBelowNull { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( fromjson( "{}" ), toBson( expression->evaluate ( fromBson( fromjson( "{a:null}" ) ) ) ) ); } }; /** Target field parent is undefined. */ class NestedBelowUndefined { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( fromjson( "{}" ), toBson( expression->evaluate ( fromBson( fromjson( "{a:undefined}" ) ) ) ) ); } }; /** Target field parent is missing. */ class NestedBelowMissing { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( fromjson( "{}" ), toBson( expression->evaluate ( fromBson( fromjson( "{z:1}" ) ) ) ) ); } }; /** Target field parent is an integer. */ class NestedBelowInt { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( fromjson( "{}" ), toBson( expression->evaluate ( fromBson( BSON( "a" << 2 ) ) ) ) ); } }; /** A value in a nested object. */ class NestedValue { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( BSON( "" << 55 ), toBson( expression->evaluate ( fromBson( BSON( "a" << BSON( "b" << 55 ) ) ) ) ) ); } }; /** Target field within an empty object. */ class NestedBelowEmptyObject { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( fromjson( "{}" ), toBson( expression->evaluate ( fromBson( BSON( "a" << BSONObj() ) ) ) ) ); } }; /** Target field within an empty array. */ class NestedBelowEmptyArray { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( BSON( "" << BSONArray() ), toBson( expression->evaluate ( fromBson( BSON( "a" << BSONArray() ) ) ) ) ); } }; /** Target field within an array containing null. */ class NestedBelowArrayWithNull { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( fromjson( "{'':[]}" ), toBson( expression->evaluate ( fromBson( fromjson( "{a:[null]}" ) ) ) ) ); } }; /** Target field within an array containing undefined. */ class NestedBelowArrayWithUndefined { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( fromjson( "{'':[]}" ), toBson( expression->evaluate ( fromBson( fromjson( "{a:[undefined]}" ) ) ) ) ); } }; /** Target field within an array containing an integer. */ class NestedBelowArrayWithInt { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( fromjson( "{'':[]}" ), toBson( expression->evaluate ( fromBson( fromjson( "{a:[1]}" ) ) ) ) ); } }; /** Target field within an array. */ class NestedWithinArray { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( fromjson( "{'':[9]}" ), toBson( expression->evaluate ( fromBson( fromjson( "{a:[{b:9}]}" ) ) ) ) ); } }; /** Multiple value types within an array. */ class MultipleArrayValues { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b" ); assertBinaryEqual( fromjson( "{'':[9,20]}" ), toBson( expression->evaluate ( fromBson( fromjson ( "{a:[{b:9},null,undefined,{g:4},{b:20},{}]}" ) ) ) ) ); } }; /** Expanding values within nested arrays. */ class ExpandNestedArrays { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b.c" ); assertBinaryEqual( fromjson( "{'':[[1,2],3,[4],[[5]],[6,7]]}" ), toBson ( expression->evaluate ( fromBson ( fromjson( "{a:[{b:[{c:1},{c:2}]}," "{b:{c:3}}," "{b:[{c:4}]}," "{b:[{c:[5]}]}," "{b:{c:[6,7]}}]}" ) ) ) ) ); } }; /** Add to a BSONObj. */ class AddToBsonObj { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b.c" ); assertBinaryEqual(BSON("foo" << "$a.b.c"), BSON("foo" << expression->serialize(false))); } }; /** Add to a BSONArray. */ class AddToBsonArray { public: void run() { intrusive_ptr expression = ExpressionFieldPath::create( "a.b.c" ); BSONArrayBuilder bab; bab << expression->serialize(false); assertBinaryEqual( BSON_ARRAY( "$a.b.c" ), bab.arr() ); } }; } // namespace FieldPath namespace Nary { /** A dummy child of ExpressionNary used for testing. */ class Testable : public ExpressionNary { public: virtual Value evaluateInternal(Variables* vars) const { // Just put all the values in a list. This is not associative/commutative so // the results will change if a factory is provided and operations are reordered. vector values; for( ExpressionVector::const_iterator i = vpOperand.begin(); i != vpOperand.end(); ++i ) { values.push_back( (*i)->evaluateInternal(vars) ); } return Value( values ); } virtual const char* getOpName() const { return "$testable"; } virtual bool isAssociativeAndCommutative() const { return _isAssociativeAndCommutative; } static intrusive_ptr create( bool associativeAndCommutative = false ) { return new Testable(associativeAndCommutative); } static intrusive_ptr factory() { return new Testable(true); } static intrusive_ptr createFromOperands( const BSONArray& operands, bool haveFactory = false ) { VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr testable = create( haveFactory ); BSONObjIterator i( operands ); while( i.more() ) { BSONElement element = i.next(); testable->addOperand( Expression::parseOperand(element, vps) ); } return testable; } void assertContents( const BSONArray& expectedContents ) { ASSERT_EQUALS( constify( BSON( "$testable" << expectedContents ) ), expressionToBson( this ) ); } private: Testable(bool isAssociativeAndCommutative) : _isAssociativeAndCommutative(isAssociativeAndCommutative) {} bool _isAssociativeAndCommutative; }; /** Adding operands to the expression. */ class AddOperand { public: void run() { intrusive_ptr testable = Testable::create(); testable->addOperand( ExpressionConstant::create( Value( 9 ) ) ); testable->assertContents( BSON_ARRAY( 9 ) ); testable->addOperand( ExpressionFieldPath::create( "ab.c" ) ); testable->assertContents( BSON_ARRAY( 9 << "$ab.c" ) ); } }; /** Dependencies of the expression. */ class Dependencies { public: void run() { intrusive_ptr testable = Testable::create(); // No arguments. assertDependencies( BSONArray(), testable ); // Add a constant argument. testable->addOperand( ExpressionConstant::create( Value( 1 ) ) ); assertDependencies( BSONArray(), testable ); // Add a field path argument. testable->addOperand( ExpressionFieldPath::create( "ab.c" ) ); assertDependencies( BSON_ARRAY( "ab.c" ), testable ); // Add an object expression. BSONObj spec = BSON( "" << BSON( "a" << "$x" << "q" << "$r" ) ); BSONElement specElement = spec.firstElement(); Expression::ObjectCtx ctx( Expression::ObjectCtx::DOCUMENT_OK ); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); testable->addOperand( Expression::parseObject(specElement.Obj(), &ctx, vps) ); assertDependencies( BSON_ARRAY( "ab.c" << "r" << "x" ), testable ); } private: void assertDependencies( const BSONArray& expectedDependencies, const intrusive_ptr& expression ) { DepsTracker dependencies; expression->addDependencies( &dependencies ); BSONArrayBuilder dependenciesBson; for( set::const_iterator i = dependencies.fields.begin(); i != dependencies.fields.end(); ++i ) { dependenciesBson << *i; } ASSERT_EQUALS( expectedDependencies, dependenciesBson.arr() ); ASSERT_EQUALS( false, dependencies.needWholeDocument ); ASSERT_EQUALS( false, dependencies.needTextScore ); } }; /** Serialize to an object. */ class AddToBsonObj { public: void run() { intrusive_ptr testable = Testable::create(); testable->addOperand( ExpressionConstant::create( Value( 5 ) ) ); ASSERT_EQUALS(BSON("foo" << BSON("$testable" << BSON_ARRAY(BSON("$const" << 5)))), BSON("foo" << testable->serialize(false))); } }; /** Serialize to an array. */ class AddToBsonArray { public: void run() { intrusive_ptr testable = Testable::create(); testable->addOperand( ExpressionConstant::create( Value( 5 ) ) ); ASSERT_EQUALS(constify(BSON_ARRAY(BSON("$testable" << BSON_ARRAY(5)))), BSON_ARRAY(testable->serialize(false))); } }; /** One operand is optimized to a constant, while another is left as is. */ class OptimizeOneOperand { public: void run() { BSONArray spec = BSON_ARRAY( BSON( "$and" << BSONArray() ) << "$abc" ); intrusive_ptr testable = Testable::createFromOperands( spec ); testable->assertContents( spec ); ASSERT( testable == testable->optimize() ); testable->assertContents( BSON_ARRAY( true << "$abc" ) ); } }; /** All operands are constants, and the operator is evaluated with them. */ class EvaluateAllConstantOperands { public: void run() { BSONArray spec = BSON_ARRAY( 1 << 2 ); intrusive_ptr testable = Testable::createFromOperands( spec ); testable->assertContents( spec ); intrusive_ptr optimized = testable->optimize(); ASSERT( testable != optimized ); ASSERT_EQUALS( BSON( "$const" << BSON_ARRAY( 1 << 2 ) ), expressionToBson( optimized ) ); } }; class NoFactoryOptimizeBase { public: virtual ~NoFactoryOptimizeBase() { } void run() { intrusive_ptr testable = createTestable(); // Without factory optimization, optimization will not produce a new expression. ASSERT( testable == testable->optimize() ); } protected: virtual intrusive_ptr createTestable() = 0; }; /** A string constant prevents factory optimization. */ class StringConstant : public NoFactoryOptimizeBase { intrusive_ptr createTestable() { return Testable::createFromOperands( BSON_ARRAY( "abc" << "def" << "$path" ), true ); } }; /** A single (instead of multiple) constant prevents optimization. SERVER-6192 */ class SingleConstant : public NoFactoryOptimizeBase { intrusive_ptr createTestable() { return Testable::createFromOperands( BSON_ARRAY( 55 << "$path" ), true ); } }; /** Factory optimization is not used without a factory. */ class NoFactory : public NoFactoryOptimizeBase { intrusive_ptr createTestable() { return Testable::createFromOperands( BSON_ARRAY( 55 << 66 << "$path" ), false ); } }; /** Factory optimization separates constant from non constant expressions. */ class FactoryOptimize { public: void run() { intrusive_ptr testable = Testable::createFromOperands( BSON_ARRAY( 55 << 66 << "$path" ), true ); intrusive_ptr optimized = testable->optimize(); // The constant expressions are evaluated separately and placed at the end. ASSERT_EQUALS( constify( BSON( "$testable" << BSON_ARRAY( "$path" << BSON_ARRAY( 55 << 66 ) ) ) ), expressionToBson( optimized ) ); } }; /** Factory optimization flattens nested operators of the same type. */ class FlattenOptimize { public: void run() { intrusive_ptr testable = Testable::createFromOperands ( BSON_ARRAY( 55 << "$path" << // $and has a factory, but it's a different factory from // $testable. BSON( "$add" << BSON_ARRAY( 5 << 6 << "$q" ) ) << 66 ), true ); // Add a nested $testable operand. testable->addOperand ( Testable::createFromOperands ( BSON_ARRAY( 99 << 100 << "$another_path" ), true ) ); intrusive_ptr optimized = testable->optimize(); ASSERT_EQUALS ( constify( BSON( "$testable" << BSON_ARRAY( // non constant parts "$path" << BSON( "$add" << BSON_ARRAY( "$q" << 11 ) ) << "$another_path" << // constant part last BSON_ARRAY( 55 << 66 << BSON_ARRAY( 99 << 100 ) ) ) ) ), expressionToBson( optimized ) ); } }; /** Three layers of factory optimization are flattened. */ class FlattenThreeLayers { public: void run() { intrusive_ptr top = Testable::createFromOperands( BSON_ARRAY( 1 << 2 << "$a" ), true ); intrusive_ptr nested = Testable::createFromOperands( BSON_ARRAY( 3 << 4 << "$b" ), true ); nested->addOperand ( Testable::createFromOperands( BSON_ARRAY( 5 << 6 << "$c" ), true ) ); top->addOperand( nested ); intrusive_ptr optimized = top->optimize(); ASSERT_EQUALS ( constify( BSON( "$testable" << BSON_ARRAY( "$a" << "$b" << "$c" << BSON_ARRAY( 1 << 2 << BSON_ARRAY( 3 << 4 << BSON_ARRAY( 5 << 6 ) ) ) ) ) ), expressionToBson( optimized ) ); } }; } // namespace Nary namespace Object { class Base { protected: void assertDependencies( const BSONArray& expectedDependencies, const intrusive_ptr& expression, bool includePath = true ) const { vector path; DepsTracker dependencies; expression->addDependencies( &dependencies, includePath ? &path : 0 ); BSONArrayBuilder bab; for( set::const_iterator i = dependencies.fields.begin(); i != dependencies.fields.end(); ++i ) { bab << *i; } ASSERT_EQUALS( expectedDependencies, bab.arr() ); ASSERT_EQUALS( false, dependencies.needWholeDocument ); ASSERT_EQUALS( false, dependencies.needTextScore ); } }; class ExpectedResultBase : public Base { public: virtual ~ExpectedResultBase() { } void run() { _expression = ExpressionObject::createRoot(); prepareExpression(); Document document = fromBson( source() ); MutableDocument result; Variables vars(0, document); expression()->addToDocument( result, document, &vars ); assertBinaryEqual( expected(), toBson( result.freeze() ) ); assertDependencies( expectedDependencies(), _expression ); ASSERT_EQUALS( expectedBsonRepresentation(), expressionToBson( _expression ) ); ASSERT_EQUALS( expectedIsSimple(), _expression->isSimple() ); } protected: intrusive_ptr expression() { return _expression; } virtual BSONObj source() { return BSON( "_id" << 0 << "a" << 1 << "b" << 2 ); } virtual void prepareExpression() = 0; virtual BSONObj expected() = 0; virtual BSONArray expectedDependencies() = 0; virtual BSONObj expectedBsonRepresentation() = 0; virtual bool expectedIsSimple() { return true; } private: intrusive_ptr _expression; }; /** Empty object spec. */ class Empty : public ExpectedResultBase { public: void prepareExpression() {} BSONObj expected() { return BSON( "_id" << 0 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); } BSONObj expectedBsonRepresentation() { return BSONObj(); } }; /** Include 'a' field only. */ class Include : public ExpectedResultBase { public: void prepareExpression() { expression()->includePath( "a" ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << 1 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << true ); } }; /** Cannot include missing 'a' field. */ class MissingInclude : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 << "b" << 2 ); } void prepareExpression() { expression()->includePath( "a" ); } BSONObj expected() { return BSON( "_id" << 0 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << true ); } }; /** Include '_id' field only. */ class IncludeId : public ExpectedResultBase { public: void prepareExpression() { expression()->includePath( "_id" ); } BSONObj expected() { return BSON( "_id" << 0 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); } BSONObj expectedBsonRepresentation() { return BSON( "_id" << true ); } }; /** Exclude '_id' field. */ class ExcludeId : public ExpectedResultBase { public: void prepareExpression() { expression()->includePath( "b" ); expression()->excludeId( true ); } BSONObj expected() { return BSON( "b" << 2 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "b" ); } BSONObj expectedBsonRepresentation() { return BSON( "_id" << false << "b" << true ); } }; /** Result order based on source document field order, not inclusion spec field order. */ class SourceOrder : public ExpectedResultBase { public: void prepareExpression() { expression()->includePath( "b" ); expression()->includePath( "a" ); } BSONObj expected() { return source(); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a" << "b" ); } BSONObj expectedBsonRepresentation() { return BSON( "b" << true << "a" << true ); } }; /** Include a nested field. */ class IncludeNested : public ExpectedResultBase { public: void prepareExpression() { expression()->includePath( "a.b" ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSON( "b" << 5 ) ); } BSONObj source() { return BSON( "_id" << 0 << "a" << BSON( "b" << 5 << "c" << 6 ) << "z" << 2 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a.b" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "b" << true ) ); } }; /** Include two nested fields. */ class IncludeTwoNested : public ExpectedResultBase { public: void prepareExpression() { expression()->includePath( "a.b" ); expression()->includePath( "a.c" ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSON( "b" << 5 << "c" << 6 ) ); } BSONObj source() { return BSON( "_id" << 0 << "a" << BSON( "b" << 5 << "c" << 6 ) << "z" << 2 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a.b" << "a.c" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "b" << true << "c" << true ) ); } }; /** Include two fields nested within different parents. */ class IncludeTwoParentNested : public ExpectedResultBase { public: void prepareExpression() { expression()->includePath( "a.b" ); expression()->includePath( "c.d" ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSON( "b" << 5 ) << "c" << BSON( "d" << 6 ) ); } BSONObj source() { return BSON( "_id" << 0 << "a" << BSON( "b" << 5 ) << "c" << BSON( "d" << 6 ) << "z" << 2 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a.b" << "c.d" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "b" << true ) << "c" << BSON( "d" << true ) ); } }; /** Attempt to include a missing nested field. */ class IncludeMissingNested : public ExpectedResultBase { public: void prepareExpression() { expression()->includePath( "a.b" ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSONObj() ); } BSONObj source() { return BSON( "_id" << 0 << "a" << BSON( "c" << 6 ) << "z" << 2 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a.b" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "b" << true ) ); } }; /** Attempt to include a nested field within a non object. */ class IncludeNestedWithinNonObject : public ExpectedResultBase { public: void prepareExpression() { expression()->includePath( "a.b" ); } BSONObj expected() { return BSON( "_id" << 0 ); } BSONObj source() { return BSON( "_id" << 0 << "a" << 2 << "z" << 2 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a.b" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "b" << true ) ); } }; /** Include a nested field within an array. */ class IncludeArrayNested : public ExpectedResultBase { public: void prepareExpression() { expression()->includePath( "a.b" ); } BSONObj expected() { return fromjson( "{_id:0,a:[{b:5},{b:2},{}]}" ); } BSONObj source() { return fromjson( "{_id:0,a:[{b:5,c:6},{b:2,c:9},{c:7},[],2],z:1}" ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a.b" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "b" << true ) ); } }; /** Don't include not root '_id' field implicitly. */ class ExcludeNonRootId : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 << "a" << BSON( "_id" << 1 << "b" << 1 ) ); } void prepareExpression() { expression()->includePath( "a.b" ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSON( "b" << 1 ) ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a.b" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "b" << true ) ); } }; /** Project a computed expression. */ class Computed : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 ); } void prepareExpression() { expression()->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value( 5 ) ) ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << 5 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "$const" << 5 ) ); } bool expectedIsSimple() { return false; } }; /** Project a computed expression replacing an existing field. */ class ComputedReplacement : public Computed { virtual BSONObj source() { return BSON( "_id" << 0 << "a" << 99 ); } }; /** An undefined value is passed through */ class ComputedUndefined : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 ); } void prepareExpression() { expression()->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value(BSONUndefined) ) ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSONUndefined); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); } BSONObj expectedBsonRepresentation() { return fromjson( "{a:{$const:undefined}}" ); } bool expectedIsSimple() { return false; } }; /** Project a computed expression replacing an existing field with Undefined. */ class ComputedUndefinedReplacement : public ComputedUndefined { virtual BSONObj source() { return BSON( "_id" << 0 << "a" << 99 ); } }; /** A null value is projected. */ class ComputedNull : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 ); } void prepareExpression() { expression()->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value(BSONNULL) ) ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSONNULL ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "$const" << BSONNULL ) ); } bool expectedIsSimple() { return false; } }; /** A nested value is projected. */ class ComputedNested : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 ); } void prepareExpression() { expression()->addField( mongo::FieldPath( "a.b" ), ExpressionConstant::create( Value( 5 ) ) ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSON( "b" << 5 ) ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "b" << BSON( "$const" << 5 ) ) ); } bool expectedIsSimple() { return false; } }; /** A field path is projected. */ class ComputedFieldPath : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 << "x" << 4 ); } void prepareExpression() { expression()->addField( mongo::FieldPath( "a" ), ExpressionFieldPath::create( "x" ) ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << 4 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "x" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << "$x" ); } bool expectedIsSimple() { return false; } }; /** A nested field path is projected. */ class ComputedNestedFieldPath : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 << "x" << BSON( "y" << 4 ) ); } void prepareExpression() { expression()->addField( mongo::FieldPath( "a.b" ), ExpressionFieldPath::create( "x.y" ) ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSON( "b" << 4 ) ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "x.y" ); } BSONObj expectedBsonRepresentation() { return BSON( "a" << BSON( "b" << "$x.y" ) ); } bool expectedIsSimple() { return false; } }; /** An empty subobject expression for a missing field is not projected. */ class EmptyNewSubobject : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 ); } void prepareExpression() { // Create a sub expression returning an empty object. intrusive_ptr subExpression = ExpressionObject::create(); subExpression->addField( mongo::FieldPath( "b" ), ExpressionFieldPath::create( "a.b" ) ); expression()->addField( mongo::FieldPath( "a" ), subExpression ); } BSONObj expected() { return BSON( "_id" << 0 ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a.b"); } BSONObj expectedBsonRepresentation() { return fromjson( "{a:{b:'$a.b'}}" ); } bool expectedIsSimple() { return false; } }; /** A non empty subobject expression for a missing field is projected. */ class NonEmptyNewSubobject : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 ); } void prepareExpression() { // Create a sub expression returning an empty object. intrusive_ptr subExpression = ExpressionObject::create(); subExpression->addField( mongo::FieldPath( "b" ), ExpressionConstant::create( Value( 6 ) ) ); expression()->addField( mongo::FieldPath( "a" ), subExpression ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSON( "b" << 6 ) ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); } BSONObj expectedBsonRepresentation() { return fromjson( "{a:{b:{$const:6}}}" ); } bool expectedIsSimple() { return false; } }; /** Two computed fields within a common parent. */ class AdjacentDottedComputedFields : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 ); } void prepareExpression() { expression()->addField( mongo::FieldPath( "a.b" ), ExpressionConstant::create( Value( 6 ) ) ); expression()->addField( mongo::FieldPath( "a.c" ), ExpressionConstant::create( Value( 7 ) ) ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSON( "b" << 6 << "c" << 7 ) ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); } BSONObj expectedBsonRepresentation() { return fromjson( "{a:{b:{$const:6},c:{$const:7}}}" ); } bool expectedIsSimple() { return false; } }; /** Two computed fields within a common parent, in one case dotted. */ class AdjacentDottedAndNestedComputedFields : public AdjacentDottedComputedFields { void prepareExpression() { expression()->addField( mongo::FieldPath( "a.b" ), ExpressionConstant::create( Value( 6 ) ) ); intrusive_ptr subExpression = ExpressionObject::create(); subExpression->addField( mongo::FieldPath( "c" ), ExpressionConstant::create( Value( 7 ) ) ); expression()->addField( mongo::FieldPath( "a" ), subExpression ); } }; /** Two computed fields within a common parent, in another case dotted. */ class AdjacentNestedAndDottedComputedFields : public AdjacentDottedComputedFields { void prepareExpression() { intrusive_ptr subExpression = ExpressionObject::create(); subExpression->addField( mongo::FieldPath( "b" ), ExpressionConstant::create( Value( 6 ) ) ); expression()->addField( mongo::FieldPath( "a" ), subExpression ); expression()->addField( mongo::FieldPath( "a.c" ), ExpressionConstant::create( Value( 7 ) ) ); } }; /** Two computed fields within a common parent, nested rather than dotted. */ class AdjacentNestedComputedFields : public AdjacentDottedComputedFields { void prepareExpression() { intrusive_ptr firstSubExpression = ExpressionObject::create(); firstSubExpression->addField( mongo::FieldPath( "b" ), ExpressionConstant::create( Value( 6 ) ) ); expression()->addField( mongo::FieldPath( "a" ), firstSubExpression ); intrusive_ptr secondSubExpression = ExpressionObject::create(); secondSubExpression->addField( mongo::FieldPath( "c" ), ExpressionConstant::create ( Value( 7 ) ) ); expression()->addField( mongo::FieldPath( "a" ), secondSubExpression ); } }; /** Field ordering is preserved when nested fields are merged. */ class AdjacentNestedOrdering : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 ); } void prepareExpression() { expression()->addField( mongo::FieldPath( "a.b" ), ExpressionConstant::create( Value( 6 ) ) ); intrusive_ptr subExpression = ExpressionObject::create(); // Add field 'd' then 'c'. Expect the same field ordering in the result doc. subExpression->addField( mongo::FieldPath( "d" ), ExpressionConstant::create( Value( 7 ) ) ); subExpression->addField( mongo::FieldPath( "c" ), ExpressionConstant::create( Value( 8 ) ) ); expression()->addField( mongo::FieldPath( "a" ), subExpression ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSON( "b" << 6 << "d" << 7 << "c" << 8 ) ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); } BSONObj expectedBsonRepresentation() { return fromjson( "{a:{b:{$const:6},d:{$const:7},c:{$const:8}}}" ); } bool expectedIsSimple() { return false; } }; /** Adjacent fields two levels deep. */ class MultipleNestedFields : public ExpectedResultBase { public: virtual BSONObj source() { return BSON( "_id" << 0 ); } void prepareExpression() { expression()->addField( mongo::FieldPath( "a.b.c" ), ExpressionConstant::create( Value( 6 ) ) ); intrusive_ptr bSubExpression = ExpressionObject::create(); bSubExpression->addField( mongo::FieldPath( "d" ), ExpressionConstant::create( Value( 7 ) ) ); intrusive_ptr aSubExpression = ExpressionObject::create(); aSubExpression->addField( mongo::FieldPath( "b" ), bSubExpression ); expression()->addField( mongo::FieldPath( "a" ), aSubExpression ); } BSONObj expected() { return BSON( "_id" << 0 << "a" << BSON( "b" << BSON( "c" << 6 << "d" << 7 ) ) ); } BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); } BSONObj expectedBsonRepresentation() { return fromjson( "{a:{b:{c:{$const:6},d:{$const:7}}}}" ); } bool expectedIsSimple() { return false; } }; /** Two expressions cannot generate the same field. */ class ConflictingExpressionFields : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value( 5 ) ) ); ASSERT_THROWS( expression->addField( mongo::FieldPath( "a" ), // Duplicate field. ExpressionConstant::create ( Value( 6 ) ) ), UserException ); } }; /** An expression field conflicts with an inclusion field. */ class ConflictingInclusionExpressionFields : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->includePath( "a" ); ASSERT_THROWS( expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create ( Value( 6 ) ) ), UserException ); } }; /** An inclusion field conflicts with an expression field. */ class ConflictingExpressionInclusionFields : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value( 5 ) ) ); ASSERT_THROWS( expression->includePath( "a" ), UserException ); } }; /** An object expression conflicts with a constant expression. */ class ConflictingObjectConstantExpressionFields : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); intrusive_ptr subExpression = ExpressionObject::create(); subExpression->includePath( "b" ); expression->addField( mongo::FieldPath( "a" ), subExpression ); ASSERT_THROWS( expression->addField( mongo::FieldPath( "a.b" ), ExpressionConstant::create ( Value( 6 ) ) ), UserException ); } }; /** A constant expression conflicts with an object expression. */ class ConflictingConstantObjectExpressionFields : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a.b" ), ExpressionConstant::create( Value( 6 ) ) ); intrusive_ptr subExpression = ExpressionObject::create(); subExpression->includePath( "b" ); ASSERT_THROWS( expression->addField( mongo::FieldPath( "a" ), subExpression ), UserException ); } }; /** Two nested expressions cannot generate the same field. */ class ConflictingNestedFields : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a.b" ), ExpressionConstant::create( Value( 5 ) ) ); ASSERT_THROWS( expression->addField( mongo::FieldPath( "a.b" ), // Duplicate field. ExpressionConstant::create ( Value( 6 ) ) ), UserException ); } }; /** An expression cannot be created for a subfield of another expression. */ class ConflictingFieldAndSubfield : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value( 5 ) ) ); ASSERT_THROWS( expression->addField( mongo::FieldPath( "a.b" ), ExpressionConstant::create ( Value( 5 ) ) ), UserException ); } }; /** An expression cannot be created for a nested field of another expression. */ class ConflictingFieldAndNestedField : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value( 5 ) ) ); intrusive_ptr subExpression = ExpressionObject::create(); subExpression->addField( mongo::FieldPath( "b" ), ExpressionConstant::create( Value( 5 ) ) ); ASSERT_THROWS( expression->addField( mongo::FieldPath( "a" ), subExpression ), UserException ); } }; /** An expression cannot be created for a parent field of another expression. */ class ConflictingSubfieldAndField : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a.b" ), ExpressionConstant::create( Value( 5 ) ) ); ASSERT_THROWS( expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create ( Value( 5 ) ) ), UserException ); } }; /** An expression cannot be created for a parent of a nested field. */ class ConflictingNestedFieldAndField : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); intrusive_ptr subExpression = ExpressionObject::create(); subExpression->addField( mongo::FieldPath( "b" ), ExpressionConstant::create( Value( 5 ) ) ); expression->addField( mongo::FieldPath( "a" ), subExpression ); ASSERT_THROWS( expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create ( Value( 5 ) ) ), UserException ); } }; /** Dependencies for non inclusion expressions. */ class NonInclusionDependencies : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value( 5 ) ) ); assertDependencies( BSON_ARRAY( "_id" ), expression, true ); assertDependencies( BSONArray(), expression, false ); expression->addField( mongo::FieldPath( "b" ), ExpressionFieldPath::create( "c.d" ) ); assertDependencies( BSON_ARRAY( "_id" << "c.d" ), expression, true ); assertDependencies( BSON_ARRAY( "c.d" ), expression, false ); } }; /** Dependencies for inclusion expressions. */ class InclusionDependencies : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->includePath( "a" ); assertDependencies( BSON_ARRAY( "_id" << "a" ), expression, true ); DepsTracker unused; // 'path' must be provided for inclusion expressions. ASSERT_THROWS( expression->addDependencies( &unused ), UserException ); } }; /** Optimizing an object expression optimizes its sub expressions. */ class Optimize : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); // Add inclusion. expression->includePath( "a" ); // Add non inclusion. intrusive_ptr andExpr = new ExpressionAnd(); expression->addField( mongo::FieldPath( "b" ), andExpr ); expression->optimize(); // Optimizing 'expression' optimizes its non inclusion sub expressions, while // inclusion sub expressions are passed through. ASSERT_EQUALS( BSON( "a" << true << "b" << BSON( "$const" << true ) ), expressionToBson( expression ) ); } }; /** Serialize to a BSONObj. */ class AddToBsonObj : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value( 5 ) ) ); ASSERT_EQUALS(constify(BSON("foo" << BSON("a" << 5))), BSON("foo" << expression->serialize(false))); } }; /** Serialize to a BSONObj, with constants represented by expressions. */ class AddToBsonObjRequireExpression : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value( 5 ) ) ); ASSERT_EQUALS(BSON("foo" << BSON("a" << BSON("$const" << 5))), BSON("foo" << expression->serialize(false))); } }; /** Serialize to a BSONArray. */ class AddToBsonArray : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->addField( mongo::FieldPath( "a" ), ExpressionConstant::create( Value( 5 ) ) ); BSONArrayBuilder bab; bab << expression->serialize(false); ASSERT_EQUALS( constify( BSON_ARRAY( BSON( "a" << 5 ) ) ), bab.arr() ); } }; /** * evaluate() does not supply an inclusion document. Inclusion spec'd fields are not * included. (Inclusion specs are not generally expected/allowed in cases where evaluate * is called instead of addToDocument.) */ class Evaluate : public Base { public: void run() { intrusive_ptr expression = ExpressionObject::createRoot(); expression->includePath( "a" ); expression->addField( mongo::FieldPath( "b" ), ExpressionConstant::create( Value( 5 ) ) ); expression->addField( mongo::FieldPath( "c" ), ExpressionFieldPath::create( "a" ) ); ASSERT_EQUALS( BSON( "b" << 5 << "c" << 1 ), toBson( expression->evaluate ( fromBson ( BSON( "_id" << 0 << "a" << 1 ) ) ).getDocument() ) ); } }; } // namespace Object namespace Or { class ExpectedResultBase { public: virtual ~ExpectedResultBase() { } void run() { BSONObj specObject = BSON( "" << spec() ); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) ); ASSERT_EQUALS( BSON( "" << expectedResult() ), toBson( expression->evaluate( fromBson( BSON( "a" << 1 ) ) ) ) ); intrusive_ptr optimized = expression->optimize(); ASSERT_EQUALS( BSON( "" << expectedResult() ), toBson( optimized->evaluate( fromBson( BSON( "a" << 1 ) ) ) ) ); } protected: virtual BSONObj spec() = 0; virtual bool expectedResult() = 0; }; class OptimizeBase { public: virtual ~OptimizeBase() { } void run() { BSONObj specObject = BSON( "" << spec() ); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) ); intrusive_ptr optimized = expression->optimize(); ASSERT_EQUALS( expectedOptimized(), expressionToBson( optimized ) ); } protected: virtual BSONObj spec() = 0; virtual BSONObj expectedOptimized() = 0; }; class NoOptimizeBase : public OptimizeBase { BSONObj expectedOptimized() { return constify( spec() ); } }; /** $or without operands. */ class NoOperands : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSONArray() ); } bool expectedResult() { return false; } }; /** $or passed 'true'. */ class True : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( true ) ); } bool expectedResult() { return true; } }; /** $or passed 'false'. */ class False : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( false ) ); } bool expectedResult() { return false; } }; /** $or passed 'true', 'true'. */ class TrueTrue : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( true << true ) ); } bool expectedResult() { return true; } }; /** $or passed 'true', 'false'. */ class TrueFalse : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( true << false ) ); } bool expectedResult() { return true; } }; /** $or passed 'false', 'true'. */ class FalseTrue : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( false << true ) ); } bool expectedResult() { return true; } }; /** $or passed 'false', 'false'. */ class FalseFalse : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( false << false ) ); } bool expectedResult() { return false; } }; /** $or passed 'false', 'false', 'false'. */ class FalseFalseFalse : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( false << false << false ) ); } bool expectedResult() { return false; } }; /** $or passed 'false', 'false', 'true'. */ class FalseFalseTrue : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( false << false << true ) ); } bool expectedResult() { return true; } }; /** $or passed '0', '1'. */ class ZeroOne : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( 0 << 1 ) ); } bool expectedResult() { return true; } }; /** $or passed '0', 'false'. */ class ZeroFalse : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( 0 << false ) ); } bool expectedResult() { return false; } }; /** $or passed a field path. */ class FieldPath : public ExpectedResultBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( "$a" ) ); } bool expectedResult() { return true; } }; /** A constant expression is optimized to a constant. */ class OptimizeConstantExpression : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( 1 ) ); } BSONObj expectedOptimized() { return BSON( "$const" << true ); } }; /** A non constant expression is not optimized. */ class NonConstant : public NoOptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( "$a" ) ); } }; /** An expression beginning with a single constant is optimized. */ class ConstantNonConstantTrue : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( 1 << "$a" ) ); } BSONObj expectedOptimized() { return BSON( "$const" << true ); } }; /** An expression beginning with a single constant is optimized. */ class ConstantNonConstantFalse : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( 0 << "$a" ) ); } BSONObj expectedOptimized() { return BSON( "$and" << BSON_ARRAY("$a") ); } // note: using $and as serialization of ExpressionCoerceToBool rather than ExpressionAnd }; /** An expression with a field path and '1'. */ class NonConstantOne : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( "$a" << 1 ) ); } BSONObj expectedOptimized() { return BSON( "$const" << true ); } }; /** An expression with a field path and '0'. */ class NonConstantZero : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( "$a" << 0 ) ); } BSONObj expectedOptimized() { return BSON( "$and" << BSON_ARRAY( "$a" ) ); } }; /** An expression with two field paths and '1'. */ class NonConstantNonConstantOne : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( "$a" << "$b" << 1 ) ); } BSONObj expectedOptimized() { return BSON( "$const" << true ); } }; /** An expression with two field paths and '0'. */ class NonConstantNonConstantZero : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( "$a" << "$b" << 0 ) ); } BSONObj expectedOptimized() { return BSON( "$or" << BSON_ARRAY( "$a" << "$b" ) ); } }; /** An expression with '0', '1', and a field path. */ class ZeroOneNonConstant : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( 0 << 1 << "$a" ) ); } BSONObj expectedOptimized() { return BSON( "$const" << true ); } }; /** An expression with '0', '0', and a field path. */ class ZeroZeroNonConstant : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( 0 << 0 << "$a" ) ); } BSONObj expectedOptimized() { return BSON( "$and" << BSON_ARRAY( "$a" ) ); } }; /** Nested $or expressions. */ class Nested : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( 0 << BSON( "$or" << BSON_ARRAY( 0 ) ) << "$a" << "$b" ) ); } BSONObj expectedOptimized() { return BSON( "$or" << BSON_ARRAY( "$a" << "$b" ) ); } }; /** Nested $or expressions containing a nested value evaluating to false. */ class NestedOne : public OptimizeBase { BSONObj spec() { return BSON( "$or" << BSON_ARRAY( 0 << BSON( "$or" << BSON_ARRAY( BSON( "$or" << BSON_ARRAY( 1 ) ) ) ) << "$a" << "$b" ) ); } BSONObj expectedOptimized() { return BSON( "$const" << true ); } }; } // namespace Or namespace Parse { namespace Object { class Base { public: virtual ~Base() {} void run() { BSONObj specObject = BSON( "" << spec() ); BSONElement specElement = specObject.firstElement(); Expression::ObjectCtx context = objectCtx(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseObject( specElement.Obj(), &context, vps ); ASSERT_EQUALS( expectedBson(), expressionToBson( expression ) ); } protected: virtual BSONObj spec() = 0; virtual Expression::ObjectCtx objectCtx() { return Expression::ObjectCtx( Expression::ObjectCtx::DOCUMENT_OK ); } virtual BSONObj expectedBson() { return constify( spec() ); } }; class ParseError { public: virtual ~ParseError() {} void run() { BSONObj specObject = BSON( "" << spec() ); BSONElement specElement = specObject.firstElement(); Expression::ObjectCtx context = objectCtx(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); ASSERT_THROWS( Expression::parseObject( specElement.Obj(), &context, vps ), UserException ); } protected: virtual BSONObj spec() = 0; virtual Expression::ObjectCtx objectCtx() { return Expression::ObjectCtx( Expression::ObjectCtx::DOCUMENT_OK ); } }; /** The spec must be an object. */ class NonObject { public: void run() { BSONObj specObject = BSON( "" << 1 ); BSONElement specElement = specObject.firstElement(); Expression::ObjectCtx context = Expression::ObjectCtx( Expression::ObjectCtx::DOCUMENT_OK ); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); ASSERT_THROWS( Expression::parseObject( specElement.Obj(), &context, vps ), UserException ); } }; /** Empty object. */ class Empty : public Base { BSONObj spec() { return BSONObj(); } }; /** Operator spec object. */ class Operator : public Base { BSONObj spec() { return BSON( "$and" << BSONArray() ); } }; /** Invalid operator not allowed. */ class InvalidOperator : public ParseError { BSONObj spec() { return BSON( "$invalid" << 1 ); } }; /** Two operators not allowed. */ class TwoOperators : public ParseError { BSONObj spec() { return BSON( "$and" << BSONArray() << "$or" << BSONArray() ); } }; /** An operator must be the first and only field. */ class OperatorLaterField : public ParseError { BSONObj spec() { return BSON( "a" << BSON( "$and" << BSONArray() ) << "$or" << BSONArray() ); } }; /** An operator must be the first and only field. */ class OperatorAndOtherField : public ParseError { BSONObj spec() { return BSON( "$and" << BSONArray() << "a" << BSON( "$or" << BSONArray() ) ); } }; /** Operators not allowed at the top level of a projection. */ class OperatorTopLevel : public ParseError { BSONObj spec() { return BSON( "$and" << BSONArray() ); } Expression::ObjectCtx objectCtx() { return Expression::ObjectCtx( Expression::ObjectCtx::DOCUMENT_OK | Expression::ObjectCtx::TOP_LEVEL ); } }; /** Dotted fields are not generally allowed. */ class Dotted : public ParseError { BSONObj spec() { return BSON( "a.b" << BSON( "$and" << BSONArray() ) ); } }; /** Dotted fields are allowed at the top level. */ class DottedTopLevel : public Base { BSONObj spec() { return BSON( "a.b" << BSON( "$and" << BSONArray() ) ); } Expression::ObjectCtx objectCtx() { return Expression::ObjectCtx( Expression::ObjectCtx::DOCUMENT_OK | Expression::ObjectCtx::TOP_LEVEL ); } BSONObj expectedBson() { return BSON( "a" << BSON( "b" << BSON( "$and" << BSONArray() ) ) ); } }; /** Nested spec. */ class Nested : public Base { BSONObj spec() { return BSON( "a" << BSON( "$and" << BSONArray() ) ); } }; /** Parse error in nested document. */ class NestedParseError : public ParseError { BSONObj spec() { return BSON( "a" << BSON( "$and" << BSONArray() << "$or" << BSONArray() ) ); } }; /** FieldPath expression. */ class FieldPath : public Base { BSONObj spec() { return BSON( "a" << "$field" ); } }; /** Invalid FieldPath expression. */ class InvalidFieldPath : public ParseError { BSONObj spec() { return BSON( "a" << "$field." ); } }; /** Non FieldPath string. */ class NonFieldPathString : public ParseError { BSONObj spec() { return BSON( "a" << "foo" ); } }; /** Inclusion spec not allowed. */ class DisallowedInclusion : public ParseError { BSONObj spec() { return BSON( "a" << 1 ); } }; class InclusionBase : public Base { Expression::ObjectCtx objectCtx() { return Expression::ObjectCtx( Expression::ObjectCtx::DOCUMENT_OK | Expression::ObjectCtx::INCLUSION_OK ); } BSONObj expectedBson() { return BSON( "a" << true ); } }; /** Inclusion with bool type. */ class InclusionBool : public InclusionBase { BSONObj spec() { return BSON( "a" << true ); } }; /** Inclusion with double type. */ class InclusionDouble : public InclusionBase { BSONObj spec() { return BSON( "a" << 1.0 ); } }; /** Inclusion with int type. */ class InclusionInt : public InclusionBase { BSONObj spec() { return BSON( "a" << 1 ); } }; /** Inclusion with long type. */ class InclusionLong : public InclusionBase { BSONObj spec() { return BSON( "a" << 1LL ); } }; /** Inclusion of a nested field. */ class NestedInclusion : public InclusionBase { BSONObj spec() { return BSON( "a" << BSON( "b" << true ) ); } BSONObj expectedBson() { return spec(); } }; /** Exclude _id. */ class ExcludeId : public Base { BSONObj spec() { return BSON( "_id" << 0 ); } Expression::ObjectCtx objectCtx() { return Expression::ObjectCtx( Expression::ObjectCtx::DOCUMENT_OK | Expression::ObjectCtx::TOP_LEVEL ); } BSONObj expectedBson() { return BSON( "_id" << false ); } }; /** Excluding non _id field not allowed. */ class ExcludeNonId : public ParseError { BSONObj spec() { return BSON( "a" << 0 ); } }; /** Excluding _id not top level. */ class ExcludeIdNotTopLevel : public ParseError { BSONObj spec() { return BSON( "_id" << 0 ); } }; /** Invalid value type. */ class InvalidType : public ParseError { BSONObj spec() { return BSON( "a" << BSONNULL ); } }; } // namespace Object namespace Expression { using mongo::Expression; class Base { public: virtual ~Base() {} void run() { BSONObj specObject = spec(); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseExpression(specElement, vps); ASSERT_EQUALS( constify( expectedBson() ), expressionToBson( expression ) ); } protected: virtual BSONObj spec() = 0; virtual BSONObj expectedBson() { return constify( spec() ); } }; class ParseError { public: virtual ~ParseError() {} void run() { BSONObj specObject = spec(); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); ASSERT_THROWS(Expression::parseExpression(specElement, vps), UserException); } protected: virtual BSONObj spec() = 0; }; /** A constant expression. */ class Const : public Base { BSONObj spec() { return BSON( "$const" << 5 ); } }; /** An expression with an invalid name. */ class InvalidName : public ParseError { BSONObj spec() { return BSON( "$invalid" << 1 ); } }; /** An expression requiring an array that is not provided with an array. */ class RequiredArrayMissing : public ParseError { BSONObj spec() { return BSON( "$strcasecmp" << "foo" ); } }; /** An expression with the wrong number of operands. */ class IncorrectOperandCount : public ParseError { BSONObj spec() { return BSON( "$strcasecmp" << BSON_ARRAY( "foo" ) ); } }; /** An expression with the correct number of operands. */ class CorrectOperandCount : public Base { BSONObj spec() { return BSON( "$strcasecmp" << BSON_ARRAY( "foo" << "FOO" ) ); } }; /** An variable argument expression with zero operands. */ class ZeroOperands : public Base { BSONObj spec() { return BSON( "$and" << BSONArray() ); } }; /** An variable argument expression with one operand. */ class OneOperand : public Base { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 1 ) ); } }; /** An variable argument expression with two operands. */ class TwoOperands : public Base { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( 1 << 2 ) ); } }; /** An variable argument expression with a singleton operand. */ class SingletonOperandVariable : public Base { BSONObj spec() { return BSON( "$and" << 1 ); } BSONObj expectedBson() { return BSON( "$and" << BSON_ARRAY( 1 ) ); } }; /** An fixed argument expression with a singleton operand. */ class SingletonOperandFixed : public Base { BSONObj spec() { return BSON( "$not" << 1 ); } BSONObj expectedBson() { return BSON( "$not" << BSON_ARRAY( 1 ) ); } }; /** An object can be provided as a singleton argument. */ class ObjectSingleton : public Base { BSONObj spec() { return BSON( "$and" << BSON( "$const" << 1 ) ); } BSONObj expectedBson() { return BSON("$and" << BSON_ARRAY(BSON("$const" << 1))); } }; /** An object can be provided as an array agrument. */ class ObjectOperand : public Base { BSONObj spec() { return BSON( "$and" << BSON_ARRAY( BSON( "$const" << 1 ) ) ); } BSONObj expectedBson() { return BSON( "$and" << BSON_ARRAY( 1 ) ); } }; } // namespace Expression namespace Operand { class Base { public: virtual ~Base() {} void run() { BSONObj specObject = spec(); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = mongo::Expression::parseOperand(specElement, vps); ASSERT_EQUALS( expectedBson(), expressionToBson( expression ) ); } protected: virtual BSONObj spec() = 0; virtual BSONObj expectedBson() { return constify( spec() ); } }; class ParseError { public: virtual ~ParseError() {} void run() { BSONObj specObject = spec(); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); ASSERT_THROWS(mongo::Expression::parseOperand(specElement, vps), UserException); } protected: virtual BSONObj spec() = 0; }; /** A field path operand. */ class FieldPath { public: void run() { BSONObj specObject = BSON( "" << "$field" ); BSONElement specElement = specObject.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = mongo::Expression::parseOperand(specElement, vps); ASSERT_EQUALS(specObject, BSON("" << expression->serialize(false))); } }; /** A string constant (not field path) operand. */ class NonFieldPathString : public Base { BSONObj spec() { return BSON( "" << "foo" ); } BSONObj expectedBson() { return BSON( "$const" << "foo" ); } }; /** An object operand. */ class Object : public Base { BSONObj spec() { return BSON( "" << BSON( "$and" << BSONArray() ) ); } BSONObj expectedBson() { return BSON( "$and" << BSONArray() ); } }; /** An inclusion operand. */ class InclusionObject : public ParseError { BSONObj spec() { return BSON( "" << BSON( "a" << 1 ) ); } }; /** A constant operand. */ class Constant : public Base { BSONObj spec() { return BSON( "" << 5 ); } BSONObj expectedBson() { return BSON( "$const" << 5 ); } }; } // namespace Operand } // namespace Parse namespace Set { Value sortSet(Value set) { if (set.nullish()) { return Value(BSONNULL); } vector sortedSet = set.getArray(); std::sort(sortedSet.begin(), sortedSet.end()); return Value(sortedSet); } class ExpectedResultBase { public: virtual ~ExpectedResultBase() {} void run() { const Document spec = getSpec(); const Value args = spec["input"]; if (!spec["expected"].missing()) { FieldIterator fields(spec["expected"].getDocument()); while (fields.more()) { const Document::FieldPair field(fields.next()); const Value expected = field.second; const BSONObj obj = BSON(field.first << args); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); const intrusive_ptr expr = Expression::parseExpression(obj.firstElement(), vps); Value result = expr->evaluate(Document()); if (result.getType() == Array) { result = sortSet(result); } if (result != expected) { string errMsg = str::stream() << "for expression " << field.first.toString() << " with argument " << args.toString() << " full tree: " << expr->serialize(false).toString() << " expected: " << expected.toString() << " but got: " << result.toString(); FAIL(errMsg); } //TODO test optimize here } } if (!spec["error"].missing()) { const vector& asserters = spec["error"].getArray(); size_t n = asserters.size(); for (size_t i = 0; i < n; i++) { const BSONObj obj = BSON(asserters[i].getString() << args); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); ASSERT_THROWS({ // NOTE: parse and evaluatation failures are treated the same const intrusive_ptr expr = Expression::parseExpression(obj.firstElement(), vps); expr->evaluate(Document()); }, UserException); } } } private: virtual Document getSpec() = 0; }; class Same : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2) << DOC_ARRAY(1 << 2) ) << "expected" << DOC("$setIsSubset" << true << "$setEquals" << true << "$setIntersection" << DOC_ARRAY(1 << 2) << "$setUnion" << DOC_ARRAY(1 << 2) << "$setDifference" << vector() ) ); } }; class Redundant : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2) << DOC_ARRAY(1 << 2 << 2) ) << "expected" << DOC("$setIsSubset" << true << "$setEquals" << true << "$setIntersection" << DOC_ARRAY(1 << 2) << "$setUnion" << DOC_ARRAY(1 << 2) << "$setDifference" << vector() ) ); } }; class DoubleRedundant : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 1 << 2) << DOC_ARRAY(1 << 2 << 2) ) << "expected" << DOC("$setIsSubset" << true << "$setEquals" << true << "$setIntersection" << DOC_ARRAY(1 << 2) << "$setUnion" << DOC_ARRAY(1 << 2) << "$setDifference" << vector() ) ); } }; class Super : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2) << DOC_ARRAY(1) ) << "expected" << DOC("$setIsSubset" << false << "$setEquals" << false << "$setIntersection" << DOC_ARRAY(1) << "$setUnion" << DOC_ARRAY(1 << 2) << "$setDifference" << DOC_ARRAY(2) ) ); } }; class SuperWithRedundant : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2 << 2) << DOC_ARRAY(1) ) << "expected" << DOC("$setIsSubset" << false << "$setEquals" << false << "$setIntersection" << DOC_ARRAY(1) << "$setUnion" << DOC_ARRAY(1 << 2) << "$setDifference" << DOC_ARRAY(2) ) ); } }; class Sub : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1) << DOC_ARRAY(1 << 2) ) << "expected" << DOC("$setIsSubset" << true << "$setEquals" << false << "$setIntersection" << DOC_ARRAY(1) << "$setUnion" << DOC_ARRAY(1 << 2) << "$setDifference" << vector() ) ); } }; class SameBackwards : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2) << DOC_ARRAY(2 << 1) ) << "expected" << DOC("$setIsSubset" << true << "$setEquals" << true << "$setIntersection" << DOC_ARRAY(1 << 2) << "$setUnion" << DOC_ARRAY(1 << 2) << "$setDifference" << vector() ) ); } }; class NoOverlap : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2) << DOC_ARRAY(8 << 4) ) << "expected" << DOC("$setIsSubset" << false << "$setEquals" << false << "$setIntersection" << vector() << "$setUnion" << DOC_ARRAY(1 << 2 << 4 << 8) << "$setDifference" << DOC_ARRAY(1 << 2)) ); } }; class Overlap : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2) << DOC_ARRAY(8 << 2 << 4) ) << "expected" << DOC("$setIsSubset" << false << "$setEquals" << false << "$setIntersection" << DOC_ARRAY(2) << "$setUnion" << DOC_ARRAY(1 << 2 << 4 << 8) << "$setDifference" << DOC_ARRAY(1)) ); } }; class LastNull : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2) << Value(BSONNULL) ) << "expected" << DOC("$setIntersection" << BSONNULL << "$setUnion" << BSONNULL << "$setDifference" << BSONNULL ) << "error" << DOC_ARRAY("$setEquals" << "$setIsSubset") ); } }; class FirstNull : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( Value(BSONNULL) << DOC_ARRAY(1 << 2) ) << "expected" << DOC("$setIntersection" << BSONNULL << "$setUnion" << BSONNULL << "$setDifference" << BSONNULL ) << "error" << DOC_ARRAY("$setEquals" << "$setIsSubset") ); } }; class NoArg : public ExpectedResultBase { Document getSpec() { return DOC("input" << vector() << "expected" << DOC("$setIntersection" << vector() << "$setUnion" << vector() ) << "error" << DOC_ARRAY("$setEquals" << "$setIsSubset" << "$setDifference") ); } }; class OneArg : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2) ) << "expected" << DOC("$setIntersection" << DOC_ARRAY(1 << 2) << "$setUnion" << DOC_ARRAY(1 << 2) ) << "error" << DOC_ARRAY("$setEquals" << "$setIsSubset" << "$setDifference") ); } }; class EmptyArg : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( vector() ) << "expected" << DOC("$setIntersection" << vector() << "$setUnion" << vector() ) << "error" << DOC_ARRAY("$setEquals" << "$setIsSubset" << "$setDifference") ); } }; class LeftArgEmpty : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( vector() << DOC_ARRAY(1 << 2) ) << "expected" << DOC("$setIntersection" << vector() << "$setUnion" << DOC_ARRAY(1 << 2) << "$setIsSubset" << true << "$setEquals" << false << "$setDifference" << vector() ) ); } }; class RightArgEmpty : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2) << vector() ) << "expected" << DOC("$setIntersection" << vector() << "$setUnion" << DOC_ARRAY(1 << 2) << "$setIsSubset" << false << "$setEquals" << false << "$setDifference" << DOC_ARRAY(1 << 2) ) ); } }; class ManyArgs : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(8 << 3) << DOC_ARRAY("asdf" << "foo") << DOC_ARRAY(80.3 << 34) << vector() << DOC_ARRAY(80.3 << "foo" << 11 << "yay") ) << "expected" << DOC("$setIntersection" << vector() << "$setEquals" << false << "$setUnion" << DOC_ARRAY(3 << 8 << 11 << 34 << 80.3 << "asdf" << "foo" << "yay") ) << "error" << DOC_ARRAY("$setIsSubset" << "$setDifference") ); } }; class ManyArgsEqual : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2 << 4) << DOC_ARRAY(1 << 2 << 2 << 4) << DOC_ARRAY(4 << 1 << 2) << DOC_ARRAY(2 << 1 << 1 << 4) ) << "expected" << DOC("$setIntersection" << DOC_ARRAY(1 << 2 << 4) << "$setEquals" << true << "$setUnion" << DOC_ARRAY(1 << 2 << 4) ) << "error" << DOC_ARRAY("$setIsSubset" << "$setDifference") ); } }; } // namespace Set namespace Strcasecmp { class ExpectedResultBase { public: virtual ~ExpectedResultBase() { } void run() { assertResult( expectedResult(), spec() ); assertResult( -expectedResult(), reverseSpec() ); } protected: virtual string a() = 0; virtual string b() = 0; virtual int expectedResult() = 0; private: BSONObj spec() { return BSON( "$strcasecmp" << BSON_ARRAY( a() << b() ) ); } BSONObj reverseSpec() { return BSON( "$strcasecmp" << BSON_ARRAY( b() << a() ) ); } void assertResult( int expectedResult, const BSONObj& spec ) { BSONObj specObj = BSON( "" << spec ); BSONElement specElement = specObj.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); ASSERT_EQUALS( constify( spec ), expressionToBson( expression ) ); ASSERT_EQUALS( BSON( "" << expectedResult ), toBson( expression->evaluate( Document() ) ) ); } }; class NullBegin : public ExpectedResultBase { string a() { return string( "\0ab", 3 ); } string b() { return string( "\0AB", 3 ); } int expectedResult() { return 0; } }; class NullEnd : public ExpectedResultBase { string a() { return string( "ab\0", 3 ); } string b() { return string( "aB\0", 3 ); } int expectedResult() { return 0; } }; class NullMiddleLt : public ExpectedResultBase { string a() { return string( "a\0a", 3 ); } string b() { return string( "a\0B", 3 ); } int expectedResult() { return -1; } }; class NullMiddleEq : public ExpectedResultBase { string a() { return string( "a\0b", 3 ); } string b() { return string( "a\0B", 3 ); } int expectedResult() { return 0; } }; class NullMiddleGt : public ExpectedResultBase { string a() { return string( "a\0c", 3 ); } string b() { return string( "a\0B", 3 ); } int expectedResult() { return 1; } }; } // namespace Strcasecmp namespace Substr { class ExpectedResultBase { public: virtual ~ExpectedResultBase() { } void run() { BSONObj specObj = BSON( "" << spec() ); BSONElement specElement = specObj.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) ); ASSERT_EQUALS( BSON( "" << expectedResult() ), toBson( expression->evaluate( Document() ) ) ); } protected: virtual string str() = 0; virtual int offset() = 0; virtual int length() = 0; virtual string expectedResult() = 0; private: BSONObj spec() { return BSON( "$substr" << BSON_ARRAY( str() << offset() << length() ) ); } }; /** Retrieve a full string containing a null character. */ class FullNull : public ExpectedResultBase { string str() { return string( "a\0b", 3 ); } int offset() { return 0; } int length() { return 3; } string expectedResult() { return str(); } }; /** Retrieve a substring beginning with a null character. */ class BeginAtNull : public ExpectedResultBase { string str() { return string( "a\0b", 3 ); } int offset() { return 1; } int length() { return 2; } string expectedResult() { return string( "\0b", 2 ); } }; /** Retrieve a substring ending with a null character. */ class EndAtNull : public ExpectedResultBase { string str() { return string( "a\0b", 3 ); } int offset() { return 0; } int length() { return 2; } string expectedResult() { return string( "a\0", 2 ); } }; /** Drop a beginning null character. */ class DropBeginningNull : public ExpectedResultBase { string str() { return string( "\0b", 2 ); } int offset() { return 1; } int length() { return 1; } string expectedResult() { return "b"; } }; /** Drop an ending null character. */ class DropEndingNull : public ExpectedResultBase { string str() { return string( "a\0", 2 ); } int offset() { return 0; } int length() { return 1; } string expectedResult() { return "a"; } }; } // namespace Substr namespace ToLower { class ExpectedResultBase { public: virtual ~ExpectedResultBase() { } void run() { BSONObj specObj = BSON( "" << spec() ); BSONElement specElement = specObj.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) ); ASSERT_EQUALS( BSON( "" << expectedResult() ), toBson( expression->evaluate( Document() ) ) ); } protected: virtual string str() = 0; virtual string expectedResult() = 0; private: BSONObj spec() { return BSON( "$toLower" << BSON_ARRAY( str() ) ); } }; /** String beginning with a null character. */ class NullBegin : public ExpectedResultBase { string str() { return string( "\0aB", 3 ); } string expectedResult() { return string( "\0ab", 3 ); } }; /** String containing a null character. */ class NullMiddle : public ExpectedResultBase { string str() { return string( "a\0B", 3 ); } string expectedResult() { return string( "a\0b", 3 ); } }; /** String ending with a null character. */ class NullEnd : public ExpectedResultBase { string str() { return string( "aB\0", 3 ); } string expectedResult() { return string( "ab\0", 3 ); } }; } // namespace ToLower namespace ToUpper { class ExpectedResultBase { public: virtual ~ExpectedResultBase() { } void run() { BSONObj specObj = BSON( "" << spec() ); BSONElement specElement = specObj.firstElement(); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); intrusive_ptr expression = Expression::parseOperand(specElement, vps); ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) ); ASSERT_EQUALS( BSON( "" << expectedResult() ), toBson( expression->evaluate( Document() ) ) ); } protected: virtual string str() = 0; virtual string expectedResult() = 0; private: BSONObj spec() { return BSON( "$toUpper" << BSON_ARRAY( str() ) ); } }; /** String beginning with a null character. */ class NullBegin : public ExpectedResultBase { string str() { return string( "\0aB", 3 ); } string expectedResult() { return string( "\0AB", 3 ); } }; /** String containing a null character. */ class NullMiddle : public ExpectedResultBase { string str() { return string( "a\0B", 3 ); } string expectedResult() { return string( "A\0B", 3 ); } }; /** String ending with a null character. */ class NullEnd : public ExpectedResultBase { string str() { return string( "aB\0", 3 ); } string expectedResult() { return string( "AB\0", 3 ); } }; } // namespace ToUpper namespace AllAnyElements { class ExpectedResultBase { public: virtual ~ExpectedResultBase() {} void run() { const Document spec = getSpec(); const Value args = spec["input"]; if (!spec["expected"].missing()) { FieldIterator fields(spec["expected"].getDocument()); while (fields.more()) { const Document::FieldPair field(fields.next()); const Value expected = field.second; const BSONObj obj = BSON(field.first << args); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); const intrusive_ptr expr = Expression::parseExpression(obj.firstElement(), vps); const Value result = expr->evaluate(Document()); if (result != expected) { string errMsg = str::stream() << "for expression " << field.first.toString() << " with argument " << args.toString() << " full tree: " << expr->serialize(false).toString() << " expected: " << expected.toString() << " but got: " << result.toString(); FAIL(errMsg); } //TODO test optimize here } } if (!spec["error"].missing()) { const vector& asserters = spec["error"].getArray(); size_t n = asserters.size(); for (size_t i = 0; i < n; i++) { const BSONObj obj = BSON(asserters[i].getString() << args); VariablesIdGenerator idGenerator; VariablesParseState vps(&idGenerator); ASSERT_THROWS({ // NOTE: parse and evaluatation failures are treated the same const intrusive_ptr expr = Expression::parseExpression(obj.firstElement(), vps); expr->evaluate(Document()); }, UserException); } } } private: virtual Document getSpec() = 0; }; class JustFalse : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(false) ) << "expected" << DOC("$allElementsTrue" << false << "$anyElementTrue" << false) ); } }; class JustTrue : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(true) ) << "expected" << DOC("$allElementsTrue" << true << "$anyElementTrue" << true) ); } }; class OneTrueOneFalse : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(true << false) ) << "expected" << DOC("$allElementsTrue" << false << "$anyElementTrue" << true) ); } }; class Empty : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( vector() ) << "expected" << DOC("$allElementsTrue" << true << "$anyElementTrue" << false) ); } }; class TrueViaInt : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(1) ) << "expected" << DOC("$allElementsTrue" << true << "$anyElementTrue" << true) ); } }; class FalseViaInt : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY( DOC_ARRAY(0) ) << "expected" << DOC("$allElementsTrue" << false << "$anyElementTrue" << false) ); } }; class Null : public ExpectedResultBase { Document getSpec() { return DOC("input" << DOC_ARRAY(BSONNULL) << "error" << DOC_ARRAY("$allElementsTrue" << "$anyElementTrue") ); } }; } // namespace AllAnyElements class All : public Suite { public: All() : Suite( "expression" ) { } void setupTests() { add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); add(); } }; SuiteInstance myall; } // namespace ExpressionTests