// 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