summaryrefslogtreecommitdiff
path: root/src/mongo/dbtests/expressiontests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/dbtests/expressiontests.cpp')
-rw-r--r--src/mongo/dbtests/expressiontests.cpp7864
1 files changed, 4227 insertions, 3637 deletions
diff --git a/src/mongo/dbtests/expressiontests.cpp b/src/mongo/dbtests/expressiontests.cpp
index 8a4592bc89a..a82e5f8f09b 100644
--- a/src/mongo/dbtests/expressiontests.cpp
+++ b/src/mongo/dbtests/expressiontests.cpp
@@ -36,3718 +36,4308 @@
namespace ExpressionTests {
- using boost::intrusive_ptr;
- using std::numeric_limits;
- using std::set;
- using std::string;
- using std::vector;
-
- /** 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));
- }
+using boost::intrusive_ptr;
+using std::numeric_limits;
+using std::set;
+using std::string;
+using std::vector;
+
+/** 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();
+ }
+ 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>& 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<ExpressionNary> expression = new ExpressionAdd();
+ populateOperands(expression);
+ ASSERT_EQUALS(expectedResult(), toBson(expression->evaluate(Document())));
}
- /** 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 ) );
+protected:
+ virtual void populateOperands(intrusive_ptr<ExpressionNary>& expression) = 0;
+ virtual BSONObj expectedResult() = 0;
+};
+
+/** $add with a NULL Document pointer, as called by ExpressionNary::optimize(). */
+class NullDocument {
+public:
+ void run() {
+ intrusive_ptr<ExpressionNary> expression = new ExpressionAdd();
+ expression->addOperand(ExpressionConstant::create(Value(2)));
+ ASSERT_EQUALS(BSON("" << 2), toBson(expression->evaluate(Document())));
}
+};
- /** 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();
+/** $add without operands. */
+class NoOperands : public ExpectedResultBase {
+ void populateOperands(intrusive_ptr<ExpressionNary>& expression) {}
+ virtual BSONObj expectedResult() {
+ return BSON("" << 0);
}
+};
+
+/** String type unsupported. */
+class String {
+public:
+ void run() {
+ intrusive_ptr<ExpressionNary> 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<ExpressionNary> expression = new ExpressionAdd();
+ expression->addOperand(ExpressionConstant::create(Value(true)));
+ ASSERT_THROWS(expression->evaluate(Document()), UserException);
+ }
+};
- /** Convert Expression to BSON. */
- static BSONObj expressionToBson( const intrusive_ptr<Expression>& expression ) {
- return BSON("" << expression->serialize(false)).firstElement().embeddedObject().getOwned();
+class SingleOperandBase : public ExpectedResultBase {
+ void populateOperands(intrusive_ptr<ExpressionNary>& expression) {
+ expression->addOperand(ExpressionConstant::create(valueFromBson(operand())));
}
+ BSONObj expectedResult() {
+ return operand();
+ }
+
+protected:
+ virtual BSONObj operand() = 0;
+};
- /** Convert Document to BSON. */
- static BSONObj toBson( const Document& document ) {
- return document.toBson();
+/** Single int argument. */
+class Int : public SingleOperandBase {
+ BSONObj operand() {
+ return BSON("" << 1);
}
-
- /** Create a Document from a BSONObj. */
- Document fromBson( BSONObj obj ) {
- return Document(obj);
+};
+
+/** Single long argument. */
+class Long : public SingleOperandBase {
+ BSONObj operand() {
+ return BSON("" << 5555LL);
}
+};
- /** Create a Value from a BSONObj. */
- Value valueFromBson( BSONObj obj ) {
- BSONElement element = obj.firstElement();
- return Value( element );
+/** Single double argument. */
+class Double : public SingleOperandBase {
+ BSONObj operand() {
+ return BSON("" << 99.99);
}
-
- namespace Add {
+};
- class ExpectedResultBase {
- public:
- virtual ~ExpectedResultBase() {}
- void run() {
- intrusive_ptr<ExpressionNary> expression = new ExpressionAdd();
- populateOperands( expression );
- ASSERT_EQUALS( expectedResult(),
- toBson( expression->evaluate( Document() ) ) );
- }
- protected:
- virtual void populateOperands( intrusive_ptr<ExpressionNary>& expression ) = 0;
- virtual BSONObj expectedResult() = 0;
- };
-
- /** $add with a NULL Document pointer, as called by ExpressionNary::optimize(). */
- class NullDocument {
- public:
- void run() {
- intrusive_ptr<ExpressionNary> 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<ExpressionNary>& expression ) {}
- virtual BSONObj expectedResult() { return BSON( "" << 0 ); }
- };
-
- /** String type unsupported. */
- class String {
- public:
- void run() {
- intrusive_ptr<ExpressionNary> 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<ExpressionNary> expression = new ExpressionAdd();
- expression->addOperand( ExpressionConstant::create( Value(true) ) );
- ASSERT_THROWS( expression->evaluate( Document() ), UserException );
- }
- };
-
- class SingleOperandBase : public ExpectedResultBase {
- void populateOperands( intrusive_ptr<ExpressionNary>& 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::fromMillisSinceEpoch(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<ExpressionNary>& 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<int>::max() ); }
- BSONObj operand2() { return BSON( "" << numeric_limits<int>::max() ); }
- BSONObj expectedResult() {
- return BSON( "" << ( (long long)( numeric_limits<int>::max() ) +
- numeric_limits<int>::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<int>::max() ); }
- BSONObj operand2() { return BSON( "" << numeric_limits<long long>::max() ); }
- BSONObj expectedResult() { return BSON( "" << ( numeric_limits<int>::max()
- + numeric_limits<long long>::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::fromMillisSinceEpoch(123450) ); }
- BSONObj expectedResult() { return BSON( "" << Date_t::fromMillisSinceEpoch(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<long long>::max() ); }
- BSONObj operand2() { return BSON( "" << double( numeric_limits<long long>::max() ) ); }
- BSONObj expectedResult() {
- return BSON( "" << numeric_limits<long long>::max()
- + double( numeric_limits<long long>::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 = Expression::parseOperand(specElement, vps);
- ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) );
- ASSERT_EQUALS( BSON( "" << expectedResult() ),
- toBson( expression->evaluate( fromBson( BSON( "a" << 1 ) ) ) ) );
- intrusive_ptr<Expression> 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 = Expression::parseOperand(specElement, vps);
- ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) );
- intrusive_ptr<Expression> 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<Expression> nested =
- ExpressionConstant::create( Value( 5 ) );
- intrusive_ptr<Expression> expression = ExpressionCoerceToBool::create( nested );
- ASSERT( expression->evaluate( Document() ).getBool() );
- }
- };
-
- /** Nested expression coerced to false. */
- class EvaluateFalse {
- public:
- void run() {
- intrusive_ptr<Expression> nested =
- ExpressionConstant::create( Value( 0 ) );
- intrusive_ptr<Expression> expression = ExpressionCoerceToBool::create( nested );
- ASSERT( !expression->evaluate( Document() ).getBool() );
- }
- };
-
- /** Dependencies forwarded from nested expression. */
- class Dependencies {
- public:
- void run() {
- intrusive_ptr<Expression> nested = ExpressionFieldPath::create( "a.b" );
- intrusive_ptr<Expression> 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 );
- }
- };
+/** Single Date argument. */
+class Date : public SingleOperandBase {
+ BSONObj operand() {
+ return BSON("" << Date_t::fromMillisSinceEpoch(12345));
+ }
+};
- /** Output to BSONObj. */
- class AddToBsonObj {
- public:
- void run() {
- intrusive_ptr<Expression> expression = ExpressionCoerceToBool::create(
- ExpressionFieldPath::create("foo"));
+/** Single null argument. */
+class Null : public SingleOperandBase {
+ BSONObj operand() {
+ return BSON("" << BSONNULL);
+ }
+ BSONObj expectedResult() {
+ return BSON("" << BSONNULL);
+ }
+};
- // serialized as $and because CoerceToBool isn't an ExpressionNary
- assertBinaryEqual(fromjson("{field:{$and:['$foo']}}"), toBsonObj(expression));
- }
- private:
- static BSONObj toBsonObj(const intrusive_ptr<Expression>& expression) {
- return BSON("field" << expression->serialize(false));
- }
- };
+/** 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();
+ }
- /** Output to BSONArray. */
- class AddToBsonArray {
- public:
- void run() {
- intrusive_ptr<Expression> expression = ExpressionCoerceToBool::create(
- ExpressionFieldPath::create("foo"));
+protected:
+ void populateOperands(intrusive_ptr<ExpressionNary>& 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;
- // 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>& expression) {
- BSONArrayBuilder bab;
- bab << expression->serialize(false);
- return bab.arr();
- }
- };
+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);
+ }
+};
- // TODO Test optimize(), difficult because a CoerceToBool cannot be output as BSON.
-
- } // namespace CoerceToBool
+/** Adding two large ints produces a long, not an overflowed int. */
+class IntIntNoOverflow : public TwoOperandBase {
+ BSONObj operand1() {
+ return BSON("" << numeric_limits<int>::max());
+ }
+ BSONObj operand2() {
+ return BSON("" << numeric_limits<int>::max());
+ }
+ BSONObj expectedResult() {
+ return BSON("" << ((long long)(numeric_limits<int>::max()) + numeric_limits<int>::max()));
+ }
+};
- namespace Compare {
+/** 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);
+ }
+};
- class OptimizeBase {
- public:
- virtual ~OptimizeBase() {
- }
- void run() {
- BSONObj specObject = BSON( "" << spec() );
- BSONElement specElement = specObject.firstElement();
- VariablesIdGenerator idGenerator;
- VariablesParseState vps(&idGenerator);
- intrusive_ptr<Expression> expression = Expression::parseOperand(specElement, vps);
- intrusive_ptr<Expression> 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 = 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<Expression> 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 = 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> 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> expression =
- ExpressionConstant::parse( specElement, vps );
- assertBinaryEqual( BSON( "" << "foo" ),
- toBson( expression->evaluate( Document() ) ) );
- }
- };
-
- /** No optimization is performed. */
- class Optimize {
- public:
- void run() {
- intrusive_ptr<Expression> 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> 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> 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>& expression ) {
- return BSON("field" << expression->serialize(false));
- }
- };
-
- /** Output to BSONArray. */
- class AddToBsonArray {
- public:
- void run() {
- intrusive_ptr<Expression> 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>& 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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<Value> 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<Testable> create( bool associativeAndCommutative = false ) {
- return new Testable(associativeAndCommutative);
- }
- static intrusive_ptr<ExpressionNary> factory() {
- return new Testable(true);
- }
- static intrusive_ptr<Testable> createFromOperands( const BSONArray& operands,
- bool haveFactory = false ) {
- VariablesIdGenerator idGenerator;
- VariablesParseState vps(&idGenerator);
- intrusive_ptr<Testable> 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 ) );
- }
+/** Adding an int and a long overflows. */
+class IntLongOverflow : public TwoOperandBase {
+ BSONObj operand1() {
+ return BSON("" << numeric_limits<int>::max());
+ }
+ BSONObj operand2() {
+ return BSON("" << numeric_limits<long long>::max());
+ }
+ BSONObj expectedResult() {
+ return BSON("" << (numeric_limits<int>::max() + numeric_limits<long long>::max()));
+ }
+};
- private:
- Testable(bool isAssociativeAndCommutative)
- : _isAssociativeAndCommutative(isAssociativeAndCommutative)
- {}
- bool _isAssociativeAndCommutative;
- };
-
- /** Adding operands to the expression. */
- class AddOperand {
- public:
- void run() {
- intrusive_ptr<Testable> 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" ) );
- }
- };
+/** 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);
+ }
+};
- /** Dependencies of the expression. */
- class Dependencies {
- public:
- void run() {
- intrusive_ptr<Testable> testable = Testable::create();
+/** Adding an int and a Date produces a Date. */
+class IntDate : public TwoOperandBase {
+ BSONObj operand1() {
+ return BSON("" << 6);
+ }
+ BSONObj operand2() {
+ return BSON("" << Date_t::fromMillisSinceEpoch(123450));
+ }
+ BSONObj expectedResult() {
+ return BSON("" << Date_t::fromMillisSinceEpoch(123456));
+ }
+};
- // No arguments.
- assertDependencies( BSONArray(), testable );
+/** 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);
+ }
+};
- // Add a constant argument.
- testable->addOperand( ExpressionConstant::create( Value( 1 ) ) );
- assertDependencies( BSONArray(), testable );
+/** Adding a long and a double does not overflow. */
+class LongDoubleNoOverflow : public TwoOperandBase {
+ BSONObj operand1() {
+ return BSON("" << numeric_limits<long long>::max());
+ }
+ BSONObj operand2() {
+ return BSON("" << double(numeric_limits<long long>::max()));
+ }
+ BSONObj expectedResult() {
+ return BSON("" << numeric_limits<long long>::max() +
+ double(numeric_limits<long long>::max()));
+ }
+};
- // Add a field path argument.
- testable->addOperand( ExpressionFieldPath::create( "ab.c" ) );
- assertDependencies( BSON_ARRAY( "ab.c" ), testable );
+/** Adding an int and null. */
+class IntNull : public TwoOperandBase {
+ BSONObj operand1() {
+ return BSON("" << 1);
+ }
+ BSONObj operand2() {
+ return BSON("" << BSONNULL);
+ }
+ BSONObj expectedResult() {
+ return BSON("" << BSONNULL);
+ }
+};
- // 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>& expression ) {
- DepsTracker dependencies;
- expression->addDependencies( &dependencies );
- BSONArrayBuilder dependenciesBson;
- for( set<string>::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 = 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 = 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 = 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 = Testable::createFromOperands( spec );
- testable->assertContents( spec );
- intrusive_ptr<Expression> optimized = testable->optimize();
- ASSERT( testable != optimized );
- ASSERT_EQUALS( BSON( "$const" << BSON_ARRAY( 1 << 2 ) ),
- expressionToBson( optimized ) );
- }
- };
+/** 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 = Expression::parseOperand(specElement, vps);
+ ASSERT_EQUALS(constify(spec()), expressionToBson(expression));
+ ASSERT_EQUALS(BSON("" << expectedResult()),
+ toBson(expression->evaluate(fromBson(BSON("a" << 1)))));
+ intrusive_ptr<Expression> optimized = expression->optimize();
+ ASSERT_EQUALS(BSON("" << expectedResult()),
+ toBson(optimized->evaluate(fromBson(BSON("a" << 1)))));
+ }
- class NoFactoryOptimizeBase {
- public:
- virtual ~NoFactoryOptimizeBase() {
- }
- void run() {
- intrusive_ptr<Testable> testable = createTestable();
- // Without factory optimization, optimization will not produce a new expression.
- ASSERT( testable == testable->optimize() );
- }
- protected:
- virtual intrusive_ptr<Testable> createTestable() = 0;
- };
-
- /** A string constant prevents factory optimization. */
- class StringConstant : public NoFactoryOptimizeBase {
- intrusive_ptr<Testable> createTestable() {
- return Testable::createFromOperands( BSON_ARRAY( "abc" << "def" << "$path" ),
- true );
- }
- };
+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 = Expression::parseOperand(specElement, vps);
+ ASSERT_EQUALS(constify(spec()), expressionToBson(expression));
+ intrusive_ptr<Expression> optimized = expression->optimize();
+ ASSERT_EQUALS(expectedOptimized(), expressionToBson(optimized));
+ }
- /** A single (instead of multiple) constant prevents optimization. SERVER-6192 */
- class SingleConstant : public NoFactoryOptimizeBase {
- intrusive_ptr<Testable> createTestable() {
- return Testable::createFromOperands( BSON_ARRAY( 55 << "$path" ), true );
- }
- };
+protected:
+ virtual BSONObj spec() = 0;
+ virtual BSONObj expectedOptimized() = 0;
+};
- /** Factory optimization is not used without a factory. */
- class NoFactory : public NoFactoryOptimizeBase {
- intrusive_ptr<Testable> 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 =
- Testable::createFromOperands( BSON_ARRAY( 55 << 66 << "$path" ), true );
- intrusive_ptr<Expression> 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 =
- 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<Expression> 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<Testable> top =
- Testable::createFromOperands( BSON_ARRAY( 1 << 2 << "$a" ), true );
- intrusive_ptr<Testable> nested =
- Testable::createFromOperands( BSON_ARRAY( 3 << 4 << "$b" ), true );
- nested->addOperand
- ( Testable::createFromOperands( BSON_ARRAY( 5 << 6 << "$c" ), true ) );
- top->addOperand( nested );
- intrusive_ptr<Expression> 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<ExpressionObject>& expression,
- bool includePath = true ) const {
- vector<string> path;
- DepsTracker dependencies;
- expression->addDependencies( &dependencies, includePath ? &path : 0 );
- BSONArrayBuilder bab;
- for( set<string>::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<ExpressionObject> 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<ExpressionObject> _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 ) );
- }
- };
+class NoOptimizeBase : public OptimizeBase {
+ BSONObj expectedOptimized() {
+ return constify(spec());
+ }
+};
- /** 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> firstSubExpression = ExpressionObject::create();
- firstSubExpression->addField( mongo::FieldPath( "b" ),
- ExpressionConstant::create( Value( 6 ) ) );
- expression()->addField( mongo::FieldPath( "a" ), firstSubExpression );
- intrusive_ptr<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> bSubExpression = ExpressionObject::create();
- bSubExpression->addField( mongo::FieldPath( "d" ),
- ExpressionConstant::create( Value( 7 ) ) );
- intrusive_ptr<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> expression = ExpressionObject::createRoot();
- intrusive_ptr<ExpressionObject> 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<ExpressionObject> expression = ExpressionObject::createRoot();
- expression->addField( mongo::FieldPath( "a.b" ),
- ExpressionConstant::create( Value( 6 ) ) );
- intrusive_ptr<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> expression = ExpressionObject::createRoot();
- expression->addField( mongo::FieldPath( "a" ),
- ExpressionConstant::create( Value( 5 ) ) );
- intrusive_ptr<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> expression = ExpressionObject::createRoot();
- intrusive_ptr<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> expression = ExpressionObject::createRoot();
- // Add inclusion.
- expression->includePath( "a" );
- // Add non inclusion.
- intrusive_ptr<Expression> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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() ) );
- }
- };
+/** $and without operands. */
+class NoOperands : public ExpectedResultBase {
+ BSONObj spec() {
+ return BSON("$and" << BSONArray());
+ }
+ bool expectedResult() {
+ return true;
+ }
+};
- } // namespace Object
+/** $and passed 'true'. */
+class True : public ExpectedResultBase {
+ BSONObj spec() {
+ return BSON("$and" << BSON_ARRAY(true));
+ }
+ bool expectedResult() {
+ return true;
+ }
+};
- 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 = Expression::parseOperand(specElement, vps);
- ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) );
- ASSERT_EQUALS( BSON( "" << expectedResult() ),
- toBson( expression->evaluate( fromBson( BSON( "a" << 1 ) ) ) ) );
- intrusive_ptr<Expression> 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 = Expression::parseOperand(specElement, vps);
- ASSERT_EQUALS( constify( spec() ), expressionToBson( expression ) );
- intrusive_ptr<Expression> 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 =
- 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 = 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<mongo::Expression> 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<mongo::Expression> 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<Value> sortedSet = set.getArray();
- std::sort(sortedSet.begin(), sortedSet.end());
- return Value(sortedSet);
+/** $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<Expression> nested = ExpressionConstant::create(Value(5));
+ intrusive_ptr<Expression> expression = ExpressionCoerceToBool::create(nested);
+ ASSERT(expression->evaluate(Document()).getBool());
+ }
+};
+
+/** Nested expression coerced to false. */
+class EvaluateFalse {
+public:
+ void run() {
+ intrusive_ptr<Expression> nested = ExpressionConstant::create(Value(0));
+ intrusive_ptr<Expression> expression = ExpressionCoerceToBool::create(nested);
+ ASSERT(!expression->evaluate(Document()).getBool());
+ }
+};
+
+/** Dependencies forwarded from nested expression. */
+class Dependencies {
+public:
+ void run() {
+ intrusive_ptr<Expression> nested = ExpressionFieldPath::create("a.b");
+ intrusive_ptr<Expression> 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> 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>& expression) {
+ return BSON("field" << expression->serialize(false));
+ }
+};
+
+/** Output to BSONArray. */
+class AddToBsonArray {
+public:
+ void run() {
+ intrusive_ptr<Expression> 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>& 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 = Expression::parseOperand(specElement, vps);
+ intrusive_ptr<Expression> 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 = 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<Expression> 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 = 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> 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> expression = ExpressionConstant::parse(specElement, vps);
+ assertBinaryEqual(BSON(""
+ << "foo"),
+ toBson(expression->evaluate(Document())));
+ }
+};
+
+/** No optimization is performed. */
+class Optimize {
+public:
+ void run() {
+ intrusive_ptr<Expression> 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> 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> 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>& expression) {
+ return BSON("field" << expression->serialize(false));
+ }
+};
+
+/** Output to BSONArray. */
+class AddToBsonArray {
+public:
+ void run() {
+ intrusive_ptr<Expression> 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>& 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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<Value> 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<Testable> create(bool associativeAndCommutative = false) {
+ return new Testable(associativeAndCommutative);
+ }
+ static intrusive_ptr<ExpressionNary> factory() {
+ return new Testable(true);
+ }
+ static intrusive_ptr<Testable> createFromOperands(const BSONArray& operands,
+ bool haveFactory = false) {
+ VariablesIdGenerator idGenerator;
+ VariablesParseState vps(&idGenerator);
+ intrusive_ptr<Testable> 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));
+ }
- 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<Expression> 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<Value>& 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<Expression> 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<Value>() )
- );
+private:
+ Testable(bool isAssociativeAndCommutative)
+ : _isAssociativeAndCommutative(isAssociativeAndCommutative) {}
+ bool _isAssociativeAndCommutative;
+};
+
+/** Adding operands to the expression. */
+class AddOperand {
+public:
+ void run() {
+ intrusive_ptr<Testable> 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 = 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);
+ }
- }
- };
-
- 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<Value>() )
- );
+private:
+ void assertDependencies(const BSONArray& expectedDependencies,
+ const intrusive_ptr<Expression>& expression) {
+ DepsTracker dependencies;
+ expression->addDependencies(&dependencies);
+ BSONArrayBuilder dependenciesBson;
+ for (set<string>::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 = 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 = 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 = 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 = Testable::createFromOperands(spec);
+ testable->assertContents(spec);
+ intrusive_ptr<Expression> 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> testable = createTestable();
+ // Without factory optimization, optimization will not produce a new expression.
+ ASSERT(testable == testable->optimize());
+ }
- }
- };
-
- 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<Value>() )
- );
+protected:
+ virtual intrusive_ptr<Testable> createTestable() = 0;
+};
+
+/** A string constant prevents factory optimization. */
+class StringConstant : public NoFactoryOptimizeBase {
+ intrusive_ptr<Testable> createTestable() {
+ return Testable::createFromOperands(BSON_ARRAY("abc"
+ << "def"
+ << "$path"),
+ true);
+ }
+};
- }
- };
-
- 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) )
- );
+/** A single (instead of multiple) constant prevents optimization. SERVER-6192 */
+class SingleConstant : public NoFactoryOptimizeBase {
+ intrusive_ptr<Testable> createTestable() {
+ return Testable::createFromOperands(BSON_ARRAY(55 << "$path"), true);
+ }
+};
- }
- };
-
- 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) )
- );
+/** Factory optimization is not used without a factory. */
+class NoFactory : public NoFactoryOptimizeBase {
+ intrusive_ptr<Testable> 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 =
+ Testable::createFromOperands(BSON_ARRAY(55 << 66 << "$path"), true);
+ intrusive_ptr<Expression> 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 = 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<Expression> 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<Testable> top =
+ Testable::createFromOperands(BSON_ARRAY(1 << 2 << "$a"), true);
+ intrusive_ptr<Testable> nested =
+ Testable::createFromOperands(BSON_ARRAY(3 << 4 << "$b"), true);
+ nested->addOperand(Testable::createFromOperands(BSON_ARRAY(5 << 6 << "$c"), true));
+ top->addOperand(nested);
+ intrusive_ptr<Expression> 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<ExpressionObject>& expression,
+ bool includePath = true) const {
+ vector<string> path;
+ DepsTracker dependencies;
+ expression->addDependencies(&dependencies, includePath ? &path : 0);
+ BSONArrayBuilder bab;
+ for (set<string>::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());
+ }
- }
- };
-
- 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<Value>() )
- );
+protected:
+ intrusive_ptr<ExpressionObject> 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;
+ }
- }
- };
-
- 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<Value>() )
- );
+private:
+ intrusive_ptr<ExpressionObject> _expression;
+};
- }
- };
-
- 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<Value>()
- << "$setUnion" << DOC_ARRAY(1 << 2 << 4 << 8)
- << "$setDifference" << DOC_ARRAY(1 << 2))
- );
+/** 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();
+ }
+};
- }
- };
-
- 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))
- );
+/** 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);
+ }
+};
- }
- };
-
- 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")
- );
+/** 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);
+ }
+};
- }
- };
-
- 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")
- );
+/** 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);
+ }
+};
- }
- };
-
- class NoArg : public ExpectedResultBase {
- Document getSpec() {
- return DOC("input" << vector<Value>()
- << "expected" << DOC("$setIntersection" << vector<Value>()
- << "$setUnion" << vector<Value>() )
- << "error" << DOC_ARRAY("$setEquals"
- << "$setIsSubset"
- << "$setDifference")
- );
+/** 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));
+ }
+};
- }
- };
-
- 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")
- );
+/** 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));
+ }
+};
- }
- };
-
- class EmptyArg : public ExpectedResultBase {
- Document getSpec() {
- return DOC("input" << DOC_ARRAY( vector<Value>() )
- << "expected" << DOC("$setIntersection" << vector<Value>()
- << "$setUnion" << vector<Value>() )
- << "error" << DOC_ARRAY("$setEquals"
- << "$setIsSubset"
- << "$setDifference")
- );
+/** 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));
+ }
+};
- }
- };
-
- class LeftArgEmpty : public ExpectedResultBase {
- Document getSpec() {
- return DOC("input" << DOC_ARRAY( vector<Value>()
- << DOC_ARRAY(1 << 2) )
- << "expected" << DOC("$setIntersection" << vector<Value>()
- << "$setUnion" << DOC_ARRAY(1 << 2)
- << "$setIsSubset" << true
- << "$setEquals" << false
- << "$setDifference" << vector<Value>() )
- );
+/** 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));
+ }
+};
- }
- };
-
- class RightArgEmpty : public ExpectedResultBase {
- Document getSpec() {
- return DOC("input" << DOC_ARRAY( DOC_ARRAY(1 << 2)
- << vector<Value>() )
- << "expected" << DOC("$setIntersection" << vector<Value>()
- << "$setUnion" << DOC_ARRAY(1 << 2)
- << "$setIsSubset" << false
- << "$setEquals" << false
- << "$setDifference" << DOC_ARRAY(1 << 2) )
- );
+/** 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));
+ }
+};
- }
- };
-
- 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<Value>()
- << DOC_ARRAY(80.3 << "foo" << 11 << "yay") )
- << "expected" << DOC("$setIntersection" << vector<Value>()
- << "$setEquals" << false
- << "$setUnion" << DOC_ARRAY(3
- << 8
- << 11
- << 34
- << 80.3
- << "asdf"
- << "foo"
- << "yay") )
- << "error" << DOC_ARRAY("$setIsSubset"
- << "$setDifference")
- );
+/** 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;
+ }
+};
- }
- };
-
- 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")
- );
+/** Project a computed expression replacing an existing field. */
+class ComputedReplacement : public Computed {
+ virtual BSONObj source() {
+ return BSON("_id" << 0 << "a" << 99);
+ }
+};
- }
- };
- } // namespace Set
+/** 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;
+ }
+};
- namespace Strcasecmp {
+/** Project a computed expression replacing an existing field with Undefined. */
+class ComputedUndefinedReplacement : public ComputedUndefined {
+ virtual BSONObj source() {
+ return BSON("_id" << 0 << "a" << 99);
+ }
+};
- 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();
+/** 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> firstSubExpression = ExpressionObject::create();
+ firstSubExpression->addField(mongo::FieldPath("b"), ExpressionConstant::create(Value(6)));
+ expression()->addField(mongo::FieldPath("a"), firstSubExpression);
+ intrusive_ptr<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> bSubExpression = ExpressionObject::create();
+ bSubExpression->addField(mongo::FieldPath("d"), ExpressionConstant::create(Value(7)));
+ intrusive_ptr<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> expression = ExpressionObject::createRoot();
+ intrusive_ptr<ExpressionObject> 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<ExpressionObject> expression = ExpressionObject::createRoot();
+ expression->addField(mongo::FieldPath("a.b"), ExpressionConstant::create(Value(6)));
+ intrusive_ptr<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> expression = ExpressionObject::createRoot();
+ expression->addField(mongo::FieldPath("a"), ExpressionConstant::create(Value(5)));
+ intrusive_ptr<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> expression = ExpressionObject::createRoot();
+ intrusive_ptr<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> expression = ExpressionObject::createRoot();
+ // Add inclusion.
+ expression->includePath("a");
+ // Add non inclusion.
+ intrusive_ptr<Expression> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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<ExpressionObject> 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 = Expression::parseOperand(specElement, vps);
+ ASSERT_EQUALS(constify(spec()), expressionToBson(expression));
+ ASSERT_EQUALS(BSON("" << expectedResult()),
+ toBson(expression->evaluate(fromBson(BSON("a" << 1)))));
+ intrusive_ptr<Expression> 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 = Expression::parseOperand(specElement, vps);
+ ASSERT_EQUALS(constify(spec()), expressionToBson(expression));
+ intrusive_ptr<Expression> 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 =
+ 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 = 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<mongo::Expression> 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<mongo::Expression> 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<Value> 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);
- intrusive_ptr<Expression> 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() {
+ const intrusive_ptr<Expression> 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
}
- void run() {
- BSONObj specObj = BSON( "" << spec() );
- BSONElement specElement = specObj.firstElement();
+ }
+ if (!spec["error"].missing()) {
+ const vector<Value>& 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);
- intrusive_ptr<Expression> 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() ) );
+ ASSERT_THROWS({
+ // NOTE: parse and evaluatation failures are treated the same
+ const intrusive_ptr<Expression> expr =
+ Expression::parseExpression(obj.firstElement(), vps);
+ expr->evaluate(Document());
+ }, UserException);
}
- };
-
- /** 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();
+ }
+ }
+
+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<Value>()));
+ }
+};
+
+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<Value>()));
+ }
+};
+
+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<Value>()));
+ }
+};
+
+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<Value>()));
+ }
+};
+
+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<Value>()));
+ }
+};
+
+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<Value>()
+ << "$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<Value>() << "expected"
+ << DOC("$setIntersection" << vector<Value>() << "$setUnion" << vector<Value>())
+ << "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<Value>()) << "expected"
+ << DOC("$setIntersection" << vector<Value>() << "$setUnion" << vector<Value>())
+ << "error" << DOC_ARRAY("$setEquals"
+ << "$setIsSubset"
+ << "$setDifference"));
+ }
+};
+
+class LeftArgEmpty : public ExpectedResultBase {
+ Document getSpec() {
+ return DOC("input" << DOC_ARRAY(vector<Value>() << DOC_ARRAY(1 << 2)) << "expected"
+ << DOC("$setIntersection" << vector<Value>() << "$setUnion"
+ << DOC_ARRAY(1 << 2) << "$setIsSubset" << true
+ << "$setEquals" << false << "$setDifference"
+ << vector<Value>()));
+ }
+};
+
+class RightArgEmpty : public ExpectedResultBase {
+ Document getSpec() {
+ return DOC("input" << DOC_ARRAY(DOC_ARRAY(1 << 2) << vector<Value>()) << "expected"
+ << DOC("$setIntersection" << vector<Value>() << "$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<Value>()
+ << DOC_ARRAY(80.3 << "foo" << 11 << "yay")) << "expected"
+ << DOC("$setIntersection"
+ << vector<Value>() << "$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 = 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 = 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 = 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 = 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);
- intrusive_ptr<Expression> 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() {
+ const intrusive_ptr<Expression> 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
}
- void run() {
- BSONObj specObj = BSON( "" << spec() );
- BSONElement specElement = specObj.firstElement();
+ }
+ if (!spec["error"].missing()) {
+ const vector<Value>& 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);
- intrusive_ptr<Expression> 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<Expression> 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<Value>& 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<Expression> 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) );
+ ASSERT_THROWS({
+ // NOTE: parse and evaluatation failures are treated the same
+ const intrusive_ptr<Expression> expr =
+ Expression::parseExpression(obj.firstElement(), vps);
+ expr->evaluate(Document());
+ }, UserException);
}
- };
+ }
+ }
- class JustTrue : public ExpectedResultBase {
- Document getSpec() {
- return DOC("input" << DOC_ARRAY( DOC_ARRAY(true) )
- << "expected" << DOC("$allElementsTrue" << true
- << "$anyElementTrue" << true) );
- }
- };
+private:
+ virtual Document getSpec() = 0;
+};
- class OneTrueOneFalse : public ExpectedResultBase {
- Document getSpec() {
- return DOC("input" << DOC_ARRAY( DOC_ARRAY(true << false) )
- << "expected" << DOC("$allElementsTrue" << false
- << "$anyElementTrue" << true) );
- }
- };
+class JustFalse : public ExpectedResultBase {
+ Document getSpec() {
+ return DOC("input" << DOC_ARRAY(DOC_ARRAY(false)) << "expected"
+ << DOC("$allElementsTrue" << false << "$anyElementTrue" << false));
+ }
+};
- class Empty : public ExpectedResultBase {
- Document getSpec() {
- return DOC("input" << DOC_ARRAY( vector<Value>() )
- << "expected" << DOC("$allElementsTrue" << true
- << "$anyElementTrue" << false) );
- }
- };
+class JustTrue : public ExpectedResultBase {
+ Document getSpec() {
+ return DOC("input" << DOC_ARRAY(DOC_ARRAY(true)) << "expected"
+ << DOC("$allElementsTrue" << true << "$anyElementTrue" << true));
+ }
+};
- class TrueViaInt : public ExpectedResultBase {
- Document getSpec() {
- return DOC("input" << DOC_ARRAY( DOC_ARRAY(1) )
- << "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 FalseViaInt : public ExpectedResultBase {
- Document getSpec() {
- return DOC("input" << DOC_ARRAY( DOC_ARRAY(0) )
- << "expected" << DOC("$allElementsTrue" << false
- << "$anyElementTrue" << false) );
- }
- };
+class Empty : public ExpectedResultBase {
+ Document getSpec() {
+ return DOC("input" << DOC_ARRAY(vector<Value>()) << "expected"
+ << DOC("$allElementsTrue" << true << "$anyElementTrue" << false));
+ }
+};
- class Null : public ExpectedResultBase {
- Document getSpec() {
- return DOC("input" << DOC_ARRAY(BSONNULL)
- << "error" << DOC_ARRAY("$allElementsTrue"
- << "$anyElementTrue") );
- }
- };
+class TrueViaInt : public ExpectedResultBase {
+ Document getSpec() {
+ return DOC("input" << DOC_ARRAY(DOC_ARRAY(1)) << "expected"
+ << DOC("$allElementsTrue" << true << "$anyElementTrue" << true));
+ }
+};
- } // namespace AllAnyElements
+class FalseViaInt : public ExpectedResultBase {
+ Document getSpec() {
+ return DOC("input" << DOC_ARRAY(DOC_ARRAY(0)) << "expected"
+ << DOC("$allElementsTrue" << false << "$anyElementTrue" << false));
+ }
+};
- class All : public Suite {
- public:
- All() : Suite( "expression" ) {
- }
- void setupTests() {
- add<Add::NullDocument>();
- add<Add::NoOperands>();
- add<Add::Date>();
- add<Add::String>();
- add<Add::Bool>();
- add<Add::Int>();
- add<Add::Long>();
- add<Add::Double>();
- add<Add::Null>();
- add<Add::Undefined>();
- add<Add::IntInt>();
- add<Add::IntIntNoOverflow>();
- add<Add::IntLong>();
- add<Add::IntLongOverflow>();
- add<Add::IntDouble>();
- add<Add::IntDate>();
- add<Add::LongDouble>();
- add<Add::LongDoubleNoOverflow>();
- add<Add::IntNull>();
- add<Add::LongUndefined>();
-
- add<And::NoOperands>();
- add<And::True>();
- add<And::False>();
- add<And::TrueTrue>();
- add<And::TrueFalse>();
- add<And::FalseTrue>();
- add<And::FalseFalse>();
- add<And::TrueTrueTrue>();
- add<And::TrueTrueFalse>();
- add<And::TrueTrueFalse>();
- add<And::ZeroOne>();
- add<And::OneTwo>();
- add<And::FieldPath>();
- add<And::OptimizeConstantExpression>();
- add<And::NonConstant>();
- add<And::ConstantNonConstantTrue>();
- add<And::ConstantNonConstantFalse>();
- add<And::NonConstantOne>();
- add<And::NonConstantZero>();
- add<And::NonConstantNonConstantOne>();
- add<And::NonConstantNonConstantZero>();
- add<And::ZeroOneNonConstant>();
- add<And::OneOneNonConstant>();
- add<And::Nested>();
- add<And::NestedZero>();
-
- add<CoerceToBool::EvaluateTrue>();
- add<CoerceToBool::EvaluateFalse>();
- add<CoerceToBool::Dependencies>();
- add<CoerceToBool::AddToBsonObj>();
- add<CoerceToBool::AddToBsonArray>();
-
- add<Compare::EqLt>();
- add<Compare::EqEq>();
- add<Compare::EqGt>();
- add<Compare::NeLt>();
- add<Compare::NeEq>();
- add<Compare::NeGt>();
- add<Compare::GtLt>();
- add<Compare::GtEq>();
- add<Compare::GtGt>();
- add<Compare::GteLt>();
- add<Compare::GteEq>();
- add<Compare::GteGt>();
- add<Compare::LtLt>();
- add<Compare::LtEq>();
- add<Compare::LtGt>();
- add<Compare::LteLt>();
- add<Compare::LteEq>();
- add<Compare::LteGt>();
- add<Compare::CmpLt>();
- add<Compare::CmpEq>();
- add<Compare::CmpGt>();
- add<Compare::CmpBracketed>();
- add<Compare::ZeroOperands>();
- add<Compare::OneOperand>();
- add<Compare::ThreeOperands>();
- add<Compare::IncompatibleTypes>();
- add<Compare::OptimizeConstants>();
- add<Compare::NoOptimizeCmp>();
- add<Compare::NoOptimizeNe>();
- add<Compare::NoOptimizeNoConstant>();
- add<Compare::NoOptimizeWithoutFieldPath>();
- add<Compare::NoOptimizeWithoutFieldPathReverse>();
- add<Compare::OptimizeEq>();
- add<Compare::OptimizeEqReverse>();
- add<Compare::OptimizeLt>();
- add<Compare::OptimizeLtReverse>();
- add<Compare::OptimizeLte>();
- add<Compare::OptimizeLteReverse>();
- add<Compare::OptimizeGt>();
- add<Compare::OptimizeGtReverse>();
- add<Compare::OptimizeGte>();
- add<Compare::OptimizeGteReverse>();
-
- add<Constant::Create>();
- add<Constant::CreateFromBsonElement>();
- add<Constant::Optimize>();
- add<Constant::Dependencies>();
- add<Constant::AddToBsonObj>();
- add<Constant::AddToBsonArray>();
-
- add<FieldPath::Invalid>();
- add<FieldPath::Optimize>();
- add<FieldPath::Dependencies>();
- add<FieldPath::Missing>();
- add<FieldPath::Present>();
- add<FieldPath::NestedBelowNull>();
- add<FieldPath::NestedBelowUndefined>();
- add<FieldPath::NestedBelowMissing>();
- add<FieldPath::NestedBelowInt>();
- add<FieldPath::NestedValue>();
- add<FieldPath::NestedBelowEmptyObject>();
- add<FieldPath::NestedBelowEmptyArray>();
- add<FieldPath::NestedBelowEmptyArray>();
- add<FieldPath::NestedBelowArrayWithNull>();
- add<FieldPath::NestedBelowArrayWithUndefined>();
- add<FieldPath::NestedBelowArrayWithInt>();
- add<FieldPath::NestedWithinArray>();
- add<FieldPath::MultipleArrayValues>();
- add<FieldPath::ExpandNestedArrays>();
- add<FieldPath::AddToBsonObj>();
- add<FieldPath::AddToBsonArray>();
-
- add<Nary::AddOperand>();
- add<Nary::Dependencies>();
- add<Nary::AddToBsonObj>();
- add<Nary::AddToBsonArray>();
- add<Nary::OptimizeOneOperand>();
- add<Nary::EvaluateAllConstantOperands>();
- add<Nary::StringConstant>();
- add<Nary::SingleConstant>();
- add<Nary::NoFactory>();
- add<Nary::FactoryOptimize>();
- add<Nary::FlattenOptimize>();
- add<Nary::FlattenThreeLayers>();
-
- add<Object::Empty>();
- add<Object::Include>();
- add<Object::MissingInclude>();
- add<Object::IncludeId>();
- add<Object::ExcludeId>();
- add<Object::SourceOrder>();
- add<Object::IncludeNested>();
- add<Object::IncludeTwoNested>();
- add<Object::IncludeTwoParentNested>();
- add<Object::IncludeMissingNested>();
- add<Object::IncludeNestedWithinNonObject>();
- add<Object::IncludeArrayNested>();
- add<Object::ExcludeNonRootId>();
- add<Object::Computed>();
- add<Object::ComputedReplacement>();
- add<Object::ComputedUndefined>();
- add<Object::ComputedUndefinedReplacement>();
- add<Object::ComputedNull>();
- add<Object::ComputedNested>();
- add<Object::ComputedFieldPath>();
- add<Object::ComputedNestedFieldPath>();
- add<Object::EmptyNewSubobject>();
- add<Object::NonEmptyNewSubobject>();
- add<Object::AdjacentNestedComputedFields>();
- add<Object::AdjacentDottedAndNestedComputedFields>();
- add<Object::AdjacentNestedAndDottedComputedFields>();
- add<Object::AdjacentDottedComputedFields>();
- add<Object::AdjacentNestedOrdering>();
- add<Object::MultipleNestedFields>();
- add<Object::ConflictingExpressionFields>();
- add<Object::ConflictingInclusionExpressionFields>();
- add<Object::ConflictingExpressionInclusionFields>();
- add<Object::ConflictingObjectConstantExpressionFields>();
- add<Object::ConflictingConstantObjectExpressionFields>();
- add<Object::ConflictingNestedFields>();
- add<Object::ConflictingFieldAndSubfield>();
- add<Object::ConflictingFieldAndNestedField>();
- add<Object::ConflictingSubfieldAndField>();
- add<Object::ConflictingNestedFieldAndField>();
- add<Object::NonInclusionDependencies>();
- add<Object::InclusionDependencies>();
- add<Object::Optimize>();
- add<Object::AddToBsonObj>();
- add<Object::AddToBsonObjRequireExpression>();
- add<Object::AddToBsonArray>();
- add<Object::Evaluate>();
-
- add<Or::NoOperands>();
- add<Or::True>();
- add<Or::False>();
- add<Or::TrueTrue>();
- add<Or::TrueFalse>();
- add<Or::FalseTrue>();
- add<Or::FalseFalse>();
- add<Or::FalseFalseFalse>();
- add<Or::FalseFalseTrue>();
- add<Or::ZeroOne>();
- add<Or::ZeroFalse>();
- add<Or::FieldPath>();
- add<Or::OptimizeConstantExpression>();
- add<Or::NonConstant>();
- add<Or::ConstantNonConstantTrue>();
- add<Or::ConstantNonConstantFalse>();
- add<Or::NonConstantOne>();
- add<Or::NonConstantZero>();
- add<Or::NonConstantNonConstantOne>();
- add<Or::NonConstantNonConstantZero>();
- add<Or::ZeroOneNonConstant>();
- add<Or::ZeroZeroNonConstant>();
- add<Or::Nested>();
- add<Or::NestedOne>();
-
- add<Parse::Object::NonObject>();
- add<Parse::Object::Empty>();
- add<Parse::Object::Operator>();
- add<Parse::Object::InvalidOperator>();
- add<Parse::Object::TwoOperators>();
- add<Parse::Object::OperatorLaterField>();
- add<Parse::Object::OperatorAndOtherField>();
- add<Parse::Object::OperatorTopLevel>();
- add<Parse::Object::Dotted>();
- add<Parse::Object::DottedTopLevel>();
- add<Parse::Object::Nested>();
- add<Parse::Object::NestedParseError>();
- add<Parse::Object::FieldPath>();
- add<Parse::Object::InvalidFieldPath>();
- add<Parse::Object::NonFieldPathString>();
- add<Parse::Object::DisallowedInclusion>();
- add<Parse::Object::InclusionBool>();
- add<Parse::Object::InclusionDouble>();
- add<Parse::Object::InclusionInt>();
- add<Parse::Object::InclusionLong>();
- add<Parse::Object::NestedInclusion>();
- add<Parse::Object::ExcludeId>();
- add<Parse::Object::ExcludeNonId>();
- add<Parse::Object::ExcludeIdNotTopLevel>();
- add<Parse::Object::InvalidType>();
- add<Parse::Expression::Const>();
- add<Parse::Expression::InvalidName>();
- add<Parse::Expression::RequiredArrayMissing>();
- add<Parse::Expression::IncorrectOperandCount>();
- add<Parse::Expression::CorrectOperandCount>();
- add<Parse::Expression::ZeroOperands>();
- add<Parse::Expression::OneOperand>();
- add<Parse::Expression::TwoOperands>();
- add<Parse::Expression::SingletonOperandVariable>();
- add<Parse::Expression::SingletonOperandFixed>();
- add<Parse::Expression::ObjectSingleton>();
- add<Parse::Expression::ObjectOperand>();
- add<Parse::Operand::FieldPath>();
- add<Parse::Operand::NonFieldPathString>();
- add<Parse::Operand::Object>();
- add<Parse::Operand::InclusionObject>();
- add<Parse::Operand::Constant>();
-
- add<Strcasecmp::NullBegin>();
- add<Strcasecmp::NullEnd>();
- add<Strcasecmp::NullMiddleLt>();
- add<Strcasecmp::NullMiddleEq>();
- add<Strcasecmp::NullMiddleGt>();
-
- add<Substr::FullNull>();
- add<Substr::BeginAtNull>();
- add<Substr::EndAtNull>();
- add<Substr::DropBeginningNull>();
- add<Substr::DropEndingNull>();
-
- add<ToLower::NullBegin>();
- add<ToLower::NullMiddle>();
- add<ToLower::NullEnd>();
-
- add<ToUpper::NullBegin>();
- add<ToUpper::NullMiddle>();
- add<ToUpper::NullEnd>();
-
- add<Set::Same>();
- add<Set::Redundant>();
- add<Set::DoubleRedundant>();
- add<Set::Sub>();
- add<Set::Super>();
- add<Set::SameBackwards>();
- add<Set::NoOverlap>();
- add<Set::Overlap>();
- add<Set::FirstNull>();
- add<Set::LastNull>();
- add<Set::NoArg>();
- add<Set::OneArg>();
- add<Set::EmptyArg>();
- add<Set::LeftArgEmpty>();
- add<Set::RightArgEmpty>();
- add<Set::ManyArgs>();
- add<Set::ManyArgsEqual>();
-
- add<AllAnyElements::JustFalse>();
- add<AllAnyElements::JustTrue>();
- add<AllAnyElements::OneTrueOneFalse>();
- add<AllAnyElements::Empty>();
- add<AllAnyElements::TrueViaInt>();
- add<AllAnyElements::FalseViaInt>();
- add<AllAnyElements::Null>();
- }
- };
+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::NullDocument>();
+ add<Add::NoOperands>();
+ add<Add::Date>();
+ add<Add::String>();
+ add<Add::Bool>();
+ add<Add::Int>();
+ add<Add::Long>();
+ add<Add::Double>();
+ add<Add::Null>();
+ add<Add::Undefined>();
+ add<Add::IntInt>();
+ add<Add::IntIntNoOverflow>();
+ add<Add::IntLong>();
+ add<Add::IntLongOverflow>();
+ add<Add::IntDouble>();
+ add<Add::IntDate>();
+ add<Add::LongDouble>();
+ add<Add::LongDoubleNoOverflow>();
+ add<Add::IntNull>();
+ add<Add::LongUndefined>();
+
+ add<And::NoOperands>();
+ add<And::True>();
+ add<And::False>();
+ add<And::TrueTrue>();
+ add<And::TrueFalse>();
+ add<And::FalseTrue>();
+ add<And::FalseFalse>();
+ add<And::TrueTrueTrue>();
+ add<And::TrueTrueFalse>();
+ add<And::TrueTrueFalse>();
+ add<And::ZeroOne>();
+ add<And::OneTwo>();
+ add<And::FieldPath>();
+ add<And::OptimizeConstantExpression>();
+ add<And::NonConstant>();
+ add<And::ConstantNonConstantTrue>();
+ add<And::ConstantNonConstantFalse>();
+ add<And::NonConstantOne>();
+ add<And::NonConstantZero>();
+ add<And::NonConstantNonConstantOne>();
+ add<And::NonConstantNonConstantZero>();
+ add<And::ZeroOneNonConstant>();
+ add<And::OneOneNonConstant>();
+ add<And::Nested>();
+ add<And::NestedZero>();
+
+ add<CoerceToBool::EvaluateTrue>();
+ add<CoerceToBool::EvaluateFalse>();
+ add<CoerceToBool::Dependencies>();
+ add<CoerceToBool::AddToBsonObj>();
+ add<CoerceToBool::AddToBsonArray>();
+
+ add<Compare::EqLt>();
+ add<Compare::EqEq>();
+ add<Compare::EqGt>();
+ add<Compare::NeLt>();
+ add<Compare::NeEq>();
+ add<Compare::NeGt>();
+ add<Compare::GtLt>();
+ add<Compare::GtEq>();
+ add<Compare::GtGt>();
+ add<Compare::GteLt>();
+ add<Compare::GteEq>();
+ add<Compare::GteGt>();
+ add<Compare::LtLt>();
+ add<Compare::LtEq>();
+ add<Compare::LtGt>();
+ add<Compare::LteLt>();
+ add<Compare::LteEq>();
+ add<Compare::LteGt>();
+ add<Compare::CmpLt>();
+ add<Compare::CmpEq>();
+ add<Compare::CmpGt>();
+ add<Compare::CmpBracketed>();
+ add<Compare::ZeroOperands>();
+ add<Compare::OneOperand>();
+ add<Compare::ThreeOperands>();
+ add<Compare::IncompatibleTypes>();
+ add<Compare::OptimizeConstants>();
+ add<Compare::NoOptimizeCmp>();
+ add<Compare::NoOptimizeNe>();
+ add<Compare::NoOptimizeNoConstant>();
+ add<Compare::NoOptimizeWithoutFieldPath>();
+ add<Compare::NoOptimizeWithoutFieldPathReverse>();
+ add<Compare::OptimizeEq>();
+ add<Compare::OptimizeEqReverse>();
+ add<Compare::OptimizeLt>();
+ add<Compare::OptimizeLtReverse>();
+ add<Compare::OptimizeLte>();
+ add<Compare::OptimizeLteReverse>();
+ add<Compare::OptimizeGt>();
+ add<Compare::OptimizeGtReverse>();
+ add<Compare::OptimizeGte>();
+ add<Compare::OptimizeGteReverse>();
+
+ add<Constant::Create>();
+ add<Constant::CreateFromBsonElement>();
+ add<Constant::Optimize>();
+ add<Constant::Dependencies>();
+ add<Constant::AddToBsonObj>();
+ add<Constant::AddToBsonArray>();
+
+ add<FieldPath::Invalid>();
+ add<FieldPath::Optimize>();
+ add<FieldPath::Dependencies>();
+ add<FieldPath::Missing>();
+ add<FieldPath::Present>();
+ add<FieldPath::NestedBelowNull>();
+ add<FieldPath::NestedBelowUndefined>();
+ add<FieldPath::NestedBelowMissing>();
+ add<FieldPath::NestedBelowInt>();
+ add<FieldPath::NestedValue>();
+ add<FieldPath::NestedBelowEmptyObject>();
+ add<FieldPath::NestedBelowEmptyArray>();
+ add<FieldPath::NestedBelowEmptyArray>();
+ add<FieldPath::NestedBelowArrayWithNull>();
+ add<FieldPath::NestedBelowArrayWithUndefined>();
+ add<FieldPath::NestedBelowArrayWithInt>();
+ add<FieldPath::NestedWithinArray>();
+ add<FieldPath::MultipleArrayValues>();
+ add<FieldPath::ExpandNestedArrays>();
+ add<FieldPath::AddToBsonObj>();
+ add<FieldPath::AddToBsonArray>();
+
+ add<Nary::AddOperand>();
+ add<Nary::Dependencies>();
+ add<Nary::AddToBsonObj>();
+ add<Nary::AddToBsonArray>();
+ add<Nary::OptimizeOneOperand>();
+ add<Nary::EvaluateAllConstantOperands>();
+ add<Nary::StringConstant>();
+ add<Nary::SingleConstant>();
+ add<Nary::NoFactory>();
+ add<Nary::FactoryOptimize>();
+ add<Nary::FlattenOptimize>();
+ add<Nary::FlattenThreeLayers>();
+
+ add<Object::Empty>();
+ add<Object::Include>();
+ add<Object::MissingInclude>();
+ add<Object::IncludeId>();
+ add<Object::ExcludeId>();
+ add<Object::SourceOrder>();
+ add<Object::IncludeNested>();
+ add<Object::IncludeTwoNested>();
+ add<Object::IncludeTwoParentNested>();
+ add<Object::IncludeMissingNested>();
+ add<Object::IncludeNestedWithinNonObject>();
+ add<Object::IncludeArrayNested>();
+ add<Object::ExcludeNonRootId>();
+ add<Object::Computed>();
+ add<Object::ComputedReplacement>();
+ add<Object::ComputedUndefined>();
+ add<Object::ComputedUndefinedReplacement>();
+ add<Object::ComputedNull>();
+ add<Object::ComputedNested>();
+ add<Object::ComputedFieldPath>();
+ add<Object::ComputedNestedFieldPath>();
+ add<Object::EmptyNewSubobject>();
+ add<Object::NonEmptyNewSubobject>();
+ add<Object::AdjacentNestedComputedFields>();
+ add<Object::AdjacentDottedAndNestedComputedFields>();
+ add<Object::AdjacentNestedAndDottedComputedFields>();
+ add<Object::AdjacentDottedComputedFields>();
+ add<Object::AdjacentNestedOrdering>();
+ add<Object::MultipleNestedFields>();
+ add<Object::ConflictingExpressionFields>();
+ add<Object::ConflictingInclusionExpressionFields>();
+ add<Object::ConflictingExpressionInclusionFields>();
+ add<Object::ConflictingObjectConstantExpressionFields>();
+ add<Object::ConflictingConstantObjectExpressionFields>();
+ add<Object::ConflictingNestedFields>();
+ add<Object::ConflictingFieldAndSubfield>();
+ add<Object::ConflictingFieldAndNestedField>();
+ add<Object::ConflictingSubfieldAndField>();
+ add<Object::ConflictingNestedFieldAndField>();
+ add<Object::NonInclusionDependencies>();
+ add<Object::InclusionDependencies>();
+ add<Object::Optimize>();
+ add<Object::AddToBsonObj>();
+ add<Object::AddToBsonObjRequireExpression>();
+ add<Object::AddToBsonArray>();
+ add<Object::Evaluate>();
+
+ add<Or::NoOperands>();
+ add<Or::True>();
+ add<Or::False>();
+ add<Or::TrueTrue>();
+ add<Or::TrueFalse>();
+ add<Or::FalseTrue>();
+ add<Or::FalseFalse>();
+ add<Or::FalseFalseFalse>();
+ add<Or::FalseFalseTrue>();
+ add<Or::ZeroOne>();
+ add<Or::ZeroFalse>();
+ add<Or::FieldPath>();
+ add<Or::OptimizeConstantExpression>();
+ add<Or::NonConstant>();
+ add<Or::ConstantNonConstantTrue>();
+ add<Or::ConstantNonConstantFalse>();
+ add<Or::NonConstantOne>();
+ add<Or::NonConstantZero>();
+ add<Or::NonConstantNonConstantOne>();
+ add<Or::NonConstantNonConstantZero>();
+ add<Or::ZeroOneNonConstant>();
+ add<Or::ZeroZeroNonConstant>();
+ add<Or::Nested>();
+ add<Or::NestedOne>();
+
+ add<Parse::Object::NonObject>();
+ add<Parse::Object::Empty>();
+ add<Parse::Object::Operator>();
+ add<Parse::Object::InvalidOperator>();
+ add<Parse::Object::TwoOperators>();
+ add<Parse::Object::OperatorLaterField>();
+ add<Parse::Object::OperatorAndOtherField>();
+ add<Parse::Object::OperatorTopLevel>();
+ add<Parse::Object::Dotted>();
+ add<Parse::Object::DottedTopLevel>();
+ add<Parse::Object::Nested>();
+ add<Parse::Object::NestedParseError>();
+ add<Parse::Object::FieldPath>();
+ add<Parse::Object::InvalidFieldPath>();
+ add<Parse::Object::NonFieldPathString>();
+ add<Parse::Object::DisallowedInclusion>();
+ add<Parse::Object::InclusionBool>();
+ add<Parse::Object::InclusionDouble>();
+ add<Parse::Object::InclusionInt>();
+ add<Parse::Object::InclusionLong>();
+ add<Parse::Object::NestedInclusion>();
+ add<Parse::Object::ExcludeId>();
+ add<Parse::Object::ExcludeNonId>();
+ add<Parse::Object::ExcludeIdNotTopLevel>();
+ add<Parse::Object::InvalidType>();
+ add<Parse::Expression::Const>();
+ add<Parse::Expression::InvalidName>();
+ add<Parse::Expression::RequiredArrayMissing>();
+ add<Parse::Expression::IncorrectOperandCount>();
+ add<Parse::Expression::CorrectOperandCount>();
+ add<Parse::Expression::ZeroOperands>();
+ add<Parse::Expression::OneOperand>();
+ add<Parse::Expression::TwoOperands>();
+ add<Parse::Expression::SingletonOperandVariable>();
+ add<Parse::Expression::SingletonOperandFixed>();
+ add<Parse::Expression::ObjectSingleton>();
+ add<Parse::Expression::ObjectOperand>();
+ add<Parse::Operand::FieldPath>();
+ add<Parse::Operand::NonFieldPathString>();
+ add<Parse::Operand::Object>();
+ add<Parse::Operand::InclusionObject>();
+ add<Parse::Operand::Constant>();
+
+ add<Strcasecmp::NullBegin>();
+ add<Strcasecmp::NullEnd>();
+ add<Strcasecmp::NullMiddleLt>();
+ add<Strcasecmp::NullMiddleEq>();
+ add<Strcasecmp::NullMiddleGt>();
+
+ add<Substr::FullNull>();
+ add<Substr::BeginAtNull>();
+ add<Substr::EndAtNull>();
+ add<Substr::DropBeginningNull>();
+ add<Substr::DropEndingNull>();
+
+ add<ToLower::NullBegin>();
+ add<ToLower::NullMiddle>();
+ add<ToLower::NullEnd>();
+
+ add<ToUpper::NullBegin>();
+ add<ToUpper::NullMiddle>();
+ add<ToUpper::NullEnd>();
+
+ add<Set::Same>();
+ add<Set::Redundant>();
+ add<Set::DoubleRedundant>();
+ add<Set::Sub>();
+ add<Set::Super>();
+ add<Set::SameBackwards>();
+ add<Set::NoOverlap>();
+ add<Set::Overlap>();
+ add<Set::FirstNull>();
+ add<Set::LastNull>();
+ add<Set::NoArg>();
+ add<Set::OneArg>();
+ add<Set::EmptyArg>();
+ add<Set::LeftArgEmpty>();
+ add<Set::RightArgEmpty>();
+ add<Set::ManyArgs>();
+ add<Set::ManyArgsEqual>();
+
+ add<AllAnyElements::JustFalse>();
+ add<AllAnyElements::JustTrue>();
+ add<AllAnyElements::OneTrueOneFalse>();
+ add<AllAnyElements::Empty>();
+ add<AllAnyElements::TrueViaInt>();
+ add<AllAnyElements::FalseViaInt>();
+ add<AllAnyElements::Null>();
+ }
+};
- SuiteInstance<All> myall;
+SuiteInstance<All> myall;
-} // namespace ExpressionTests
+} // namespace ExpressionTests