summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/SConscript17
-rw-r--r--src/mongo/db/matcher.h3
-rw-r--r--src/mongo/db/matcher/expression.cpp10
-rw-r--r--src/mongo/db/matcher/expression.h59
-rw-r--r--src/mongo/db/matcher/expression_array.cpp54
-rw-r--r--src/mongo/db/matcher/expression_array.h25
-rw-r--r--src/mongo/db/matcher/expression_array_test.cpp135
-rw-r--r--src/mongo/db/matcher/expression_geo.cpp66
-rw-r--r--src/mongo/db/matcher/expression_geo.h47
-rw-r--r--src/mongo/db/matcher/expression_geo_test.cpp48
-rw-r--r--src/mongo/db/matcher/expression_leaf.cpp144
-rw-r--r--src/mongo/db/matcher/expression_leaf.h141
-rw-r--r--src/mongo/db/matcher/expression_leaf_test.cpp323
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp108
-rw-r--r--src/mongo/db/matcher/expression_parser.h24
-rw-r--r--src/mongo/db/matcher/expression_parser_geo.cpp47
-rw-r--r--src/mongo/db/matcher/expression_parser_geo_test.cpp43
-rw-r--r--src/mongo/db/matcher/expression_parser_test.cpp6
-rw-r--r--src/mongo/db/matcher/expression_parser_tree.cpp2
-rw-r--r--src/mongo/db/matcher/expression_test.cpp20
-rw-r--r--src/mongo/db/matcher/expression_tree.cpp48
-rw-r--r--src/mongo/db/matcher/expression_tree.h18
-rw-r--r--src/mongo/db/matcher/expression_tree_test.cpp112
-rw-r--r--src/mongo/db/matcher/expression_where.cpp172
-rw-r--r--src/mongo/db/matcher/matcher.cpp130
-rw-r--r--src/mongo/db/matcher/matcher.h33
-rw-r--r--src/mongo/dbtests/matchertests.cpp108
27 files changed, 1649 insertions, 294 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index e3ea7d89dc3..3f867df752d 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -126,6 +126,16 @@ env.StaticLibrary('expressions',
'$BUILD_DIR/third_party/pcrecpp'
] )
+env.StaticLibrary('expressions_geo',
+ ['db/matcher/expression_geo.cpp',
+ 'db/matcher/expression_parser_geo.cpp'],
+ LIBDEPS=['expressions','geoquery','geoparser'] )
+
+env.StaticLibrary('expressions_where',
+ ['db/matcher/expression_where.cpp'],
+ LIBDEPS=['expressions'] )
+
+
env.CppUnitTest('expression_test',
['db/matcher/expression_test.cpp',
'db/matcher/expression_leaf_test.cpp',
@@ -133,6 +143,11 @@ env.CppUnitTest('expression_test',
'db/matcher/expression_array_test.cpp'],
LIBDEPS=['expressions'] )
+env.CppUnitTest('expression_geo_test',
+ ['db/matcher/expression_geo_test.cpp',
+ 'db/matcher/expression_parser_geo_test.cpp'],
+ LIBDEPS=['expressions_geo'] )
+
env.CppUnitTest('expression_parser_test',
['db/matcher/expression_parser_test.cpp',
'db/matcher/expression_parser_array_test.cpp',
@@ -316,6 +331,8 @@ env.StaticLibrary("coredb", [
'geoparser',
'geoquery',
'expressions',
+ 'expressions_geo',
+ 'expressions_where',
'$BUILD_DIR/mongo/foundation'])
coreServerFiles = [ "db/client_basic.cpp",
diff --git a/src/mongo/db/matcher.h b/src/mongo/db/matcher.h
index a0a383a0004..8c4cf3ab016 100644
--- a/src/mongo/db/matcher.h
+++ b/src/mongo/db/matcher.h
@@ -206,13 +206,14 @@ namespace mongo {
const BSONObj *getQuery() const { return &_jsobj; };
- private:
/**
* Generate a matcher for the provided index key format using the
* provided full doc matcher.
*/
Matcher( const Matcher &docMatcher, const BSONObj &constrainIndexKey );
+ private:
+
void addBasic(const BSONElement &e, int c, bool isNot) {
// TODO May want to selectively ignore these element types based on op type.
if ( e.type() == MinKey || e.type() == MaxKey )
diff --git a/src/mongo/db/matcher/expression.cpp b/src/mongo/db/matcher/expression.cpp
index 4b9b9f020ba..e2372aa14b6 100644
--- a/src/mongo/db/matcher/expression.cpp
+++ b/src/mongo/db/matcher/expression.cpp
@@ -25,6 +25,11 @@
namespace mongo {
+ MatchExpression::MatchExpression( MatchCategory category, MatchType type )
+ : _matchCategory( category ), _matchType( type ){
+ }
+
+
string MatchExpression::toString() const {
StringBuilder buf;
debugString( buf, 0 );
@@ -36,6 +41,11 @@ namespace mongo {
debug << " ";
}
+ void AtomicMatchExpression::debugString( StringBuilder& debug, int level ) const {
+ _debugAddSpace( debug, level );
+ debug << "$atomic\n";
+ }
+
}
diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h
index c91f44c1a33..ed4a7f5bf5b 100644
--- a/src/mongo/db/matcher/expression.h
+++ b/src/mongo/db/matcher/expression.h
@@ -29,9 +29,29 @@ namespace mongo {
class MatchExpression {
MONGO_DISALLOW_COPYING( MatchExpression );
-
public:
- MatchExpression(){}
+ enum MatchType {
+ // tree types
+ AND, OR, NOR, NOT,
+
+ // array types
+ ALL, ELEM_MATCH_OBJECT, ELEM_MATCH_VALUE, SIZE,
+
+ // leaf types
+ LTE, LT, EQ, GT, GTE, NE, REGEX, MOD, EXISTS, IN, NIN,
+
+ // special types
+ TYPE_OPERATOR, GEO, WHERE,
+
+ // things that maybe shouldn't even be nodes
+ ATOMIC
+ };
+
+ enum MatchCategory {
+ LEAF, ARRAY, TREE, TYPE_CATEGORY, SPECIAL
+ };
+
+ MatchExpression( MatchCategory category, MatchType type );
virtual ~MatchExpression(){}
/**
@@ -46,10 +66,45 @@ namespace mongo {
*/
virtual bool matchesSingleElement( const BSONElement& e ) const = 0;
+ virtual size_t numChildren() const { return 0; }
+ virtual const MatchExpression* getChild( size_t i ) const { return NULL; }
+
+ MatchType matchType() const { return _matchType; }
+ MatchCategory matchCategory() const { return _matchCategory; }
+
virtual string toString() const;
virtual void debugString( StringBuilder& debug, int level = 0 ) const = 0;
+
+ virtual bool equivalent( const MatchExpression* other ) const = 0;
protected:
void _debugAddSpace( StringBuilder& debug, int level ) const;
+
+ private:
+ MatchCategory _matchCategory;
+ MatchType _matchType;
};
+ /**
+ * this isn't really an expression, but a hint to other things
+ * not sure where to put it in the end
+ */
+ class AtomicMatchExpression : public MatchExpression {
+ public:
+ AtomicMatchExpression() : MatchExpression( SPECIAL, ATOMIC ){}
+
+ virtual bool matches( const BSONObj& doc, MatchDetails* details = 0 ) const {
+ return true;
+ }
+
+ virtual bool matchesSingleElement( const BSONElement& e ) const {
+ return true;
+ }
+
+ virtual void debugString( StringBuilder& debug, int level = 0 ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const {
+ return other->matchType() == ATOMIC;
+ }
+
+ };
}
diff --git a/src/mongo/db/matcher/expression_array.cpp b/src/mongo/db/matcher/expression_array.cpp
index 73be4fb4275..f58655044bf 100644
--- a/src/mongo/db/matcher/expression_array.cpp
+++ b/src/mongo/db/matcher/expression_array.cpp
@@ -119,6 +119,14 @@ namespace mongo {
debug << _path << " $all TODO\n";
}
+ bool AllMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ const AllMatchExpression* realOther = static_cast<const AllMatchExpression*>( other );
+ return _path == realOther->_path && _arrayEntries.equivalent( realOther->_arrayEntries );
+ }
+
// -------
bool ArrayMatchingMatchExpression::matches( const BSONObj& doc, MatchDetails* details ) const {
@@ -170,6 +178,26 @@ namespace mongo {
}
+ bool ArrayMatchingMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ const ArrayMatchingMatchExpression* realOther =
+ static_cast<const ArrayMatchingMatchExpression*>( other );
+
+ if ( _path != realOther->_path )
+ return false;
+
+ if ( numChildren() != realOther->numChildren() )
+ return false;
+
+ for ( unsigned i = 0; i < numChildren(); i++ )
+ if ( !getChild(i)->equivalent( realOther->getChild(i) ) )
+ return false;
+ return true;
+ }
+
+
// -------
Status ElemMatchObjectMatchExpression::init( const StringData& path, const MatchExpression* sub ) {
@@ -319,6 +347,24 @@ namespace mongo {
}
}
+ bool AllElemMatchOp::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ const AllElemMatchOp* realOther = static_cast<const AllElemMatchOp*>( other );
+ if ( _path != realOther->_path )
+ return false;
+
+ if ( _list.size() != realOther->_list.size() )
+ return false;
+
+ for ( unsigned i = 0; i < _list.size(); i++ )
+ if ( !_list[i]->equivalent( realOther->_list[i] ) )
+ return false;
+
+ return true;
+ }
+
// ---------
@@ -339,6 +385,14 @@ namespace mongo {
debug << _path << " $size : " << _size << "\n";
}
+ bool SizeMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ const SizeMatchExpression* realOther = static_cast<const SizeMatchExpression*>( other );
+ return _path == realOther->_path && _size == realOther->_size;
+ }
+
// ------------------
diff --git a/src/mongo/db/matcher/expression_array.h b/src/mongo/db/matcher/expression_array.h
index 1d467a16ad1..1d7a2f621d5 100644
--- a/src/mongo/db/matcher/expression_array.h
+++ b/src/mongo/db/matcher/expression_array.h
@@ -42,6 +42,7 @@ namespace mongo {
*/
class AllMatchExpression : public MatchExpression {
public:
+ AllMatchExpression() : MatchExpression( ARRAY, ALL ){}
Status init( const StringData& path );
ArrayFilterEntries* getArrayFilterEntries() { return &_arrayEntries; }
@@ -50,6 +51,9 @@ namespace mongo {
virtual bool matchesSingleElement( const BSONElement& e ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ bool equivalent( const MatchExpression* other ) const;
+
private:
bool _match( const BSONElementSet& all ) const;
@@ -60,6 +64,7 @@ namespace mongo {
class ArrayMatchingMatchExpression : public MatchExpression {
public:
+ ArrayMatchingMatchExpression( MatchType matchType ) : MatchExpression( ARRAY, matchType ){}
virtual ~ArrayMatchingMatchExpression(){}
virtual bool matches( const BSONObj& doc, MatchDetails* details ) const;
@@ -71,6 +76,8 @@ namespace mongo {
virtual bool matchesArray( const BSONObj& anArray, MatchDetails* details ) const = 0;
+ bool equivalent( const MatchExpression* other ) const;
+
protected:
StringData _path;
};
@@ -78,17 +85,23 @@ namespace mongo {
class ElemMatchObjectMatchExpression : public ArrayMatchingMatchExpression {
public:
+ ElemMatchObjectMatchExpression() : ArrayMatchingMatchExpression( ELEM_MATCH_OBJECT ){}
Status init( const StringData& path, const MatchExpression* sub );
bool matchesArray( const BSONObj& anArray, MatchDetails* details ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ virtual size_t numChildren() const { return 1; }
+ virtual const MatchExpression* getChild( size_t i ) const { return _sub.get(); }
+
private:
boost::scoped_ptr<const MatchExpression> _sub;
};
class ElemMatchValueMatchExpression : public ArrayMatchingMatchExpression {
public:
+ ElemMatchValueMatchExpression() : ArrayMatchingMatchExpression( ELEM_MATCH_VALUE ){}
virtual ~ElemMatchValueMatchExpression();
Status init( const StringData& path );
@@ -98,6 +111,10 @@ namespace mongo {
bool matchesArray( const BSONObj& anArray, MatchDetails* details ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ virtual size_t numChildren() const { return _subs.size(); }
+ virtual const MatchExpression* getChild( size_t i ) const { return _subs[i]; }
+
private:
bool _arrayElementMatchesAll( const BSONElement& e ) const;
@@ -110,6 +127,7 @@ namespace mongo {
*/
class AllElemMatchOp : public MatchExpression {
public:
+ AllElemMatchOp() : MatchExpression( ARRAY, ALL ){}
virtual ~AllElemMatchOp();
Status init( const StringData& path );
@@ -123,6 +141,9 @@ namespace mongo {
virtual bool matchesSingleElement( const BSONElement& e ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const;
+
private:
bool _allMatch( const BSONObj& anArray ) const;
@@ -132,11 +153,15 @@ namespace mongo {
class SizeMatchExpression : public ArrayMatchingMatchExpression {
public:
+ SizeMatchExpression() : ArrayMatchingMatchExpression( SIZE ){}
Status init( const StringData& path, int size );
virtual bool matchesArray( const BSONObj& anArray, MatchDetails* details ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const;
+
private:
int _size; // >= 0 real, < 0, nothing will match
};
diff --git a/src/mongo/db/matcher/expression_array_test.cpp b/src/mongo/db/matcher/expression_array_test.cpp
index eceac5a4cde..9b2a0018bb7 100644
--- a/src/mongo/db/matcher/expression_array_test.cpp
+++ b/src/mongo/db/matcher/expression_array_test.cpp
@@ -183,6 +183,31 @@ namespace mongo {
ASSERT( !all.matches( BSON( "a" << 4 ), NULL ) );
}
+ TEST( AllMatchExpression, Equivalent ) {
+ BSONObj operand = BSON_ARRAY( 5 << 2 );
+
+ AllMatchExpression e1;
+ AllMatchExpression e2;
+ AllMatchExpression e3;
+
+ e1.init( "a" );
+ e1.getArrayFilterEntries()->addEquality( operand[0] );
+ e1.getArrayFilterEntries()->addEquality( operand[1] );
+
+ e2.init( "a" );
+ e2.getArrayFilterEntries()->addEquality( operand[0] );
+
+ e3.init( "b" );
+ e3.getArrayFilterEntries()->addEquality( operand[0] );
+ e3.getArrayFilterEntries()->addEquality( operand[1] );
+
+ ASSERT( e1.equivalent( &e1 ) );
+ ASSERT( !e1.equivalent( &e2 ) );
+ ASSERT( !e1.equivalent( &e3 ) );
+
+ }
+
+
/**
TEST( AllMatchExpression, MatchesIndexKey ) {
BSONObj operand = BSON( "$all" << BSON_ARRAY( 5 ) );
@@ -199,8 +224,8 @@ namespace mongo {
BSONObj baseOperand = BSON( "b" << 5 );
BSONObj match = BSON( "a" << BSON_ARRAY( BSON( "b" << 5.0 ) ) );
BSONObj notMatch = BSON( "a" << BSON_ARRAY( BSON( "b" << 6 ) ) );
- auto_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() );
- ASSERT( eq->init( "b", ComparisonMatchExpression::EQ, baseOperand[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() );
+ ASSERT( eq->init( "b", baseOperand[ "b" ] ).isOK() );
ElemMatchObjectMatchExpression op;
ASSERT( op.init( "a", eq.release() ).isOK() );
ASSERT( op.matchesSingleElement( match[ "a" ] ) );
@@ -211,8 +236,8 @@ namespace mongo {
BSONObj baseOperand = BSON( "1" << 5 );
BSONObj match = BSON( "a" << BSON_ARRAY( BSON_ARRAY( 's' << 5.0 ) ) );
BSONObj notMatch = BSON( "a" << BSON_ARRAY( BSON_ARRAY( 5 << 6 ) ) );
- auto_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() );
- ASSERT( eq->init( "1", ComparisonMatchExpression::EQ, baseOperand[ "1" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() );
+ ASSERT( eq->init( "1", baseOperand[ "1" ] ).isOK() );
ElemMatchObjectMatchExpression op;
ASSERT( op.init( "a", eq.release() ).isOK() );
ASSERT( op.matchesSingleElement( match[ "a" ] ) );
@@ -228,12 +253,12 @@ namespace mongo {
BSONObj notMatch3 = BSON( "a" << BSON_ARRAY( BSON( "b" << BSON_ARRAY( 5 << 6 ) ) ) );
BSONObj match =
BSON( "a" << BSON_ARRAY( BSON( "b" << BSON_ARRAY( 5 << 6 ) << "c" << 7 ) ) );
- auto_ptr<ComparisonMatchExpression> eq1( new ComparisonMatchExpression() );
- ASSERT( eq1->init( "b", ComparisonMatchExpression::EQ, baseOperand1[ "b" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> eq2( new ComparisonMatchExpression() );
- ASSERT( eq2->init( "b", ComparisonMatchExpression::EQ, baseOperand2[ "b" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> eq3( new ComparisonMatchExpression() );
- ASSERT( eq3->init( "c", ComparisonMatchExpression::EQ, baseOperand3[ "c" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eq1( new EqualityMatchExpression() );
+ ASSERT( eq1->init( "b", baseOperand1[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eq2( new EqualityMatchExpression() );
+ ASSERT( eq2->init( "b", baseOperand2[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eq3( new EqualityMatchExpression() );
+ ASSERT( eq3->init( "c", baseOperand3[ "c" ] ).isOK() );
auto_ptr<AndMatchExpression> andOp( new AndMatchExpression() );
andOp->add( eq1.release() );
@@ -250,8 +275,8 @@ namespace mongo {
TEST( ElemMatchObjectMatchExpression, MatchesNonArray ) {
BSONObj baseOperand = BSON( "b" << 5 );
- auto_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() );
- ASSERT( eq->init( "b", ComparisonMatchExpression::EQ, baseOperand[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() );
+ ASSERT( eq->init( "b", baseOperand[ "b" ] ).isOK() );
ElemMatchObjectMatchExpression op;
ASSERT( op.init( "a", eq.release() ).isOK() );
// Directly nested objects are not matched with $elemMatch. An intervening array is
@@ -263,8 +288,8 @@ namespace mongo {
TEST( ElemMatchObjectMatchExpression, MatchesArrayObject ) {
BSONObj baseOperand = BSON( "b" << 5 );
- auto_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() );
- ASSERT( eq->init( "b", ComparisonMatchExpression::EQ, baseOperand[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() );
+ ASSERT( eq->init( "b", baseOperand[ "b" ] ).isOK() );
ElemMatchObjectMatchExpression op;
ASSERT( op.init( "a", eq.release() ).isOK() );
ASSERT( op.matches( BSON( "a" << BSON_ARRAY( BSON( "b" << 5 ) ) ), NULL ) );
@@ -276,8 +301,8 @@ namespace mongo {
TEST( ElemMatchObjectMatchExpression, MatchesMultipleNamedValues ) {
BSONObj baseOperand = BSON( "c" << 5 );
- auto_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() );
- ASSERT( eq->init( "c", ComparisonMatchExpression::EQ, baseOperand[ "c" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() );
+ ASSERT( eq->init( "c", baseOperand[ "c" ] ).isOK() );
ElemMatchObjectMatchExpression op;
ASSERT( op.init( "a.b", eq.release() ).isOK() );
ASSERT( op.matches( BSON( "a" <<
@@ -297,8 +322,8 @@ namespace mongo {
TEST( ElemMatchObjectMatchExpression, ElemMatchKey ) {
BSONObj baseOperand = BSON( "c" << 6 );
- auto_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() );
- ASSERT( eq->init( "c", ComparisonMatchExpression::EQ, baseOperand[ "c" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() );
+ ASSERT( eq->init( "c", baseOperand[ "c" ] ).isOK() );
ElemMatchObjectMatchExpression op;
ASSERT( op.init( "a.b", eq.release() ).isOK() );
MatchDetails details;
@@ -342,8 +367,8 @@ namespace mongo {
BSONObj baseOperand = BSON( "$gt" << 5 );
BSONObj match = BSON( "a" << BSON_ARRAY( 6 ) );
BSONObj notMatch = BSON( "a" << BSON_ARRAY( 4 ) );
- auto_ptr<ComparisonMatchExpression> gt( new ComparisonMatchExpression() );
- ASSERT( gt->init( "", ComparisonMatchExpression::GT, baseOperand[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> gt( new GTMatchExpression() );
+ ASSERT( gt->init( "", baseOperand[ "$gt" ] ).isOK() );
ElemMatchValueMatchExpression op;
ASSERT( op.init( "a", gt.release() ).isOK() );
ASSERT( op.matchesSingleElement( match[ "a" ] ) );
@@ -356,10 +381,10 @@ namespace mongo {
BSONObj notMatch1 = BSON( "a" << BSON_ARRAY( 0 << 1 ) );
BSONObj notMatch2 = BSON( "a" << BSON_ARRAY( 10 << 11 ) );
BSONObj match = BSON( "a" << BSON_ARRAY( 0 << 5 << 11 ) );
- auto_ptr<ComparisonMatchExpression> gt( new ComparisonMatchExpression() );
- ASSERT( gt->init( "", ComparisonMatchExpression::GT, baseOperand1[ "$gt" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() );
- ASSERT( lt->init( "", ComparisonMatchExpression::LT, baseOperand2[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> gt( new GTMatchExpression() );
+ ASSERT( gt->init( "", baseOperand1[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> lt( new LTMatchExpression() );
+ ASSERT( lt->init( "", baseOperand2[ "$lt" ] ).isOK() );
ElemMatchValueMatchExpression op;
ASSERT( op.init( "a" ).isOK() );
@@ -373,8 +398,8 @@ namespace mongo {
TEST( ElemMatchValueMatchExpression, MatchesNonArray ) {
BSONObj baseOperand = BSON( "$gt" << 5 );
- auto_ptr<ComparisonMatchExpression> gt( new ComparisonMatchExpression() );
- ASSERT( gt->init( "", ComparisonMatchExpression::GT, baseOperand[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> gt( new GTMatchExpression() );
+ ASSERT( gt->init( "", baseOperand[ "$gt" ] ).isOK() );
ElemMatchObjectMatchExpression op;
ASSERT( op.init( "a", gt.release() ).isOK() );
// Directly nested objects are not matched with $elemMatch. An intervening array is
@@ -385,8 +410,8 @@ namespace mongo {
TEST( ElemMatchValueMatchExpression, MatchesArrayScalar ) {
BSONObj baseOperand = BSON( "$gt" << 5 );
- auto_ptr<ComparisonMatchExpression> gt( new ComparisonMatchExpression() );
- ASSERT( gt->init( "", ComparisonMatchExpression::GT, baseOperand[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> gt( new GTMatchExpression() );
+ ASSERT( gt->init( "", baseOperand[ "$gt" ] ).isOK() );
ElemMatchValueMatchExpression op;
ASSERT( op.init( "a", gt.release() ).isOK() );
ASSERT( op.matches( BSON( "a" << BSON_ARRAY( 6 ) ), NULL ) );
@@ -396,8 +421,8 @@ namespace mongo {
TEST( ElemMatchValueMatchExpression, MatchesMultipleNamedValues ) {
BSONObj baseOperand = BSON( "$gt" << 5 );
- auto_ptr<ComparisonMatchExpression> gt( new ComparisonMatchExpression() );
- ASSERT( gt->init( "", ComparisonMatchExpression::GT, baseOperand[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> gt( new GTMatchExpression() );
+ ASSERT( gt->init( "", baseOperand[ "$gt" ] ).isOK() );
ElemMatchValueMatchExpression op;
ASSERT( op.init( "a.b", gt.release() ).isOK() );
ASSERT( op.matches( BSON( "a" << BSON_ARRAY( BSON( "b" << BSON_ARRAY( 6 ) ) ) ), NULL ) );
@@ -409,8 +434,8 @@ namespace mongo {
TEST( ElemMatchValueMatchExpression, ElemMatchKey ) {
BSONObj baseOperand = BSON( "$gt" << 6 );
- auto_ptr<ComparisonMatchExpression> gt( new ComparisonMatchExpression() );
- ASSERT( gt->init( "", ComparisonMatchExpression::GT, baseOperand[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> gt( new GTMatchExpression() );
+ ASSERT( gt->init( "", baseOperand[ "$gt" ] ).isOK() );
ElemMatchValueMatchExpression op;
ASSERT( op.init( "a.b", gt.release() ).isOK() );
MatchDetails details;
@@ -452,12 +477,12 @@ namespace mongo {
BSONObj baseOperanda1 = BSON( "a" << 1 );
- auto_ptr<ComparisonMatchExpression> eqa1( new ComparisonMatchExpression() );
- ASSERT( eqa1->init( "a", ComparisonMatchExpression::EQ, baseOperanda1[ "a" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eqa1( new EqualityMatchExpression() );
+ ASSERT( eqa1->init( "a", baseOperanda1[ "a" ] ).isOK() );
BSONObj baseOperandb1 = BSON( "b" << 1 );
- auto_ptr<ComparisonMatchExpression> eqb1( new ComparisonMatchExpression() );
- ASSERT( eqb1->init( "b", ComparisonMatchExpression::EQ, baseOperandb1[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eqb1( new EqualityMatchExpression() );
+ ASSERT( eqb1->init( "b", baseOperandb1[ "b" ] ).isOK() );
auto_ptr<AndMatchExpression> and1( new AndMatchExpression() );
and1->add( eqa1.release() );
@@ -469,12 +494,12 @@ namespace mongo {
// elemMatch1 = { x : { $elemMatch : { a : 1, b : 1 } } }
BSONObj baseOperanda2 = BSON( "a" << 2 );
- auto_ptr<ComparisonMatchExpression> eqa2( new ComparisonMatchExpression() );
- ASSERT( eqa2->init( "a", ComparisonMatchExpression::EQ, baseOperanda2[ "a" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eqa2( new EqualityMatchExpression() );
+ ASSERT( eqa2->init( "a", baseOperanda2[ "a" ] ).isOK() );
BSONObj baseOperandb2 = BSON( "b" << 2 );
- auto_ptr<ComparisonMatchExpression> eqb2( new ComparisonMatchExpression() );
- ASSERT( eqb2->init( "b", ComparisonMatchExpression::EQ, baseOperandb2[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> eqb2( new EqualityMatchExpression() );
+ ASSERT( eqb2->init( "b", baseOperandb2[ "b" ] ).isOK() );
auto_ptr<AndMatchExpression> and2( new AndMatchExpression() );
and2->add( eqa2.release() );
@@ -510,12 +535,12 @@ namespace mongo {
TEST( AllElemMatchOp, Matches ) {
BSONObj baseOperandgt1 = BSON( "$gt" << 1 );
- auto_ptr<ComparisonMatchExpression> gt1( new ComparisonMatchExpression() );
- ASSERT( gt1->init( "", ComparisonMatchExpression::GT, baseOperandgt1[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> gt1( new GTMatchExpression() );
+ ASSERT( gt1->init( "", baseOperandgt1[ "$gt" ] ).isOK() );
BSONObj baseOperandlt1 = BSON( "$lt" << 10 );
- auto_ptr<ComparisonMatchExpression> lt1( new ComparisonMatchExpression() );
- ASSERT( lt1->init( "", ComparisonMatchExpression::LT, baseOperandlt1[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> lt1( new LTMatchExpression() );
+ ASSERT( lt1->init( "", baseOperandlt1[ "$lt" ] ).isOK() );
auto_ptr<ElemMatchValueMatchExpression> elemMatch1( new ElemMatchValueMatchExpression() );
elemMatch1->init( "x" );
@@ -523,12 +548,12 @@ namespace mongo {
elemMatch1->add( lt1.release() );
BSONObj baseOperandgt2 = BSON( "$gt" << 101 );
- auto_ptr<ComparisonMatchExpression> gt2( new ComparisonMatchExpression() );
- ASSERT( gt2->init( "", ComparisonMatchExpression::GT, baseOperandgt2[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> gt2( new GTMatchExpression() );
+ ASSERT( gt2->init( "", baseOperandgt2[ "$gt" ] ).isOK() );
BSONObj baseOperandlt2 = BSON( "$lt" << 110 );
- auto_ptr<ComparisonMatchExpression> lt2( new ComparisonMatchExpression() );
- ASSERT( lt2->init( "", ComparisonMatchExpression::LT, baseOperandlt2[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> lt2( new LTMatchExpression() );
+ ASSERT( lt2->init( "", baseOperandlt2[ "$lt" ] ).isOK() );
auto_ptr<ElemMatchValueMatchExpression> elemMatch2( new ElemMatchValueMatchExpression() );
elemMatch2->init( "x" );
@@ -630,6 +655,20 @@ namespace mongo {
ASSERT_EQUALS( "1", details.elemMatchKey() );
}
+ TEST( SizeMatchExpression, Equivalent ) {
+ SizeMatchExpression e1;
+ SizeMatchExpression e2;
+ SizeMatchExpression e3;
+
+ e1.init( "a", 5 );
+ e2.init( "a", 6 );
+ e3.init( "v", 5 );
+
+ ASSERT( e1.equivalent( &e1 ) );
+ ASSERT( !e1.equivalent( &e2 ) );
+ ASSERT( !e1.equivalent( &e3 ) );
+ }
+
/**
TEST( SizeMatchExpression, MatchesIndexKey ) {
BSONObj operand = BSON( "$size" << 4 );
diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp
new file mode 100644
index 00000000000..d038a9543ed
--- /dev/null
+++ b/src/mongo/db/matcher/expression_geo.cpp
@@ -0,0 +1,66 @@
+// expression_geo.cpp
+
+/**
+ * Copyright (C) 2013 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mongo/pch.h"
+#include "mongo/db/matcher/expression_geo.h"
+
+namespace mongo {
+
+ Status GeoMatchExpression::init( const StringData& path, const GeoQuery& query ) {
+ _path = path;
+ _query = query;
+ return Status::OK();
+ }
+
+ bool GeoMatchExpression::matchesSingleElement( const BSONElement& e ) const {
+ if ( !e.isABSONObj())
+ return false;
+
+ GeometryContainer container;
+ if ( !container.parseFrom( e.Obj() ) )
+ return false;
+
+ return _query.satisfiesPredicate( container );
+ }
+
+ void GeoMatchExpression::debugString( StringBuilder& debug, int level ) const {
+ _debugAddSpace( debug, level );
+ debug << "GEO\n";
+ }
+
+ bool GeoMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ const GeoMatchExpression* realOther = static_cast<const GeoMatchExpression*>( other );
+
+ if ( _path != realOther->_path )
+ return false;
+
+ // TODO:
+ // return _query == realOther->_query;
+ return false;
+ }
+
+ LeafMatchExpression* GeoMatchExpression::shallowClone() const {
+ GeoMatchExpression* next = new GeoMatchExpression();
+ next->init( _path, _query );
+ return next;
+ }
+
+}
diff --git a/src/mongo/db/matcher/expression_geo.h b/src/mongo/db/matcher/expression_geo.h
new file mode 100644
index 00000000000..9808519c586
--- /dev/null
+++ b/src/mongo/db/matcher/expression_geo.h
@@ -0,0 +1,47 @@
+// expression_geo.h
+
+/**
+ * Copyright (C) 2013 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "mongo/db/geo/geoquery.h"
+#include "mongo/db/matcher/expression.h"
+#include "mongo/db/matcher/expression_leaf.h"
+
+namespace mongo {
+
+ class GeoMatchExpression : public LeafMatchExpression {
+ public:
+ GeoMatchExpression() : LeafMatchExpression( GEO ){}
+ virtual ~GeoMatchExpression(){}
+
+ Status init( const StringData& path, const GeoQuery& query );
+
+ virtual bool matchesSingleElement( const BSONElement& e ) const;
+
+ virtual void debugString( StringBuilder& debug, int level = 0 ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const;
+
+ virtual LeafMatchExpression* shallowClone() const;
+
+ private:
+ GeoQuery _query;
+ };
+
+}
diff --git a/src/mongo/db/matcher/expression_geo_test.cpp b/src/mongo/db/matcher/expression_geo_test.cpp
new file mode 100644
index 00000000000..66404a9aa77
--- /dev/null
+++ b/src/mongo/db/matcher/expression_geo_test.cpp
@@ -0,0 +1,48 @@
+// expression_geo_test.cpp
+
+/**
+ * Copyright (C) 2013 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** Unit tests for MatchExpression operator implementations in match_operators.{h,cpp}. */
+
+#include "mongo/unittest/unittest.h"
+
+#include "mongo/db/jsobj.h"
+#include "mongo/db/json.h"
+#include "mongo/db/matcher.h"
+#include "mongo/db/matcher/expression.h"
+#include "mongo/db/matcher/expression_geo.h"
+
+namespace mongo {
+
+ TEST( ExpressionGeoTest, Geo1 ) {
+ BSONObj query = fromjson("{loc:{$within:{$box:[{x: 4, y:4},[6,6]]}}}");
+
+ GeoQuery gq;
+ ASSERT( gq.parseFrom( query["loc"].Obj() ) );
+
+ GeoMatchExpression ge;
+ ASSERT( ge.init( "a", gq ).isOK() );
+
+ ASSERT(!ge.matches(fromjson("{a: [3,4]}")));
+ ASSERT(ge.matches(fromjson("{a: [4,4]}")));
+ ASSERT(ge.matches(fromjson("{a: [5,5]}")));
+ ASSERT(ge.matches(fromjson("{a: [5,5.1]}")));
+ ASSERT(ge.matches(fromjson("{a: {x: 5, y:5.1}}")));
+
+ }
+
+}
diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp
index cad5cd110f1..db78d4eaa85 100644
--- a/src/mongo/db/matcher/expression_leaf.cpp
+++ b/src/mongo/db/matcher/expression_leaf.cpp
@@ -75,15 +75,39 @@ namespace mongo {
// -------------
- Status ComparisonMatchExpression::init( const StringData& path, Type type, const BSONElement& rhs ) {
+ bool ComparisonMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( other->matchType() != matchType() )
+ return false;
+ const ComparisonMatchExpression* realOther =
+ static_cast<const ComparisonMatchExpression*>( other );
+
+ return
+ _path == realOther->_path &&
+ _rhs.valuesEqual( realOther->_rhs );
+ }
+
+
+ Status ComparisonMatchExpression::init( const StringData& path, const BSONElement& rhs ) {
_path = path;
- _type = type;
_rhs = rhs;
if ( rhs.eoo() ) {
return Status( ErrorCodes::BadValue, "need a real operand" );
}
- _allHaveToMatch = _type == NE;
+ switch ( matchType() ) {
+ case NE:
+ _allHaveToMatch = true;
+ break;
+ case LT:
+ case LTE:
+ case EQ:
+ case GT:
+ case GTE:
+ _allHaveToMatch = false;
+ break;
+ default:
+ return Status( ErrorCodes::BadValue, "bad match type for ComparisonMatchExpression" );
+ }
return Status::OK();
}
@@ -96,20 +120,20 @@ namespace mongo {
// some special cases
// jstNULL and undefined are treated the same
if ( e.canonicalType() + _rhs.canonicalType() == 5 ) {
- return _type == EQ || _type == LTE || _type == GTE;
+ return matchType() == EQ || matchType() == LTE || matchType() == GTE;
}
return _invertForNE( false );
}
if ( _rhs.type() == Array ) {
- if ( _type != EQ && _type != NE ) {
+ if ( matchType() != EQ && matchType() != NE ) {
return false;
}
}
int x = compareElementValues( e, _rhs );
- switch ( _type ) {
+ switch ( matchType() ) {
case LT:
return x < 0;
case LTE:
@@ -122,12 +146,14 @@ namespace mongo {
return x >= 0;
case NE:
return x != 0;
+ default:
+ throw 1;
}
throw 1;
}
bool ComparisonMatchExpression::_invertForNE( bool normal ) const {
- if ( _type == NE )
+ if ( matchType() == NE )
return !normal;
return normal;
}
@@ -135,13 +161,14 @@ namespace mongo {
void ComparisonMatchExpression::debugString( StringBuilder& debug, int level ) const {
_debugAddSpace( debug, level );
debug << _path << " ";
- switch ( _type ) {
+ switch ( matchType() ) {
case LT: debug << "$lt"; break;
case LTE: debug << "$lte"; break;
case EQ: debug << "=="; break;
case GT: debug << "$gt"; break;
case GTE: debug << "$gte"; break;
case NE: debug << "$ne"; break;
+ default: debug << " UNKNOWN - should be impossible"; break;
}
debug << " " << _rhs.toString( false ) << "\n";
}
@@ -166,6 +193,18 @@ namespace mongo {
return options;
}
+ bool RegexMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ const RegexMatchExpression* realOther = static_cast<const RegexMatchExpression*>( other );
+ return
+ _path == realOther->_path &&
+ _regex == realOther->_regex
+ && _flags == realOther->_flags;
+ }
+
+
Status RegexMatchExpression::init( const StringData& path, const BSONElement& e ) {
if ( e.type() != RegEx )
return Status( ErrorCodes::BadValue, "regex not a regex" );
@@ -229,6 +268,17 @@ namespace mongo {
debug << _path << " mod " << _divisor << " % x == " << _remainder << "\n";
}
+ bool ModMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ const ModMatchExpression* realOther = static_cast<const ModMatchExpression*>( other );
+ return
+ _path == realOther->_path &&
+ _divisor == realOther->_divisor &&
+ _remainder == realOther->_remainder;
+ }
+
// ------------------
@@ -250,6 +300,15 @@ namespace mongo {
debug << _path << " exists: " << _exists << "\n";
}
+ bool ExistsMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ const ExistsMatchExpression* realOther = static_cast<const ExistsMatchExpression*>( other );
+ return _path == realOther->_path && _exists == realOther->_exists;
+ }
+
+
// ----
Status TypeMatchExpression::init( const StringData& path, int type ) {
@@ -311,6 +370,15 @@ namespace mongo {
}
+ bool TypeMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ const TypeMatchExpression* realOther = static_cast<const TypeMatchExpression*>( other );
+ return _path == realOther->_path && _type == realOther->_type;
+ }
+
+
// --------
ArrayFilterEntries::ArrayFilterEntries(){
@@ -345,6 +413,27 @@ namespace mongo {
return Status::OK();
}
+ bool ArrayFilterEntries::equivalent( const ArrayFilterEntries& other ) const {
+ if ( _hasNull != other._hasNull )
+ return false;
+
+ if ( _regexes.size() != other._regexes.size() )
+ return false;
+ for ( unsigned i = 0; i < _regexes.size(); i++ )
+ if ( !_regexes[i]->equivalent( other._regexes[i] ) )
+ return false;
+
+ return _equalities == other._equalities;
+ }
+
+ void ArrayFilterEntries::copyTo( ArrayFilterEntries& toFillIn ) const {
+ toFillIn._hasNull = _hasNull;
+ toFillIn._equalities = _equalities;
+ for ( unsigned i = 0; i < _regexes.size(); i++ )
+ toFillIn._regexes.push_back( static_cast<RegexMatchExpression*>(_regexes[i]->shallowClone()) );
+ }
+
+
// -----------
void InMatchExpression::init( const StringData& path ) {
@@ -373,12 +462,35 @@ namespace mongo {
debug << _path << " $in: TODO\n";
}
+ bool InMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+ const InMatchExpression* realOther = static_cast<const InMatchExpression*>( other );
+ log() << "yo: " << _path << " " << realOther->_path << std::endl;
+ return
+ _path == realOther->_path &&
+ _arrayEntries.equivalent( realOther->_arrayEntries );
+ }
+
+ LeafMatchExpression* InMatchExpression::shallowClone() const {
+ InMatchExpression* next = new InMatchExpression();
+ copyTo( next );
+ return next;
+ }
+
+ void InMatchExpression::copyTo( InMatchExpression* toFillIn ) const {
+ toFillIn->init( _path );
+ _arrayEntries.copyTo( toFillIn->_arrayEntries );
+ }
+
+
// ----------
void NinMatchExpression::init( const StringData& path ) {
_path = path;
_allHaveToMatch = true;
+ _in.init( path );
}
@@ -392,6 +504,22 @@ namespace mongo {
debug << _path << " $nin: TODO\n";
}
+ bool NinMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ return _in.equivalent( &(static_cast<const NinMatchExpression*>(other)->_in) );
+
+ }
+
+ LeafMatchExpression* NinMatchExpression::shallowClone() const {
+ NinMatchExpression* next = new NinMatchExpression();
+ next->init( _path );
+ _in.copyTo( &next->_in );
+ return next;
+ }
+
+
}
diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h
index a0aebe7a345..30b3463fcb6 100644
--- a/src/mongo/db/matcher/expression_leaf.h
+++ b/src/mongo/db/matcher/expression_leaf.h
@@ -30,15 +30,20 @@ namespace mongo {
class LeafMatchExpression : public MatchExpression {
public:
- LeafMatchExpression() {
+ LeafMatchExpression( MatchType matchType ) : MatchExpression( LEAF, matchType ) {
_allHaveToMatch = false;
}
virtual ~LeafMatchExpression(){}
+ virtual LeafMatchExpression* shallowClone() const = 0;
+
virtual bool matches( const BSONObj& doc, MatchDetails* details = 0 ) const;
virtual bool matchesSingleElement( const BSONElement& e ) const = 0;
+
+ virtual const StringData getPath() const { return _path; }
+
protected:
StringData _path;
bool _allHaveToMatch;
@@ -48,25 +53,90 @@ namespace mongo {
class ComparisonMatchExpression : public LeafMatchExpression {
public:
- enum Type { LTE, LT, EQ, GT, GTE, NE };
+ ComparisonMatchExpression( MatchType type ) : LeafMatchExpression( type ){}
- Status init( const StringData& path, Type type, const BSONElement& rhs );
+ Status init( const StringData& path, const BSONElement& rhs );
virtual ~ComparisonMatchExpression(){}
- virtual Type getType() const { return _type; }
-
virtual bool matchesSingleElement( const BSONElement& e ) const;
virtual void debugString( StringBuilder& debug, int level = 0 ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const;
+
protected:
bool _invertForNE( bool normal ) const;
- private:
- Type _type;
BSONElement _rhs;
};
+ class EqualityMatchExpression : public ComparisonMatchExpression {
+ public:
+ EqualityMatchExpression() : ComparisonMatchExpression( EQ ){}
+ virtual LeafMatchExpression* shallowClone() const {
+ ComparisonMatchExpression* e = new EqualityMatchExpression();
+ e->init( _path, _rhs );
+ return e;
+ }
+ };
+
+ class LTEMatchExpression : public ComparisonMatchExpression {
+ public:
+ LTEMatchExpression() : ComparisonMatchExpression( LTE ){}
+ virtual LeafMatchExpression* shallowClone() const {
+ ComparisonMatchExpression* e = new LTEMatchExpression();
+ e->init( _path, _rhs );
+ return e;
+ }
+
+ };
+
+ class LTMatchExpression : public ComparisonMatchExpression {
+ public:
+ LTMatchExpression() : ComparisonMatchExpression( LT ){}
+ virtual LeafMatchExpression* shallowClone() const {
+ ComparisonMatchExpression* e = new LTMatchExpression();
+ e->init( _path, _rhs );
+ return e;
+ }
+
+ };
+
+ class GTMatchExpression : public ComparisonMatchExpression {
+ public:
+ GTMatchExpression() : ComparisonMatchExpression( GT ){}
+ virtual LeafMatchExpression* shallowClone() const {
+ ComparisonMatchExpression* e = new GTMatchExpression();
+ e->init( _path, _rhs );
+ return e;
+ }
+
+ };
+
+ class GTEMatchExpression : public ComparisonMatchExpression {
+ public:
+ GTEMatchExpression() : ComparisonMatchExpression( GTE ){}
+ virtual LeafMatchExpression* shallowClone() const {
+ ComparisonMatchExpression* e = new GTEMatchExpression();
+ e->init( _path, _rhs );
+ return e;
+ }
+
+ };
+
+ class NEMatchExpression : public ComparisonMatchExpression {
+ public:
+ NEMatchExpression() : ComparisonMatchExpression( NE ){}
+ virtual LeafMatchExpression* shallowClone() const {
+ ComparisonMatchExpression* e = new NEMatchExpression();
+ e->init( _path, _rhs );
+ return e;
+ }
+
+ };
+
+
class RegexMatchExpression : public LeafMatchExpression {
public:
/**
@@ -76,12 +146,23 @@ namespace mongo {
*/
static const size_t MaxPatternSize = 32764;
+ RegexMatchExpression() : LeafMatchExpression( REGEX ){}
+
Status init( const StringData& path, const StringData& regex, const StringData& options );
Status init( const StringData& path, const BSONElement& e );
+ virtual LeafMatchExpression* shallowClone() const {
+ RegexMatchExpression* e = new RegexMatchExpression();
+ e->init( _path, _regex, _flags );
+ return e;
+ }
+
virtual bool matchesSingleElement( const BSONElement& e ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const;
+
private:
std::string _regex;
std::string _flags;
@@ -90,11 +171,22 @@ namespace mongo {
class ModMatchExpression : public LeafMatchExpression {
public:
+ ModMatchExpression() : LeafMatchExpression( MOD ){}
+
Status init( const StringData& path, int divisor, int remainder );
+ virtual LeafMatchExpression* shallowClone() const {
+ ModMatchExpression* m = new ModMatchExpression();
+ m->init( _path, _divisor, _remainder );
+ return m;
+ }
+
virtual bool matchesSingleElement( const BSONElement& e ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const;
+
private:
int _divisor;
int _remainder;
@@ -102,17 +194,30 @@ namespace mongo {
class ExistsMatchExpression : public LeafMatchExpression {
public:
+ ExistsMatchExpression() : LeafMatchExpression( EXISTS ){}
+
Status init( const StringData& path, bool exists );
+ virtual LeafMatchExpression* shallowClone() const {
+ ExistsMatchExpression* e = new ExistsMatchExpression();
+ e->init( _path, _exists );
+ return e;
+ }
+
virtual bool matchesSingleElement( const BSONElement& e ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const;
+
private:
bool _exists;
};
class TypeMatchExpression : public MatchExpression {
public:
+ TypeMatchExpression() : MatchExpression( TYPE_CATEGORY, TYPE_OPERATOR ){}
+
Status init( const StringData& path, int type );
virtual bool matchesSingleElement( const BSONElement& e ) const;
@@ -120,6 +225,8 @@ namespace mongo {
virtual bool matches( const BSONObj& doc, MatchDetails* details = 0 ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const;
private:
bool _matches( const StringData& path, const BSONObj& doc, MatchDetails* details = 0 ) const;
@@ -152,36 +259,54 @@ namespace mongo {
bool singleNull() const { return size() == 1 && _hasNull; }
int size() const { return _equalities.size() + _regexes.size(); }
+ bool equivalent( const ArrayFilterEntries& other ) const;
+
+ void copyTo( ArrayFilterEntries& toFillIn ) const;
private:
bool _hasNull; // if _equalities has a jstNULL element in it
BSONElementSet _equalities;
std::vector<RegexMatchExpression*> _regexes;
};
-
/**
* query operator: $in
*/
class InMatchExpression : public LeafMatchExpression {
public:
+ InMatchExpression() : LeafMatchExpression( IN ){}
void init( const StringData& path );
+
+ virtual LeafMatchExpression* shallowClone() const;
+
ArrayFilterEntries* getArrayFilterEntries() { return &_arrayEntries; }
virtual bool matchesSingleElement( const BSONElement& e ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const;
+
+ void copyTo( InMatchExpression* toFillIn ) const;
+
private:
ArrayFilterEntries _arrayEntries;
};
class NinMatchExpression : public LeafMatchExpression {
public:
+ NinMatchExpression() : LeafMatchExpression( NIN ){}
void init( const StringData& path );
+
+ virtual LeafMatchExpression* shallowClone() const;
+
ArrayFilterEntries* getArrayFilterEntries() { return _in.getArrayFilterEntries(); }
virtual bool matchesSingleElement( const BSONElement& e ) const;
virtual void debugString( StringBuilder& debug, int level ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const;
+
private:
InMatchExpression _in;
};
diff --git a/src/mongo/db/matcher/expression_leaf_test.cpp b/src/mongo/db/matcher/expression_leaf_test.cpp
index f6db53166c9..e102f92f8c5 100644
--- a/src/mongo/db/matcher/expression_leaf_test.cpp
+++ b/src/mongo/db/matcher/expression_leaf_test.cpp
@@ -30,39 +30,40 @@ namespace mongo {
BSONObj match = BSON( "a" << 5.0 );
BSONObj notMatch = BSON( "a" << 6 );
- ComparisonMatchExpression eq;
- eq.init( "", ComparisonMatchExpression::EQ, operand["a"] );
+ EqualityMatchExpression eq;
+ eq.init( "", operand["a"] );
ASSERT( eq.matchesSingleElement( match.firstElement() ) );
ASSERT( !eq.matchesSingleElement( notMatch.firstElement() ) );
- }
+ ASSERT( eq.equivalent( &eq ) );
+ }
TEST( EqOp, InvalidEooOperand ) {
BSONObj operand;
- ComparisonMatchExpression eq;
- ASSERT( !eq.init( "", ComparisonMatchExpression::EQ, operand.firstElement() ).isOK() );
+ EqualityMatchExpression eq;
+ ASSERT( !eq.init( "", operand.firstElement() ).isOK() );
}
TEST( EqOp, MatchesScalar ) {
BSONObj operand = BSON( "a" << 5 );
- ComparisonMatchExpression eq;
- eq.init( "a", ComparisonMatchExpression::EQ, operand[ "a" ] );
+ EqualityMatchExpression eq;
+ eq.init( "a", operand[ "a" ] );
ASSERT( eq.matches( BSON( "a" << 5.0 ), NULL ) );
ASSERT( !eq.matches( BSON( "a" << 4 ), NULL ) );
}
TEST( EqOp, MatchesArrayValue ) {
BSONObj operand = BSON( "a" << 5 );
- ComparisonMatchExpression eq;
- eq.init( "a", ComparisonMatchExpression::EQ, operand[ "a" ] );
+ EqualityMatchExpression eq;
+ eq.init( "a", operand[ "a" ] );
ASSERT( eq.matches( BSON( "a" << BSON_ARRAY( 5.0 << 6 ) ), NULL ) );
ASSERT( !eq.matches( BSON( "a" << BSON_ARRAY( 6 << 7 ) ), NULL ) );
}
TEST( EqOp, MatchesReferencedObjectValue ) {
BSONObj operand = BSON( "a.b" << 5 );
- ComparisonMatchExpression eq;
- eq.init( "a.b", ComparisonMatchExpression::EQ, operand[ "a.b" ] );
+ EqualityMatchExpression eq;
+ eq.init( "a.b", operand[ "a.b" ] );
ASSERT( eq.matches( BSON( "a" << BSON( "b" << 5 ) ), NULL ) );
ASSERT( eq.matches( BSON( "a" << BSON( "b" << BSON_ARRAY( 5 ) ) ), NULL ) );
ASSERT( eq.matches( BSON( "a" << BSON_ARRAY( BSON( "b" << 5 ) ) ), NULL ) );
@@ -70,16 +71,16 @@ namespace mongo {
TEST( EqOp, MatchesReferencedArrayValue ) {
BSONObj operand = BSON( "a.0" << 5 );
- ComparisonMatchExpression eq;
- eq.init( "a.0", ComparisonMatchExpression::EQ, operand[ "a.0" ] );
+ EqualityMatchExpression eq;
+ eq.init( "a.0", operand[ "a.0" ] );
ASSERT( eq.matches( BSON( "a" << BSON_ARRAY( 5 ) ), NULL ) );
ASSERT( !eq.matches( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 5 ) ) ), NULL ) );
}
TEST( EqOp, MatchesNull ) {
BSONObj operand = BSON( "a" << BSONNULL );
- ComparisonMatchExpression eq;
- eq.init( "a", ComparisonMatchExpression::EQ, operand[ "a" ] );
+ EqualityMatchExpression eq;
+ eq.init( "a", operand[ "a" ] );
ASSERT( eq.matches( BSONObj(), NULL ) );
ASSERT( eq.matches( BSON( "a" << BSONNULL ), NULL ) );
ASSERT( !eq.matches( BSON( "a" << 4 ), NULL ) );
@@ -87,8 +88,8 @@ namespace mongo {
TEST( EqOp, MatchesMinKey ) {
BSONObj operand = BSON( "a" << MinKey );
- ComparisonMatchExpression eq;
- eq.init( "a", ComparisonMatchExpression::EQ, operand[ "a" ] );
+ EqualityMatchExpression eq;
+ eq.init( "a", operand[ "a" ] );
ASSERT( eq.matches( BSON( "a" << MinKey ), NULL ) );
ASSERT( !eq.matches( BSON( "a" << MaxKey ), NULL ) );
ASSERT( !eq.matches( BSON( "a" << 4 ), NULL ) );
@@ -98,8 +99,8 @@ namespace mongo {
TEST( EqOp, MatchesMaxKey ) {
BSONObj operand = BSON( "a" << MaxKey );
- ComparisonMatchExpression eq;
- ASSERT( eq.init( "a", ComparisonMatchExpression::EQ, operand[ "a" ] ).isOK() );
+ EqualityMatchExpression eq;
+ ASSERT( eq.init( "a", operand[ "a" ] ).isOK() );
ASSERT( eq.matches( BSON( "a" << MaxKey ), NULL ) );
ASSERT( !eq.matches( BSON( "a" << MinKey ), NULL ) );
ASSERT( !eq.matches( BSON( "a" << 4 ), NULL ) );
@@ -107,8 +108,8 @@ namespace mongo {
TEST( EqOp, MatchesFullArray ) {
BSONObj operand = BSON( "a" << BSON_ARRAY( 1 << 2 ) );
- ComparisonMatchExpression eq;
- ASSERT( eq.init( "a", ComparisonMatchExpression::EQ, operand[ "a" ] ).isOK() );
+ EqualityMatchExpression eq;
+ ASSERT( eq.init( "a", operand[ "a" ] ).isOK() );
ASSERT( eq.matches( BSON( "a" << BSON_ARRAY( 1 << 2 ) ), NULL ) );
ASSERT( !eq.matches( BSON( "a" << BSON_ARRAY( 1 << 2 << 3 ) ), NULL ) );
ASSERT( !eq.matches( BSON( "a" << BSON_ARRAY( 1 ) ), NULL ) );
@@ -117,8 +118,8 @@ namespace mongo {
TEST( EqOp, ElemMatchKey ) {
BSONObj operand = BSON( "a" << 5 );
- ComparisonMatchExpression eq;
- ASSERT( eq.init( "a", ComparisonMatchExpression::EQ, operand[ "a" ] ).isOK() );
+ EqualityMatchExpression eq;
+ ASSERT( eq.init( "a", operand[ "a" ] ).isOK() );
MatchDetails details;
details.requestElemMatchKey();
ASSERT( !eq.matches( BSON( "a" << 4 ), &details ) );
@@ -130,11 +131,27 @@ namespace mongo {
ASSERT_EQUALS( "2", details.elemMatchKey() );
}
+ TEST( EqOp, Equality1 ) {
+ EqualityMatchExpression eq1;
+ EqualityMatchExpression eq2;
+ EqualityMatchExpression eq3;
+
+ BSONObj operand = BSON( "a" << 5 << "b" << 5 << "c" << 4 );
+
+ eq1.init( "a", operand["a"] );
+ eq2.init( "a", operand["b"] );
+ eq3.init( "c", operand["c"] );
+
+ ASSERT( eq1.equivalent( &eq1 ) );
+ ASSERT( eq1.equivalent( &eq2 ) );
+ ASSERT( !eq1.equivalent( &eq3 ) );
+ }
+
/**
TEST( EqOp, MatchesIndexKeyScalar ) {
BSONObj operand = BSON( "a" << 6 );
- ComparisonMatchExpression eq;
- ASSERT( eq.init( "a", ComparisonMatchExpression::EQ, operand[ "a" ] ).isOK() );
+ EqualityMatchExpression eq;
+ ASSERT( eq.init( "a", operand[ "a" ] ).isOK() );
IndexSpec indexSpec( BSON( "a" << 1 ) );
ASSERT( MatchMatchExpression::PartialMatchResult_True ==
eq.matchesIndexKey( BSON( "" << 6 ), indexSpec ) );
@@ -146,8 +163,8 @@ namespace mongo {
TEST( EqOp, MatchesIndexKeyMissing ) {
BSONObj operand = BSON( "a" << 6 );
- ComparisonMatchExpression eq;
- ASSERT( eq.init( "a", ComparisonMatchExpression::EQ, operand[ "a" ] ).isOK() );
+ EqualityMatchExpression eq;
+ ASSERT( eq.init( "a", operand[ "a" ] ).isOK() );
IndexSpec indexSpec( BSON( "b" << 1 ) );
ASSERT( MatchMatchExpression::PartialMatchResult_Unknown ==
eq.matchesIndexKey( BSON( "" << 6 ), indexSpec ) );
@@ -186,8 +203,8 @@ namespace mongo {
BSONObj notMatch = BSON( "a" << 6 );
BSONObj notMatchEqual = BSON( "a" << 5 );
BSONObj notMatchWrongType = BSON( "a" << "foo" );
- ComparisonMatchExpression lt;
- ASSERT( lt.init( "", ComparisonMatchExpression::LT, operand[ "$lt" ] ).isOK() );
+ LTMatchExpression lt;
+ ASSERT( lt.init( "", operand[ "$lt" ] ).isOK() );
ASSERT( lt.matchesSingleElement( match.firstElement() ) );
ASSERT( !lt.matchesSingleElement( notMatch.firstElement() ) );
ASSERT( !lt.matchesSingleElement( notMatchEqual.firstElement() ) );
@@ -196,38 +213,38 @@ namespace mongo {
TEST( LtOp, InvalidEooOperand ) {
BSONObj operand;
- ComparisonMatchExpression lt;
- ASSERT( !lt.init( "", ComparisonMatchExpression::LT, operand.firstElement() ).isOK() );
+ LTMatchExpression lt;
+ ASSERT( !lt.init( "", operand.firstElement() ).isOK() );
}
TEST( LtOp, MatchesScalar ) {
BSONObj operand = BSON( "$lt" << 5 );
- ComparisonMatchExpression lt;
- ASSERT( lt.init( "a", ComparisonMatchExpression::LT, operand[ "$lt" ] ).isOK() );
+ LTMatchExpression lt;
+ ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() );
ASSERT( lt.matches( BSON( "a" << 4.5 ), NULL ) );
ASSERT( !lt.matches( BSON( "a" << 6 ), NULL ) );
}
TEST( LtOp, MatchesArrayValue ) {
BSONObj operand = BSON( "$lt" << 5 );
- ComparisonMatchExpression lt;
- ASSERT( lt.init( "a", ComparisonMatchExpression::LT, operand[ "$lt" ] ).isOK() );
+ LTMatchExpression lt;
+ ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() );
ASSERT( lt.matches( BSON( "a" << BSON_ARRAY( 6 << 4.5 ) ), NULL ) );
ASSERT( !lt.matches( BSON( "a" << BSON_ARRAY( 6 << 7 ) ), NULL ) );
}
TEST( LtOp, MatchesWholeArray ) {
BSONObj operand = BSON( "$lt" << BSON_ARRAY( 5 ) );
- ComparisonMatchExpression lt;
- ASSERT( lt.init( "a", ComparisonMatchExpression::LT, operand[ "$lt" ] ).isOK() );
+ LTMatchExpression lt;
+ ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() );
// Arrays are not comparable as inequalities.
ASSERT( !lt.matches( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) );
}
TEST( LtOp, MatchesNull ) {
BSONObj operand = BSON( "$lt" << BSONNULL );
- ComparisonMatchExpression lt;
- ASSERT( lt.init( "a", ComparisonMatchExpression::LT, operand[ "$lt" ] ).isOK() );
+ LTMatchExpression lt;
+ ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() );
ASSERT( !lt.matches( BSONObj(), NULL ) );
ASSERT( !lt.matches( BSON( "a" << BSONNULL ), NULL ) );
ASSERT( !lt.matches( BSON( "a" << 4 ), NULL ) );
@@ -235,8 +252,8 @@ namespace mongo {
TEST( LtOp, MatchesMinKey ) {
BSONObj operand = BSON( "a" << MinKey );
- ComparisonMatchExpression lt;
- ASSERT( lt.init( "a", ComparisonMatchExpression::LT, operand[ "a" ] ).isOK() );
+ LTMatchExpression lt;
+ ASSERT( lt.init( "a", operand[ "a" ] ).isOK() );
ASSERT( !lt.matches( BSON( "a" << MinKey ), NULL ) );
ASSERT( !lt.matches( BSON( "a" << MaxKey ), NULL ) );
ASSERT( !lt.matches( BSON( "a" << 4 ), NULL ) );
@@ -244,8 +261,8 @@ namespace mongo {
TEST( LtOp, MatchesMaxKey ) {
BSONObj operand = BSON( "a" << MaxKey );
- ComparisonMatchExpression lt;
- ASSERT( lt.init( "a", ComparisonMatchExpression::LT, operand[ "a" ] ).isOK() );
+ LTMatchExpression lt;
+ ASSERT( lt.init( "a", operand[ "a" ] ).isOK() );
ASSERT( !lt.matches( BSON( "a" << MaxKey ), NULL ) );
ASSERT( lt.matches( BSON( "a" << MinKey ), NULL ) );
ASSERT( lt.matches( BSON( "a" << 4 ), NULL ) );
@@ -253,8 +270,8 @@ namespace mongo {
TEST( LtOp, ElemMatchKey ) {
BSONObj operand = BSON( "$lt" << 5 );
- ComparisonMatchExpression lt;
- ASSERT( lt.init( "a", ComparisonMatchExpression::LT, operand[ "$lt" ] ).isOK() );
+ LTMatchExpression lt;
+ ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() );
MatchDetails details;
details.requestElemMatchKey();
ASSERT( !lt.matches( BSON( "a" << 6 ), &details ) );
@@ -322,8 +339,8 @@ namespace mongo {
BSONObj equalMatch = BSON( "a" << 5 );
BSONObj notMatch = BSON( "a" << 6 );
BSONObj notMatchWrongType = BSON( "a" << "foo" );
- ComparisonMatchExpression lte;
- ASSERT( lte.init( "", ComparisonMatchExpression::LTE, operand[ "$lte" ] ).isOK() );
+ LTEMatchExpression lte;
+ ASSERT( lte.init( "", operand[ "$lte" ] ).isOK() );
ASSERT( lte.matchesSingleElement( match.firstElement() ) );
ASSERT( lte.matchesSingleElement( equalMatch.firstElement() ) );
ASSERT( !lte.matchesSingleElement( notMatch.firstElement() ) );
@@ -332,38 +349,38 @@ namespace mongo {
TEST( LteOp, InvalidEooOperand ) {
BSONObj operand;
- ComparisonMatchExpression lte;
- ASSERT( !lte.init( "", ComparisonMatchExpression::LTE, operand.firstElement() ).isOK() );
+ LTEMatchExpression lte;
+ ASSERT( !lte.init( "", operand.firstElement() ).isOK() );
}
TEST( LteOp, MatchesScalar ) {
BSONObj operand = BSON( "$lte" << 5 );
- ComparisonMatchExpression lte;
- ASSERT( lte.init( "a", ComparisonMatchExpression::LTE, operand[ "$lte" ] ).isOK() );
+ LTEMatchExpression lte;
+ ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() );
ASSERT( lte.matches( BSON( "a" << 4.5 ), NULL ) );
ASSERT( !lte.matches( BSON( "a" << 6 ), NULL ) );
}
TEST( LteOp, MatchesArrayValue ) {
BSONObj operand = BSON( "$lte" << 5 );
- ComparisonMatchExpression lte;
- ASSERT( lte.init( "a", ComparisonMatchExpression::LTE, operand[ "$lte" ] ).isOK() );
+ LTEMatchExpression lte;
+ ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() );
ASSERT( lte.matches( BSON( "a" << BSON_ARRAY( 6 << 4.5 ) ), NULL ) );
ASSERT( !lte.matches( BSON( "a" << BSON_ARRAY( 6 << 7 ) ), NULL ) );
}
TEST( LteOp, MatchesWholeArray ) {
BSONObj operand = BSON( "$lte" << BSON_ARRAY( 5 ) );
- ComparisonMatchExpression lte;
- ASSERT( lte.init( "a", ComparisonMatchExpression::LTE, operand[ "$lte" ] ).isOK() );
+ LTEMatchExpression lte;
+ ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() );
// Arrays are not comparable as inequalities.
ASSERT( !lte.matches( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) );
}
TEST( LteOp, MatchesNull ) {
BSONObj operand = BSON( "$lte" << BSONNULL );
- ComparisonMatchExpression lte;
- ASSERT( lte.init( "a", ComparisonMatchExpression::LTE, operand[ "$lte" ] ).isOK() );
+ LTEMatchExpression lte;
+ ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() );
ASSERT( lte.matches( BSONObj(), NULL ) );
ASSERT( lte.matches( BSON( "a" << BSONNULL ), NULL ) );
ASSERT( !lte.matches( BSON( "a" << 4 ), NULL ) );
@@ -371,8 +388,8 @@ namespace mongo {
TEST( LteOp, MatchesMinKey ) {
BSONObj operand = BSON( "a" << MinKey );
- ComparisonMatchExpression lte;
- ASSERT( lte.init( "a", ComparisonMatchExpression::LTE, operand[ "a" ] ).isOK() );
+ LTEMatchExpression lte;
+ ASSERT( lte.init( "a", operand[ "a" ] ).isOK() );
ASSERT( lte.matches( BSON( "a" << MinKey ), NULL ) );
ASSERT( !lte.matches( BSON( "a" << MaxKey ), NULL ) );
ASSERT( !lte.matches( BSON( "a" << 4 ), NULL ) );
@@ -380,8 +397,8 @@ namespace mongo {
TEST( LteOp, MatchesMaxKey ) {
BSONObj operand = BSON( "a" << MaxKey );
- ComparisonMatchExpression lte;
- ASSERT( lte.init( "a", ComparisonMatchExpression::LTE, operand[ "a" ] ).isOK() );
+ LTEMatchExpression lte;
+ ASSERT( lte.init( "a", operand[ "a" ] ).isOK() );
ASSERT( lte.matches( BSON( "a" << MaxKey ), NULL ) );
ASSERT( lte.matches( BSON( "a" << MinKey ), NULL ) );
ASSERT( lte.matches( BSON( "a" << 4 ), NULL ) );
@@ -390,8 +407,8 @@ namespace mongo {
TEST( LteOp, ElemMatchKey ) {
BSONObj operand = BSON( "$lte" << 5 );
- ComparisonMatchExpression lte;
- ASSERT( lte.init( "a", ComparisonMatchExpression::LTE, operand[ "$lte" ] ).isOK() );
+ LTEMatchExpression lte;
+ ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() );
MatchDetails details;
details.requestElemMatchKey();
ASSERT( !lte.matches( BSON( "a" << 6 ), &details ) );
@@ -470,38 +487,38 @@ namespace mongo {
TEST( GtOp, InvalidEooOperand ) {
BSONObj operand;
- ComparisonMatchExpression gt;
- ASSERT( !gt.init( "", ComparisonMatchExpression::GT, operand.firstElement() ).isOK() );
+ GTMatchExpression gt;
+ ASSERT( !gt.init( "", operand.firstElement() ).isOK() );
}
TEST( GtOp, MatchesScalar ) {
BSONObj operand = BSON( "$gt" << 5 );
- ComparisonMatchExpression gt;
- ASSERT( gt.init( "a", ComparisonMatchExpression::GT, operand[ "$gt" ] ).isOK() );
+ GTMatchExpression gt;
+ ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() );
ASSERT( gt.matches( BSON( "a" << 5.5 ), NULL ) );
ASSERT( !gt.matches( BSON( "a" << 4 ), NULL ) );
}
TEST( GtOp, MatchesArrayValue ) {
BSONObj operand = BSON( "$gt" << 5 );
- ComparisonMatchExpression gt;
- ASSERT( gt.init( "a", ComparisonMatchExpression::GT, operand[ "$gt" ] ).isOK() );
+ GTMatchExpression gt;
+ ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() );
ASSERT( gt.matches( BSON( "a" << BSON_ARRAY( 3 << 5.5 ) ), NULL ) );
ASSERT( !gt.matches( BSON( "a" << BSON_ARRAY( 2 << 4 ) ), NULL ) );
}
TEST( GtOp, MatchesWholeArray ) {
BSONObj operand = BSON( "$gt" << BSON_ARRAY( 5 ) );
- ComparisonMatchExpression gt;
- ASSERT( gt.init( "a", ComparisonMatchExpression::GT, operand[ "$gt" ] ).isOK() );
+ GTMatchExpression gt;
+ ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() );
// Arrays are not comparable as inequalities.
ASSERT( !gt.matches( BSON( "a" << BSON_ARRAY( 6 ) ), NULL ) );
}
TEST( GtOp, MatchesNull ) {
BSONObj operand = BSON( "$gt" << BSONNULL );
- ComparisonMatchExpression gt;
- ASSERT( gt.init( "a", ComparisonMatchExpression::GT, operand[ "$gt" ] ).isOK() );
+ GTMatchExpression gt;
+ ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() );
ASSERT( !gt.matches( BSONObj(), NULL ) );
ASSERT( !gt.matches( BSON( "a" << BSONNULL ), NULL ) );
ASSERT( !gt.matches( BSON( "a" << 4 ), NULL ) );
@@ -509,8 +526,8 @@ namespace mongo {
TEST( GtOp, MatchesMinKey ) {
BSONObj operand = BSON( "a" << MinKey );
- ComparisonMatchExpression gt;
- ASSERT( gt.init( "a", ComparisonMatchExpression::GT, operand[ "a" ] ).isOK() );
+ GTMatchExpression gt;
+ ASSERT( gt.init( "a", operand[ "a" ] ).isOK() );
ASSERT( !gt.matches( BSON( "a" << MinKey ), NULL ) );
ASSERT( gt.matches( BSON( "a" << MaxKey ), NULL ) );
ASSERT( gt.matches( BSON( "a" << 4 ), NULL ) );
@@ -518,8 +535,8 @@ namespace mongo {
TEST( GtOp, MatchesMaxKey ) {
BSONObj operand = BSON( "a" << MaxKey );
- ComparisonMatchExpression gt;
- ASSERT( gt.init( "a", ComparisonMatchExpression::GT, operand[ "a" ] ).isOK() );
+ GTMatchExpression gt;
+ ASSERT( gt.init( "a", operand[ "a" ] ).isOK() );
ASSERT( !gt.matches( BSON( "a" << MaxKey ), NULL ) );
ASSERT( !gt.matches( BSON( "a" << MinKey ), NULL ) );
ASSERT( !gt.matches( BSON( "a" << 4 ), NULL ) );
@@ -527,8 +544,8 @@ namespace mongo {
TEST( GtOp, ElemMatchKey ) {
BSONObj operand = BSON( "$gt" << 5 );
- ComparisonMatchExpression gt;
- ASSERT( gt.init( "a", ComparisonMatchExpression::GT, operand[ "$gt" ] ).isOK() );
+ GTMatchExpression gt;
+ ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() );
MatchDetails details;
details.requestElemMatchKey();
ASSERT( !gt.matches( BSON( "a" << 4 ), &details ) );
@@ -597,8 +614,8 @@ namespace mongo {
BSONObj equalMatch = BSON( "a" << 5 );
BSONObj notMatch = BSON( "a" << 4 );
BSONObj notMatchWrongType = BSON( "a" << "foo" );
- ComparisonMatchExpression gte;
- ASSERT( gte.init( "", ComparisonMatchExpression::GTE, operand[ "$gte" ] ).isOK() );
+ GTEMatchExpression gte;
+ ASSERT( gte.init( "", operand[ "$gte" ] ).isOK() );
ASSERT( gte.matchesSingleElement( match.firstElement() ) );
ASSERT( gte.matchesSingleElement( equalMatch.firstElement() ) );
ASSERT( !gte.matchesSingleElement( notMatch.firstElement() ) );
@@ -607,38 +624,38 @@ namespace mongo {
TEST( ComparisonMatchExpression, InvalidEooOperand ) {
BSONObj operand;
- ComparisonMatchExpression gte;
- ASSERT( !gte.init( "", ComparisonMatchExpression::GTE, operand.firstElement() ).isOK() );
+ GTEMatchExpression gte;
+ ASSERT( !gte.init( "", operand.firstElement() ).isOK() );
}
TEST( ComparisonMatchExpression, MatchesScalar ) {
BSONObj operand = BSON( "$gte" << 5 );
- ComparisonMatchExpression gte;
- ASSERT( gte.init( "a", ComparisonMatchExpression::GTE, operand[ "$gte" ] ).isOK() );
+ GTEMatchExpression gte;
+ ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() );
ASSERT( gte.matches( BSON( "a" << 5.5 ), NULL ) );
ASSERT( !gte.matches( BSON( "a" << 4 ), NULL ) );
}
TEST( ComparisonMatchExpression, MatchesArrayValue ) {
BSONObj operand = BSON( "$gte" << 5 );
- ComparisonMatchExpression gte;
- ASSERT( gte.init( "a", ComparisonMatchExpression::GTE, operand[ "$gte" ] ).isOK() );
+ GTEMatchExpression gte;
+ ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() );
ASSERT( gte.matches( BSON( "a" << BSON_ARRAY( 4 << 5.5 ) ), NULL ) );
ASSERT( !gte.matches( BSON( "a" << BSON_ARRAY( 1 << 2 ) ), NULL ) );
}
TEST( ComparisonMatchExpression, MatchesWholeArray ) {
BSONObj operand = BSON( "$gte" << BSON_ARRAY( 5 ) );
- ComparisonMatchExpression gte;
- ASSERT( gte.init( "a", ComparisonMatchExpression::GTE, operand[ "$gte" ] ).isOK() );
+ GTEMatchExpression gte;
+ ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() );
// Arrays are not comparable as inequalities.
ASSERT( !gte.matches( BSON( "a" << BSON_ARRAY( 6 ) ), NULL ) );
}
TEST( ComparisonMatchExpression, MatchesNull ) {
BSONObj operand = BSON( "$gte" << BSONNULL );
- ComparisonMatchExpression gte;
- ASSERT( gte.init( "a", ComparisonMatchExpression::GTE, operand[ "$gte" ] ).isOK() );
+ GTEMatchExpression gte;
+ ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() );
ASSERT( gte.matches( BSONObj(), NULL ) );
ASSERT( gte.matches( BSON( "a" << BSONNULL ), NULL ) );
ASSERT( !gte.matches( BSON( "a" << 4 ), NULL ) );
@@ -646,8 +663,8 @@ namespace mongo {
TEST( ComparisonMatchExpression, MatchesMinKey ) {
BSONObj operand = BSON( "a" << MinKey );
- ComparisonMatchExpression gte;
- ASSERT( gte.init( "a", ComparisonMatchExpression::GTE, operand[ "a" ] ).isOK() );
+ GTEMatchExpression gte;
+ ASSERT( gte.init( "a", operand[ "a" ] ).isOK() );
ASSERT( gte.matches( BSON( "a" << MinKey ), NULL ) );
ASSERT( gte.matches( BSON( "a" << MaxKey ), NULL ) );
ASSERT( gte.matches( BSON( "a" << 4 ), NULL ) );
@@ -655,8 +672,8 @@ namespace mongo {
TEST( ComparisonMatchExpression, MatchesMaxKey ) {
BSONObj operand = BSON( "a" << MaxKey );
- ComparisonMatchExpression gte;
- ASSERT( gte.init( "a", ComparisonMatchExpression::GTE, operand[ "a" ] ).isOK() );
+ GTEMatchExpression gte;
+ ASSERT( gte.init( "a", operand[ "a" ] ).isOK() );
ASSERT( gte.matches( BSON( "a" << MaxKey ), NULL ) );
ASSERT( !gte.matches( BSON( "a" << MinKey ), NULL ) );
ASSERT( !gte.matches( BSON( "a" << 4 ), NULL ) );
@@ -664,8 +681,8 @@ namespace mongo {
TEST( ComparisonMatchExpression, ElemMatchKey ) {
BSONObj operand = BSON( "$gte" << 5 );
- ComparisonMatchExpression gte;
- ASSERT( gte.init( "a", ComparisonMatchExpression::GTE, operand[ "$gte" ] ).isOK() );
+ GTEMatchExpression gte;
+ ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() );
MatchDetails details;
details.requestElemMatchKey();
ASSERT( !gte.matches( BSON( "a" << 4 ), &details ) );
@@ -732,30 +749,30 @@ namespace mongo {
BSONObj operand = BSON( "$ne" << 5 );
BSONObj match = BSON( "a" << 6 );
BSONObj notMatch = BSON( "a" << 5 );
- ComparisonMatchExpression ne;
- ASSERT( ne.init( "", ComparisonMatchExpression::NE, operand[ "$ne" ] ).isOK() );
+ NEMatchExpression ne;
+ ASSERT( ne.init( "", operand[ "$ne" ] ).isOK() );
ASSERT( ne.matchesSingleElement( match.firstElement() ) );
ASSERT( !ne.matchesSingleElement( notMatch.firstElement() ) );
}
TEST( NeOp, InvalidEooOperand ) {
BSONObj operand;
- ComparisonMatchExpression ne;
- ASSERT( !ne.init( "", ComparisonMatchExpression::NE, operand.firstElement() ).isOK() );
+ NEMatchExpression ne;
+ ASSERT( !ne.init( "", operand.firstElement() ).isOK() );
}
TEST( NeOp, MatchesScalar ) {
BSONObj operand = BSON( "$ne" << 5 );
- ComparisonMatchExpression ne;
- ASSERT( ne.init( "a", ComparisonMatchExpression::NE, operand[ "$ne" ] ).isOK() );
+ NEMatchExpression ne;
+ ASSERT( ne.init( "a", operand[ "$ne" ] ).isOK() );
ASSERT( ne.matches( BSON( "a" << 4 ), NULL ) );
ASSERT( !ne.matches( BSON( "a" << 5 ), NULL ) );
}
TEST( NeOp, MatchesArrayValue ) {
BSONObj operand = BSON( "$ne" << 5 );
- ComparisonMatchExpression ne;
- ASSERT( ne.init( "a", ComparisonMatchExpression::NE, operand[ "$ne" ] ).isOK() );
+ NEMatchExpression ne;
+ ASSERT( ne.init( "a", operand[ "$ne" ] ).isOK() );
ASSERT( ne.matches( BSON( "a" << BSON_ARRAY( 4 << 6 ) ), NULL ) );
ASSERT( !ne.matches( BSON( "a" << BSON_ARRAY( 4 << 5 ) ), NULL ) );
ASSERT( !ne.matches( BSON( "a" << BSON_ARRAY( 5 << 5 ) ), NULL ) );
@@ -763,8 +780,8 @@ namespace mongo {
TEST( NeOp, MatchesNull ) {
BSONObj operand = BSON( "$ne" << BSONNULL );
- ComparisonMatchExpression ne;
- ASSERT( ne.init( "a", ComparisonMatchExpression::NE, operand[ "$ne" ] ).isOK() );
+ NEMatchExpression ne;
+ ASSERT( ne.init( "a", operand[ "$ne" ] ).isOK() );
ASSERT( ne.matches( BSON( "a" << 4 ), NULL ) );
ASSERT( !ne.matches( BSONObj(), NULL ) );
ASSERT( !ne.matches( BSON( "a" << BSONNULL ), NULL ) );
@@ -772,8 +789,8 @@ namespace mongo {
TEST( NeOp, ElemMatchKey ) {
BSONObj operand = BSON( "$ne" << 5 );
- ComparisonMatchExpression ne;
- ASSERT( ne.init( "a", ComparisonMatchExpression::NE, operand[ "$ne" ] ).isOK() );
+ NEMatchExpression ne;
+ ASSERT( ne.init( "a", operand[ "$ne" ] ).isOK() );
MatchDetails details;
details.requestElemMatchKey();
ASSERT( !ne.matches( BSON( "a" << BSON_ARRAY( 2 << 6 << 5 ) ), &details ) );
@@ -975,6 +992,22 @@ namespace mongo {
ASSERT_EQUALS( "1", details.elemMatchKey() );
}
+ TEST( RegexMatchExpression, Equality1 ) {
+ RegexMatchExpression r1;
+ RegexMatchExpression r2;
+ RegexMatchExpression r3;
+ RegexMatchExpression r4;
+ ASSERT( r1.init( "a" , "b" ,"" ).isOK() );
+ ASSERT( r2.init( "a" , "b" ,"x" ).isOK() );
+ ASSERT( r3.init( "a" , "c" ,"" ).isOK() );
+ ASSERT( r4.init( "b" , "b" ,"" ).isOK() );
+
+ ASSERT( r1.equivalent( &r1 ) );
+ ASSERT( !r1.equivalent( &r2 ) );
+ ASSERT( !r1.equivalent( &r3 ) );
+ ASSERT( !r1.equivalent( &r4 ) );
+ }
+
/**
TEST( RegexMatchExpression, MatchesIndexKeyScalar ) {
RegexMatchExpression regex;
@@ -1068,6 +1101,24 @@ namespace mongo {
ASSERT( details.hasElemMatchKey() );
ASSERT_EQUALS( "1", details.elemMatchKey() );
}
+
+ TEST( ModMatchExpression, Equality1 ) {
+ ModMatchExpression m1;
+ ModMatchExpression m2;
+ ModMatchExpression m3;
+ ModMatchExpression m4;
+
+ m1.init( "a" , 1 , 2 );
+ m2.init( "a" , 2 , 2 );
+ m3.init( "a" , 1 , 1 );
+ m4.init( "b" , 1 , 2 );
+
+ ASSERT( m1.equivalent( &m1 ) );
+ ASSERT( !m1.equivalent( &m2 ) );
+ ASSERT( !m1.equivalent( &m3 ) );
+ ASSERT( !m1.equivalent( &m4 ) );
+ }
+
/**
TEST( ModMatchExpression, MatchesIndexKey ) {
BSONObj operand = BSON( "$mod" << BSON_ARRAY( 2 << 1 ) );
@@ -1150,6 +1201,20 @@ namespace mongo {
ASSERT( details.hasElemMatchKey() );
ASSERT_EQUALS( "1", details.elemMatchKey() );
}
+
+ TEST( ExistsMatchExpression, Equivalent ) {
+ ExistsMatchExpression e1;
+ ExistsMatchExpression e2;
+ ExistsMatchExpression e3;
+ e1.init( "a" , false );
+ e2.init( "a" , true );
+ e3.init( "b" , false );
+
+ ASSERT( e1.equivalent( &e1 ) );
+ ASSERT( !e1.equivalent( &e2 ) );
+ ASSERT( !e1.equivalent( &e3 ) );
+ }
+
/**
TEST( ExistsMatchExpression, MatchesIndexKey ) {
BSONObj operand = BSON( "$exists" << true );
@@ -1247,6 +1312,21 @@ namespace mongo {
ASSERT( details.hasElemMatchKey() );
ASSERT_EQUALS( "1", details.elemMatchKey() );
}
+
+ TEST( TypeMatchExpression, Equivalent ) {
+ TypeMatchExpression e1;
+ TypeMatchExpression e2;
+ TypeMatchExpression e3;
+ e1.init( "a", String );
+ e2.init( "a", NumberDouble );
+ e3.init( "b", String );
+
+ ASSERT( e1.equivalent( &e1 ) );
+ ASSERT( !e1.equivalent( &e2 ) );
+ ASSERT( !e1.equivalent( &e3 ) );
+ }
+
+
/**
TEST( TypeMatchExpression, MatchesIndexKey ) {
BSONObj operand = BSON( "$type" << 2 );
@@ -1551,6 +1631,31 @@ namespace mongo {
ASSERT( !details.hasElemMatchKey() );
}
+ TEST( NinMatchExpression, Equivalent ) {
+ BSONArray operand = BSON_ARRAY( 5 << 2 );
+
+ NinMatchExpression e1;
+ NinMatchExpression e2;
+ NinMatchExpression e3;
+
+ e1.init( "a" );
+ e2.init( "a" );
+ e3.init( "b" );
+
+ e1.getArrayFilterEntries()->addEquality( operand[0] );
+ e1.getArrayFilterEntries()->addEquality( operand[1] );
+
+ e2.getArrayFilterEntries()->addEquality( operand[0] );
+
+ e3.getArrayFilterEntries()->addEquality( operand[0] );
+ e3.getArrayFilterEntries()->addEquality( operand[1] );
+
+ ASSERT( e1.equivalent( &e1 ) );
+ ASSERT( !e1.equivalent( &e2 ) );
+ ASSERT( !e1.equivalent( &e3 ) );
+ }
+
+
/**
TEST( NinMatchExpression, MatchesIndexKey ) {
BSONObj operand = BSON( "$nin" << BSON_ARRAY( 5 ) );
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index e444866d60b..f7de6dd6f5b 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -32,24 +32,29 @@
namespace mongo {
StatusWithMatchExpression MatchExpressionParser::_parseComparison( const char* name,
- ComparisonMatchExpression::Type cmp,
- const BSONElement& e ) {
- std::auto_ptr<ComparisonMatchExpression> temp( new ComparisonMatchExpression() );
+ ComparisonMatchExpression* cmp,
+ const BSONElement& e ) {
+ std::auto_ptr<ComparisonMatchExpression> temp( cmp );
- Status s = temp->init( name, cmp, e );
+ Status s = temp->init( name, e );
if ( !s.isOK() )
return StatusWithMatchExpression(s);
return StatusWithMatchExpression( temp.release() );
}
- StatusWithMatchExpression MatchExpressionParser::_parseSubField( const char* name,
- const BSONElement& e ) {
+ StatusWithMatchExpression MatchExpressionParser::_parseSubField( const BSONObj& context,
+ const char* name,
+ const BSONElement& e,
+ int position,
+ bool* stop ) {
+
+ *stop = false;
// TODO: these should move to getGtLtOp, or its replacement
if ( mongoutils::str::equals( "$eq", e.fieldName() ) )
- return _parseComparison( name, ComparisonMatchExpression::EQ, e );
+ return _parseComparison( name, new EqualityMatchExpression(), e );
if ( mongoutils::str::equals( "$not", e.fieldName() ) ) {
return _parseNot( name, e );
@@ -63,17 +68,17 @@ namespace mongo {
mongoutils::str::stream() << "unknown operator: "
<< e.fieldName() );
case BSONObj::LT:
- return _parseComparison( name, ComparisonMatchExpression::LT, e );
+ return _parseComparison( name, new LTMatchExpression(), e );
case BSONObj::LTE:
- return _parseComparison( name, ComparisonMatchExpression::LTE, e );
+ return _parseComparison( name, new LTEMatchExpression(), e );
case BSONObj::GT:
- return _parseComparison( name, ComparisonMatchExpression::GT, e );
+ return _parseComparison( name, new GTMatchExpression(), e );
case BSONObj::GTE:
- return _parseComparison( name, ComparisonMatchExpression::GTE, e );
+ return _parseComparison( name, new GTEMatchExpression(), e );
case BSONObj::NE:
- return _parseComparison( name, ComparisonMatchExpression::NE, e );
+ return _parseComparison( name, new NEMatchExpression(), e );
case BSONObj::Equality:
- return _parseComparison( name, ComparisonMatchExpression::EQ, e );
+ return _parseComparison( name, new EqualityMatchExpression(), e );
case BSONObj::opIN: {
if ( e.type() != Array )
@@ -157,19 +162,30 @@ namespace mongo {
case BSONObj::opOPTIONS:
return StatusWithMatchExpression( ErrorCodes::BadValue, "$options has to be after a $regex" );
+ case BSONObj::opREGEX: {
+ if ( position != 0 )
+ return StatusWithMatchExpression( ErrorCodes::BadValue, "$regex has to be first" );
+
+ *stop = true;
+ return _parseRegexDocument( name, context );
+ }
+
case BSONObj::opELEM_MATCH:
return _parseElemMatch( name, e );
case BSONObj::opALL:
return _parseAll( name, e );
+ case BSONObj::opWITHIN:
+ return expressionParserGeoCallback( name, context );
+
default:
return StatusWithMatchExpression( ErrorCodes::BadValue, "not done" );
}
}
- StatusWithMatchExpression MatchExpressionParser::parse( const BSONObj& obj ) {
+ StatusWithMatchExpression MatchExpressionParser::_parse( const BSONObj& obj, bool topLevel ) {
std::auto_ptr<AndMatchExpression> root( new AndMatchExpression() );
@@ -211,10 +227,26 @@ namespace mongo {
return StatusWithMatchExpression( s );
root->add( temp.release() );
}
+ else if ( mongoutils::str::equals( "atomic", rest ) ) {
+ if ( !topLevel )
+ return StatusWithMatchExpression( ErrorCodes::BadValue,
+ "$atomic has to be at the top level" );
+ if ( e.trueValue() )
+ root->add( new AtomicMatchExpression() );
+ }
+ else if ( mongoutils::str::equals( "where", rest ) ) {
+ if ( !topLevel )
+ return StatusWithMatchExpression( ErrorCodes::BadValue,
+ "$within has to be at the top level" );
+ StatusWithMatchExpression s = expressionParserWhereCallback( e );
+ if ( !s.isOK() )
+ return s;
+ root->add( s.getValue() );
+ }
else {
return StatusWithMatchExpression( ErrorCodes::BadValue,
mongoutils::str::stream()
- << "unkown operator: "
+ << "unknown top level operator: "
<< e.fieldName() );
}
@@ -236,8 +268,8 @@ namespace mongo {
continue;
}
- std::auto_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() );
- Status s = eq->init( e.fieldName(), ComparisonMatchExpression::EQ, e );
+ std::auto_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() );
+ Status s = eq->init( e.fieldName(), e );
if ( !s.isOK() )
return StatusWithMatchExpression( s );
@@ -251,31 +283,24 @@ namespace mongo {
const BSONObj& sub,
AndMatchExpression* root ) {
- bool first = true;
+ int position = 0;
BSONObjIterator j( sub );
while ( j.more() ) {
BSONElement deep = j.next();
- int op = deep.getGtLtOp();
- if ( op == BSONObj::opREGEX ) {
- if ( !first )
- return Status( ErrorCodes::BadValue, "$regex has to be first" );
-
- StatusWithMatchExpression s = _parseRegexDocument( name, sub );
- if ( !s.isOK() )
- return s.getStatus();
- root->add( s.getValue() );
- return Status::OK();
- }
-
- StatusWithMatchExpression s = _parseSubField( name, deep );
+ bool stop = false;
+ StatusWithMatchExpression s = _parseSubField( sub, name, deep, position, &stop );
if ( !s.isOK() )
return s.getStatus();
root->add( s.getValue() );
- first = false;
+
+ if ( stop )
+ break;
+ position++;
}
+
return Status::OK();
}
@@ -406,8 +431,8 @@ namespace mongo {
if ( !s.isOK() )
return StatusWithMatchExpression( s );
- for ( size_t i = 0; i < theAnd.size(); i++ ) {
- temp->add( theAnd.get( i ) );
+ for ( size_t i = 0; i < theAnd.numChildren(); i++ ) {
+ temp->add( theAnd.getChild( i ) );
}
theAnd.clearAndRelease();
@@ -416,7 +441,7 @@ namespace mongo {
// object case
- StatusWithMatchExpression sub = parse( obj );
+ StatusWithMatchExpression sub = _parse( obj, false );
if ( !sub.isOK() )
return sub;
@@ -483,5 +508,18 @@ namespace mongo {
return StatusWithMatchExpression( temp.release() );
}
+ StatusWithMatchExpression expressionParserGeoCallbackDefault( const char* name,
+ const BSONObj& section ) {
+ return StatusWithMatchExpression( ErrorCodes::BadValue, "geo not linked in" );
+ }
+
+ MatchExpressionParserGeoCallback expressionParserGeoCallback = expressionParserGeoCallbackDefault;
+
+ StatusWithMatchExpression expressionParserWhereCallbackDefault(const BSONElement& where) {
+ return StatusWithMatchExpression( ErrorCodes::BadValue, "$where not linked in" );
+ }
+
+ MatchExpressionParserWhereCallback expressionParserWhereCallback = expressionParserWhereCallbackDefault;
+
}
diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h
index b062387102e..b387af9f36e 100644
--- a/src/mongo/db/matcher/expression_parser.h
+++ b/src/mongo/db/matcher/expression_parser.h
@@ -18,6 +18,8 @@
#pragma once
+#include <boost/function.hpp>
+
#include "mongo/base/status.h"
#include "mongo/base/status_with.h"
#include "mongo/db/matcher/expression.h"
@@ -30,9 +32,12 @@ namespace mongo {
class MatchExpressionParser {
public:
- static StatusWithMatchExpression parse( const BSONObj& obj );
+ static StatusWithMatchExpression parse( const BSONObj& obj ) {
+ return _parse( obj, true );
+ }
private:
+ static StatusWithMatchExpression _parse( const BSONObj& obj, bool topLevel );
/**
* parses a field in a sub expression
@@ -48,12 +53,15 @@ namespace mongo {
* if the query is { x : { $gt : 5, $lt : 8 } }
* e is $gt : 5
*/
- static StatusWithMatchExpression _parseSubField( const char* name,
- const BSONElement& e );
+ static StatusWithMatchExpression _parseSubField( const BSONObj& context,
+ const char* name,
+ const BSONElement& e,
+ int position,
+ bool* stop );
static StatusWithMatchExpression _parseComparison( const char* name,
- ComparisonMatchExpression::Type cmp,
- const BSONElement& e );
+ ComparisonMatchExpression* cmp,
+ const BSONElement& e );
static StatusWithMatchExpression _parseMOD( const char* name,
const BSONElement& e );
@@ -84,4 +92,10 @@ namespace mongo {
};
+ typedef boost::function<StatusWithMatchExpression(const char* name, const BSONObj& section)> MatchExpressionParserGeoCallback;
+ extern MatchExpressionParserGeoCallback expressionParserGeoCallback;
+
+ typedef boost::function<StatusWithMatchExpression(const BSONElement& where)> MatchExpressionParserWhereCallback;
+ extern MatchExpressionParserWhereCallback expressionParserWhereCallback;
+
}
diff --git a/src/mongo/db/matcher/expression_parser_geo.cpp b/src/mongo/db/matcher/expression_parser_geo.cpp
new file mode 100644
index 00000000000..cbb41731deb
--- /dev/null
+++ b/src/mongo/db/matcher/expression_parser_geo.cpp
@@ -0,0 +1,47 @@
+// expression_parser_geo.cpp
+
+/**
+ * Copyright (C) 2013 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mongo/db/matcher/expression_parser.h"
+
+#include "mongo/base/init.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/matcher/expression_geo.h"
+#include "mongo/util/log.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+
+ StatusWithMatchExpression expressionParserGeoCallbackReal( const char* name,
+ const BSONObj& section ) {
+ GeoQuery gq;
+ if ( !gq.parseFrom( section ) )
+ return StatusWithMatchExpression( ErrorCodes::BadValue, "bad geo query" );
+
+ auto_ptr<GeoMatchExpression> e( new GeoMatchExpression() );
+ Status s = e->init( name, gq );
+ if ( !s.isOK() )
+ return StatusWithMatchExpression( s );
+ return StatusWithMatchExpression( e.release() );
+ }
+
+ MONGO_INITIALIZER( MatchExpressionParserGeo )( ::mongo::InitializerContext* context ) {
+ expressionParserGeoCallback = expressionParserGeoCallbackReal;
+ return Status::OK();
+ }
+
+}
diff --git a/src/mongo/db/matcher/expression_parser_geo_test.cpp b/src/mongo/db/matcher/expression_parser_geo_test.cpp
new file mode 100644
index 00000000000..d8cf7734c17
--- /dev/null
+++ b/src/mongo/db/matcher/expression_parser_geo_test.cpp
@@ -0,0 +1,43 @@
+// expression_parser_geo_test.cpp
+
+/**
+ * Copyright (C) 2013 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mongo/unittest/unittest.h"
+
+#include "mongo/db/matcher/expression_parser.h"
+
+#include "mongo/db/jsobj.h"
+#include "mongo/db/json.h"
+#include "mongo/db/matcher/expression.h"
+#include "mongo/db/matcher/expression_geo.h"
+
+namespace mongo {
+
+ TEST( MatchExpressionParserGeo, WithinBox ) {
+ BSONObj query = fromjson("{a:{$within:{$box:[{x: 4, y:4},[6,6]]}}}");
+
+ StatusWithMatchExpression result = MatchExpressionParser::parse( query );
+ ASSERT_TRUE( result.isOK() );
+
+ ASSERT(!result.getValue()->matches(fromjson("{a: [3,4]}")));
+ ASSERT(result.getValue()->matches(fromjson("{a: [4,4]}")));
+ ASSERT(result.getValue()->matches(fromjson("{a: [5,5]}")));
+ ASSERT(result.getValue()->matches(fromjson("{a: [5,5.1]}")));
+ ASSERT(result.getValue()->matches(fromjson("{a: {x: 5, y:5.1}}")));
+
+ }
+}
diff --git a/src/mongo/db/matcher/expression_parser_test.cpp b/src/mongo/db/matcher/expression_parser_test.cpp
index f7ee08f3da0..12d9ce553c2 100644
--- a/src/mongo/db/matcher/expression_parser_test.cpp
+++ b/src/mongo/db/matcher/expression_parser_test.cpp
@@ -48,6 +48,12 @@ namespace mongo {
ASSERT( !result.getValue()->matches( BSON( "x" << 5 << "y" << 4 ) ) );
}
+ TEST( AtomicMatchExpressionTest, Simple1 ) {
+ BSONObj query = BSON( "x" << 5 << "$atomic" << BSON( "$gt" << 5 << "$lt" << 8 ) );
+ StatusWithMatchExpression result = MatchExpressionParser::parse( query );
+ ASSERT_TRUE( result.isOK() );
+ }
+
StatusWith<int> fib( int n ) {
if ( n < 0 ) return StatusWith<int>( ErrorCodes::BadValue, "paramter to fib has to be >= 0" );
if ( n <= 1 ) return StatusWith<int>( 1 );
diff --git a/src/mongo/db/matcher/expression_parser_tree.cpp b/src/mongo/db/matcher/expression_parser_tree.cpp
index d510ecaae1a..e2aa3f1780e 100644
--- a/src/mongo/db/matcher/expression_parser_tree.cpp
+++ b/src/mongo/db/matcher/expression_parser_tree.cpp
@@ -39,7 +39,7 @@ namespace mongo {
return Status( ErrorCodes::BadValue,
"$or/$and/$nor entries need to be full objects" );
- StatusWithMatchExpression sub = parse( e.Obj() );
+ StatusWithMatchExpression sub = _parse( e.Obj(), false );
if ( !sub.isOK() )
return sub.getStatus();
diff --git a/src/mongo/db/matcher/expression_test.cpp b/src/mongo/db/matcher/expression_test.cpp
index 93c1d20af2a..81ab018fbfb 100644
--- a/src/mongo/db/matcher/expression_test.cpp
+++ b/src/mongo/db/matcher/expression_test.cpp
@@ -35,8 +35,8 @@ namespace mongo {
TEST( LeafMatchExpressionTest, Equal1 ) {
BSONObj temp = BSON( "x" << 5 );
- ComparisonMatchExpression e;
- e.init( "x", ComparisonMatchExpression::EQ, temp["x"] );
+ EqualityMatchExpression e;
+ e.init( "x", temp["x"] );
ASSERT_TRUE( e.matches( fromjson( "{ x : 5 }" ) ) );
ASSERT_TRUE( e.matches( fromjson( "{ x : [5] }" ) ) );
@@ -54,8 +54,8 @@ namespace mongo {
BSONObj temp = BSON( "x" << 5 );
{
- ComparisonMatchExpression e;
- e.init( "x", ComparisonMatchExpression::LTE, temp["x"] );
+ LTEMatchExpression e;
+ e.init( "x", temp["x"] );
ASSERT_TRUE( e.matches( fromjson( "{ x : 5 }" ) ) );
ASSERT_TRUE( e.matches( fromjson( "{ x : 4 }" ) ) );
ASSERT_FALSE( e.matches( fromjson( "{ x : 6 }" ) ) );
@@ -63,8 +63,8 @@ namespace mongo {
}
{
- ComparisonMatchExpression e;
- e.init( "x", ComparisonMatchExpression::LT, temp["x"] );
+ LTMatchExpression e;
+ e.init( "x", temp["x"] );
ASSERT_FALSE( e.matches( fromjson( "{ x : 5 }" ) ) );
ASSERT_TRUE( e.matches( fromjson( "{ x : 4 }" ) ) );
ASSERT_FALSE( e.matches( fromjson( "{ x : 6 }" ) ) );
@@ -72,8 +72,8 @@ namespace mongo {
}
{
- ComparisonMatchExpression e;
- e.init( "x", ComparisonMatchExpression::GTE, temp["x"] );
+ GTEMatchExpression e;
+ e.init( "x", temp["x"] );
ASSERT_TRUE( e.matches( fromjson( "{ x : 5 }" ) ) );
ASSERT_FALSE( e.matches( fromjson( "{ x : 4 }" ) ) );
ASSERT_TRUE( e.matches( fromjson( "{ x : 6 }" ) ) );
@@ -81,8 +81,8 @@ namespace mongo {
}
{
- ComparisonMatchExpression e;
- e.init( "x", ComparisonMatchExpression::GT, temp["x"] );
+ GTMatchExpression e;
+ e.init( "x", temp["x"] );
ASSERT_FALSE( e.matches( fromjson( "{ x : 5 }" ) ) );
ASSERT_FALSE( e.matches( fromjson( "{ x : 4 }" ) ) );
ASSERT_TRUE( e.matches( fromjson( "{ x : 6 }" ) ) );
diff --git a/src/mongo/db/matcher/expression_tree.cpp b/src/mongo/db/matcher/expression_tree.cpp
index 840a4fdd96c..94d54ec07ab 100644
--- a/src/mongo/db/matcher/expression_tree.cpp
+++ b/src/mongo/db/matcher/expression_tree.cpp
@@ -42,11 +42,28 @@ namespace mongo {
_expressions[i]->debugString( debug, level + 1 );
}
+ bool ListOfMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ const ListOfMatchExpression* realOther = static_cast<const ListOfMatchExpression*>( other );
+
+ if ( _expressions.size() != realOther->_expressions.size() )
+ return false;
+
+ // TOOD: order doesn't matter
+ for ( unsigned i = 0; i < _expressions.size(); i++ )
+ if ( !_expressions[i]->equivalent( realOther->_expressions[i] ) )
+ return false;
+
+ return true;
+ }
+
// -----
bool AndMatchExpression::matches( const BSONObj& doc, MatchDetails* details ) const {
- for ( size_t i = 0; i < size(); i++ ) {
- if ( !get(i)->matches( doc, details ) ) {
+ for ( size_t i = 0; i < numChildren(); i++ ) {
+ if ( !getChild(i)->matches( doc, details ) ) {
if ( details )
details->resetOutput();
return false;
@@ -56,8 +73,8 @@ namespace mongo {
}
bool AndMatchExpression::matchesSingleElement( const BSONElement& e ) const {
- for ( size_t i = 0; i < size(); i++ ) {
- if ( !get(i)->matchesSingleElement( e ) ) {
+ for ( size_t i = 0; i < numChildren(); i++ ) {
+ if ( !getChild(i)->matchesSingleElement( e ) ) {
return false;
}
}
@@ -74,8 +91,8 @@ namespace mongo {
// -----
bool OrMatchExpression::matches( const BSONObj& doc, MatchDetails* details ) const {
- for ( size_t i = 0; i < size(); i++ ) {
- if ( get(i)->matches( doc, NULL ) ) {
+ for ( size_t i = 0; i < numChildren(); i++ ) {
+ if ( getChild(i)->matches( doc, NULL ) ) {
return true;
}
}
@@ -83,8 +100,8 @@ namespace mongo {
}
bool OrMatchExpression::matchesSingleElement( const BSONElement& e ) const {
- for ( size_t i = 0; i < size(); i++ ) {
- if ( get(i)->matchesSingleElement( e ) ) {
+ for ( size_t i = 0; i < numChildren(); i++ ) {
+ if ( getChild(i)->matchesSingleElement( e ) ) {
return true;
}
}
@@ -101,8 +118,8 @@ namespace mongo {
// ----
bool NorMatchExpression::matches( const BSONObj& doc, MatchDetails* details ) const {
- for ( size_t i = 0; i < size(); i++ ) {
- if ( get(i)->matches( doc, NULL ) ) {
+ for ( size_t i = 0; i < numChildren(); i++ ) {
+ if ( getChild(i)->matches( doc, NULL ) ) {
return false;
}
}
@@ -110,8 +127,8 @@ namespace mongo {
}
bool NorMatchExpression::matchesSingleElement( const BSONElement& e ) const {
- for ( size_t i = 0; i < size(); i++ ) {
- if ( get(i)->matchesSingleElement( e ) ) {
+ for ( size_t i = 0; i < numChildren(); i++ ) {
+ if ( getChild(i)->matchesSingleElement( e ) ) {
return false;
}
}
@@ -132,4 +149,11 @@ namespace mongo {
_exp->debugString( debug, level + 1 );
}
+ bool NotMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+
+ return _exp->equivalent( other->getChild(0) );
+ }
+
}
diff --git a/src/mongo/db/matcher/expression_tree.h b/src/mongo/db/matcher/expression_tree.h
index 8dd6885d26c..8c826f78769 100644
--- a/src/mongo/db/matcher/expression_tree.h
+++ b/src/mongo/db/matcher/expression_tree.h
@@ -30,6 +30,7 @@ namespace mongo {
class ListOfMatchExpression : public MatchExpression {
public:
+ ListOfMatchExpression( MatchType type ) : MatchExpression( TREE, type ){}
virtual ~ListOfMatchExpression();
/**
@@ -43,8 +44,10 @@ namespace mongo {
*/
void clearAndRelease() { _expressions.clear(); }
- size_t size() const { return _expressions.size(); }
- MatchExpression* get( size_t i ) const { return _expressions[i]; }
+ virtual size_t numChildren() const { return _expressions.size(); }
+ virtual const MatchExpression* getChild( size_t i ) const { return _expressions[i]; }
+
+ bool equivalent( const MatchExpression* other ) const;
protected:
void _debugList( StringBuilder& debug, int level ) const;
@@ -55,6 +58,7 @@ namespace mongo {
class AndMatchExpression : public ListOfMatchExpression {
public:
+ AndMatchExpression() : ListOfMatchExpression( AND ){}
virtual ~AndMatchExpression(){}
virtual bool matches( const BSONObj& doc, MatchDetails* details = 0 ) const;
@@ -65,6 +69,7 @@ namespace mongo {
class OrMatchExpression : public ListOfMatchExpression {
public:
+ OrMatchExpression() : ListOfMatchExpression( OR ){}
virtual ~OrMatchExpression(){}
virtual bool matches( const BSONObj& doc, MatchDetails* details = 0 ) const;
@@ -75,6 +80,7 @@ namespace mongo {
class NorMatchExpression : public ListOfMatchExpression {
public:
+ NorMatchExpression() : ListOfMatchExpression( NOR ){}
virtual ~NorMatchExpression(){}
virtual bool matches( const BSONObj& doc, MatchDetails* details = 0 ) const;
@@ -85,6 +91,7 @@ namespace mongo {
class NotMatchExpression : public MatchExpression {
public:
+ NotMatchExpression() : MatchExpression( TREE, NOT ){}
/**
* @param exp - I own it, and will delete
*/
@@ -102,6 +109,13 @@ namespace mongo {
}
virtual void debugString( StringBuilder& debug, int level = 0 ) const;
+
+ bool equivalent( const MatchExpression* other ) const;
+
+ virtual size_t numChildren() const { return 1; }
+ virtual MatchExpression* getChild( size_t i ) const { return _exp.get(); }
+
+
private:
boost::scoped_ptr<MatchExpression> _exp;
};
diff --git a/src/mongo/db/matcher/expression_tree_test.cpp b/src/mongo/db/matcher/expression_tree_test.cpp
index a02d423c621..e2032da14fd 100644
--- a/src/mongo/db/matcher/expression_tree_test.cpp
+++ b/src/mongo/db/matcher/expression_tree_test.cpp
@@ -28,8 +28,8 @@ namespace mongo {
TEST( NotMatchExpression, MatchesScalar ) {
BSONObj baseOperand = BSON( "$lt" << 5 );
- auto_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() );
- ASSERT( lt->init( "a", ComparisonMatchExpression::LT, baseOperand[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> lt( new LTMatchExpression() );
+ ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() );
NotMatchExpression notOp;
ASSERT( notOp.init( lt.release() ).isOK() );
ASSERT( notOp.matches( BSON( "a" << 6 ), NULL ) );
@@ -38,8 +38,8 @@ namespace mongo {
TEST( NotMatchExpression, MatchesArray ) {
BSONObj baseOperand = BSON( "$lt" << 5 );
- auto_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() );
- ASSERT( lt->init( "a", ComparisonMatchExpression::LT, baseOperand[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> lt( new LTMatchExpression() );
+ ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() );
NotMatchExpression notOp;
ASSERT( notOp.init( lt.release() ).isOK() );
ASSERT( notOp.matches( BSON( "a" << BSON_ARRAY( 6 ) ), NULL ) );
@@ -50,8 +50,8 @@ namespace mongo {
TEST( NotMatchExpression, ElemMatchKey ) {
BSONObj baseOperand = BSON( "$lt" << 5 );
- auto_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() );
- ASSERT( lt->init( "a", ComparisonMatchExpression::LT, baseOperand[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> lt( new LTMatchExpression() );
+ ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() );
NotMatchExpression notOp;
ASSERT( notOp.init( lt.release() ).isOK() );
MatchDetails details;
@@ -107,10 +107,10 @@ namespace mongo {
BSONObj notMatch2 = BSON( "a" << "a1" );
BSONObj notMatch3 = BSON( "a" << "r" );
- auto_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() );
- ASSERT( sub1->init( "a", ComparisonMatchExpression::LT, baseOperand1[ "$lt" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() );
- ASSERT( sub2->init( "a", ComparisonMatchExpression::GT, baseOperand2[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub1( new LTMatchExpression() );
+ ASSERT( sub1->init( "a", baseOperand1[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub2( new GTMatchExpression() );
+ ASSERT( sub2->init( "a", baseOperand2[ "$gt" ] ).isOK() );
auto_ptr<RegexMatchExpression> sub3( new RegexMatchExpression() );
ASSERT( sub3->init( "a", "1", "" ).isOK() );
@@ -127,8 +127,8 @@ namespace mongo {
TEST( AndOp, MatchesSingleClause ) {
BSONObj baseOperand = BSON( "$ne" << 5 );
- auto_ptr<ComparisonMatchExpression> ne( new ComparisonMatchExpression() );
- ASSERT( ne->init( "a", ComparisonMatchExpression::NE, baseOperand[ "$ne" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> ne( new NEMatchExpression() );
+ ASSERT( ne->init( "a", baseOperand[ "$ne" ] ).isOK() );
AndMatchExpression andOp;
andOp.add( ne.release() );
@@ -144,14 +144,14 @@ namespace mongo {
BSONObj baseOperand2 = BSON( "$lt" << 10 );
BSONObj baseOperand3 = BSON( "$lt" << 100 );
- auto_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() );
- ASSERT( sub1->init( "a", ComparisonMatchExpression::GT, baseOperand1[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub1( new GTMatchExpression() );
+ ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() );
- ASSERT( sub2->init( "a", ComparisonMatchExpression::LT, baseOperand2[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub2( new LTMatchExpression() );
+ ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> sub3( new ComparisonMatchExpression() );
- ASSERT( sub3->init( "b", ComparisonMatchExpression::LT, baseOperand3[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub3( new LTMatchExpression() );
+ ASSERT( sub3->init( "b", baseOperand3[ "$lt" ] ).isOK() );
AndMatchExpression andOp;
andOp.add( sub1.release() );
@@ -169,11 +169,11 @@ namespace mongo {
BSONObj baseOperand1 = BSON( "a" << 1 );
BSONObj baseOperand2 = BSON( "b" << 2 );
- auto_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() );
- ASSERT( sub1->init( "a", ComparisonMatchExpression::EQ, baseOperand1[ "a" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub1( new EqualityMatchExpression() );
+ ASSERT( sub1->init( "a", baseOperand1[ "a" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() );
- ASSERT( sub2->init( "b", ComparisonMatchExpression::EQ, baseOperand2[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub2( new EqualityMatchExpression() );
+ ASSERT( sub2->init( "b", baseOperand2[ "b" ] ).isOK() );
AndMatchExpression andOp;
andOp.add( sub1.release() );
@@ -290,8 +290,8 @@ namespace mongo {
*/
TEST( OrOp, MatchesSingleClause ) {
BSONObj baseOperand = BSON( "$ne" << 5 );
- auto_ptr<ComparisonMatchExpression> ne( new ComparisonMatchExpression() );
- ASSERT( ne->init( "a", ComparisonMatchExpression::NE, baseOperand[ "$ne" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> ne( new NEMatchExpression() );
+ ASSERT( ne->init( "a", baseOperand[ "$ne" ] ).isOK() );
OrMatchExpression orOp;
orOp.add( ne.release() );
@@ -306,12 +306,12 @@ namespace mongo {
BSONObj baseOperand1 = BSON( "$gt" << 10 );
BSONObj baseOperand2 = BSON( "$lt" << 0 );
BSONObj baseOperand3 = BSON( "b" << 100 );
- auto_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() );
- ASSERT( sub1->init( "a", ComparisonMatchExpression::GT, baseOperand1[ "$gt" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() );
- ASSERT( sub2->init( "a", ComparisonMatchExpression::LT, baseOperand2[ "$lt" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> sub3( new ComparisonMatchExpression() );
- ASSERT( sub3->init( "b", ComparisonMatchExpression::EQ, baseOperand3[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub1( new GTMatchExpression() );
+ ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub2( new LTMatchExpression() );
+ ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub3( new EqualityMatchExpression() );
+ ASSERT( sub3->init( "b", baseOperand3[ "b" ] ).isOK() );
OrMatchExpression orOp;
orOp.add( sub1.release() );
@@ -330,10 +330,10 @@ namespace mongo {
TEST( OrOp, ElemMatchKey ) {
BSONObj baseOperand1 = BSON( "a" << 1 );
BSONObj baseOperand2 = BSON( "b" << 2 );
- auto_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() );
- ASSERT( sub1->init( "a", ComparisonMatchExpression::EQ, baseOperand1[ "a" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() );
- ASSERT( sub2->init( "b", ComparisonMatchExpression::EQ, baseOperand2[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub1( new EqualityMatchExpression() );
+ ASSERT( sub1->init( "a", baseOperand1[ "a" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub2( new EqualityMatchExpression() );
+ ASSERT( sub2->init( "b", baseOperand2[ "b" ] ).isOK() );
OrMatchExpression orOp;
orOp.add( sub1.release() );
@@ -451,8 +451,8 @@ namespace mongo {
TEST( NorOp, MatchesSingleClause ) {
BSONObj baseOperand = BSON( "$ne" << 5 );
- auto_ptr<ComparisonMatchExpression> ne( new ComparisonMatchExpression() );
- ASSERT( ne->init( "a", ComparisonMatchExpression::NE, baseOperand[ "$ne" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> ne( new NEMatchExpression() );
+ ASSERT( ne->init( "a", baseOperand[ "$ne" ] ).isOK() );
NorMatchExpression norOp;
norOp.add( ne.release() );
@@ -468,12 +468,12 @@ namespace mongo {
BSONObj baseOperand2 = BSON( "$lt" << 0 );
BSONObj baseOperand3 = BSON( "b" << 100 );
- auto_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() );
- ASSERT( sub1->init( "a", ComparisonMatchExpression::GT, baseOperand1[ "$gt" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() );
- ASSERT( sub2->init( "a", ComparisonMatchExpression::LT, baseOperand2[ "$lt" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> sub3( new ComparisonMatchExpression() );
- ASSERT( sub3->init( "b", ComparisonMatchExpression::EQ, baseOperand3[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub1( new GTMatchExpression() );
+ ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub2( new LTMatchExpression() );
+ ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub3( new EqualityMatchExpression() );
+ ASSERT( sub3->init( "b", baseOperand3[ "b" ] ).isOK() );
NorMatchExpression norOp;
norOp.add( sub1.release() );
@@ -492,10 +492,10 @@ namespace mongo {
TEST( NorOp, ElemMatchKey ) {
BSONObj baseOperand1 = BSON( "a" << 1 );
BSONObj baseOperand2 = BSON( "b" << 2 );
- auto_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() );
- ASSERT( sub1->init( "a", ComparisonMatchExpression::EQ, baseOperand1[ "a" ] ).isOK() );
- auto_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() );
- ASSERT( sub2->init( "b", ComparisonMatchExpression::EQ, baseOperand2[ "b" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub1( new EqualityMatchExpression() );
+ ASSERT( sub1->init( "a", baseOperand1[ "a" ] ).isOK() );
+ auto_ptr<ComparisonMatchExpression> sub2( new EqualityMatchExpression() );
+ ASSERT( sub2->init( "b", baseOperand2[ "b" ] ).isOK() );
NorMatchExpression norOp;
norOp.add( sub1.release() );
@@ -514,6 +514,26 @@ namespace mongo {
ASSERT( !details.hasElemMatchKey() );
}
+
+ TEST( NorOp, Equivalent ) {
+ BSONObj baseOperand1 = BSON( "a" << 1 );
+ BSONObj baseOperand2 = BSON( "b" << 2 );
+ EqualityMatchExpression sub1;
+ ASSERT( sub1.init( "a", baseOperand1[ "a" ] ).isOK() );
+ EqualityMatchExpression sub2;
+ ASSERT( sub2.init( "b", baseOperand2[ "b" ] ).isOK() );
+
+ NorMatchExpression e1;
+ e1.add( sub1.shallowClone() );
+ e1.add( sub2.shallowClone() );
+
+ NorMatchExpression e2;
+ e2.add( sub1.shallowClone() );
+
+ ASSERT( e1.equivalent( &e1 ) );
+ ASSERT( !e1.equivalent( &e2 ) );
+ }
+
/**
TEST( NorOp, MatchesIndexKey ) {
BSONObj baseOperand = BSON( "a" << 5 );
diff --git a/src/mongo/db/matcher/expression_where.cpp b/src/mongo/db/matcher/expression_where.cpp
new file mode 100644
index 00000000000..1bfc74107af
--- /dev/null
+++ b/src/mongo/db/matcher/expression_where.cpp
@@ -0,0 +1,172 @@
+// expression_where.cpp
+
+/**
+ * Copyright (C) 2013 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mongo/pch.h"
+#include "mongo/base/init.h"
+#include "mongo/db/namespacestring.h"
+#include "mongo/db/client.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/matcher/expression.h"
+#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/scripting/engine.h"
+
+namespace mongo {
+
+ class WhereMatchExpression : public MatchExpression {
+ public:
+ WhereMatchExpression() : MatchExpression( SPECIAL, WHERE ){ _func = 0; }
+ virtual ~WhereMatchExpression(){}
+
+ Status init( const StringData& ns, const StringData& theCode, const BSONObj& scope );
+
+ virtual bool matches( const BSONObj& doc, MatchDetails* details = 0 ) const;
+
+ virtual bool matchesSingleElement( const BSONElement& e ) const {
+ return false;
+ }
+
+ virtual void debugString( StringBuilder& debug, int level = 0 ) const;
+
+ virtual bool equivalent( const MatchExpression* other ) const ;
+ private:
+ string _ns;
+ string _code;
+ BSONObj _userScope;
+
+ auto_ptr<Scope> _scope;
+ ScriptingFunction _func;
+ };
+
+ Status WhereMatchExpression::init( const StringData& ns,
+ const StringData& theCode,
+ const BSONObj& scope ) {
+
+ if ( ns.size() == 0 )
+ return Status( ErrorCodes::BadValue, "ns for $where cannot be empty" );
+
+ if ( theCode.size() == 0 )
+ return Status( ErrorCodes::BadValue, "code for $where cannot be empty" );
+
+ _ns = ns.toString();
+ _code = theCode.toString();
+ _userScope = scope.getOwned();
+
+ NamespaceString nswrapper( _ns );
+ _scope = globalScriptEngine->getPooledScope( nswrapper.db.c_str(), "where" );
+ _func = _scope->createFunction( _code.c_str() );
+
+ if ( !_func )
+ return Status( ErrorCodes::BadValue, "$where compile error" );
+
+ return Status::OK();
+ }
+
+ bool WhereMatchExpression::matches( const BSONObj& obj, MatchDetails* details ) const {
+ verify( _func );
+
+ if ( ! _userScope.isEmpty() ) {
+ _scope->init( &_userScope );
+ }
+ _scope->setObject( "obj", const_cast< BSONObj & >( obj ) );
+ _scope->setBoolean( "fullObject" , true ); // this is a hack b/c fullObject used to be relevant
+
+ int err = _scope->invoke( _func, 0, &obj, 1000 * 60, false );
+ if ( err == -3 ) { // INVOKE_ERROR
+ stringstream ss;
+ ss << "error on invocation of $where function:\n"
+ << _scope->getError();
+ uassert( 16812, ss.str(), false);
+ }
+ else if ( err != 0 ) { // ! INVOKE_SUCCESS
+ uassert( 16813, "unknown error in invocation of $where function", false);
+ }
+
+ return _scope->getBoolean( "__returnValue" ) != 0;
+ }
+
+ void WhereMatchExpression::debugString( StringBuilder& debug, int level ) const {
+ _debugAddSpace( debug, level );
+ debug << "$where\n";
+
+ _debugAddSpace( debug, level + 1 );
+ debug << "ns: " << _ns << "\n";
+
+ _debugAddSpace( debug, level + 1 );
+ debug << "code: " << _code << "\n";
+
+ _debugAddSpace( debug, level + 1 );
+ debug << "scope: " << _userScope << "\n";
+ }
+
+ bool WhereMatchExpression::equivalent( const MatchExpression* other ) const {
+ if ( matchType() != other->matchType() )
+ return false;
+ const WhereMatchExpression* realOther = static_cast<const WhereMatchExpression*>(other);
+ return
+ _ns == realOther->_ns &&
+ _code == realOther->_code &&
+ _userScope == realOther->_userScope;
+ }
+
+
+ // -----------------
+
+ StatusWithMatchExpression expressionParserWhereCallbackReal(const BSONElement& where) {
+ if ( !haveClient() )
+ return StatusWithMatchExpression( ErrorCodes::BadValue, "no current client needed for $where" );
+
+ Client::Context* context = cc().getContext();
+ if ( !context )
+ return StatusWithMatchExpression( ErrorCodes::BadValue, "no context in $where parsing" );
+
+ const char* ns = context->ns();
+ if ( !ns )
+ return StatusWithMatchExpression( ErrorCodes::BadValue, "no ns in $where parsing" );
+
+ if ( !globalScriptEngine )
+ return StatusWithMatchExpression( ErrorCodes::BadValue, "no globalScriptEngine in $where parsing" );
+
+ auto_ptr<WhereMatchExpression> exp( new WhereMatchExpression() );
+ if ( where.type() == String || where.type() == Code ) {
+ Status s = exp->init( ns, where.valuestr(), BSONObj() );
+ if ( !s.isOK() )
+ return StatusWithMatchExpression( s );
+ return StatusWithMatchExpression( exp.release() );
+ }
+
+ if ( where.type() == CodeWScope ) {
+ Status s = exp->init( ns,
+ where.codeWScopeCode(),
+ BSONObj( where.codeWScopeScopeDataUnsafe() ) );
+ if ( !s.isOK() )
+ return StatusWithMatchExpression( s );
+ return StatusWithMatchExpression( exp.release() );
+ }
+
+ return StatusWithMatchExpression( ErrorCodes::BadValue, "$where got bad type" );
+ }
+
+ MONGO_INITIALIZER( MatchExpressionWhere )( ::mongo::InitializerContext* context ) {
+ expressionParserWhereCallback = expressionParserWhereCallbackReal;
+ return Status::OK();
+ }
+
+
+
+
+}
diff --git a/src/mongo/db/matcher/matcher.cpp b/src/mongo/db/matcher/matcher.cpp
index 0db46c3fba2..a427128bf98 100644
--- a/src/mongo/db/matcher/matcher.cpp
+++ b/src/mongo/db/matcher/matcher.cpp
@@ -18,6 +18,7 @@
#include "mongo/pch.h"
+#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/matcher/matcher.h"
#include "mongo/util/mongoutils/str.h"
@@ -35,9 +36,138 @@ namespace mongo {
_expression.reset( result.getValue() );
}
+ Matcher2::Matcher2( const Matcher2 &docMatcher, const BSONObj &constrainIndexKey ) {
+
+ MatchExpression* indexExpression = spliceForIndex( constrainIndexKey,
+ docMatcher._expression.get() );
+ if ( indexExpression )
+ _expression.reset( indexExpression );
+ }
+
bool Matcher2::matches(const BSONObj& doc, MatchDetails* details ) const {
+ if ( !_expression )
+ return true;
return _expression->matches( doc, details );
}
+ bool Matcher2::atomic() const {
+ if ( !_expression )
+ return false;
+
+ if ( _expression->matchType() == MatchExpression::ATOMIC )
+ return true;
+
+ // we only go down one level
+ for ( unsigned i = 0; i < _expression->numChildren(); i++ ) {
+ if ( _expression->getChild( i )->matchType() == MatchExpression::ATOMIC )
+ return true;
+ }
+
+ return false;
+ }
+
+ bool Matcher2::singleSimpleCriterion() const {
+ if ( !_expression )
+ return false;
+
+ if ( _expression->matchType() == MatchExpression::EQ )
+ return true;
+
+ if ( _expression->matchType() == MatchExpression::AND &&
+ _expression->numChildren() == 1 &&
+ _expression->getChild(0)->matchType() == MatchExpression::EQ )
+ return true;
+
+ return false;
+ }
+
+ bool Matcher2::keyMatch( const Matcher2 &docMatcher ) const {
+ if ( !_expression )
+ return docMatcher._expression.get() == NULL;
+ if ( !docMatcher._expression )
+ return false;
+
+ return _expression->equivalent( docMatcher._expression.get() );
+ }
+
+ MatchExpression* Matcher2::spliceForIndex( const BSONObj& key,
+ const MatchExpression* full ) {
+ set<string> keys;
+ for ( BSONObjIterator i(key); i.more(); ) {
+ BSONElement e = i.next();
+ keys.insert( e.fieldName() );
+ }
+ return _spliceForIndex( keys, full );
+ }
+
+ MatchExpression* Matcher2::_spliceForIndex( const set<string>& keys,
+ const MatchExpression* full ) {
+
+ switch ( full->matchType() ) {
+ case MatchExpression::NOT:
+ case MatchExpression::NOR:
+ // maybe?
+ return NULL;
+
+ case MatchExpression::AND:
+ case MatchExpression::OR: {
+ auto_ptr<ListOfMatchExpression> dup;
+ for ( unsigned i = 0; i < full->numChildren(); i++ ) {
+ MatchExpression* sub = _spliceForIndex( keys, full->getChild( i ) );
+ if ( !sub )
+ continue;
+ if ( !dup.get() ) {
+ if ( full->matchType() == MatchExpression::AND )
+ dup.reset( new AndMatchExpression() );
+ else
+ dup.reset( new OrMatchExpression() );
+ }
+ dup->add( sub );
+ }
+ if ( dup.get() )
+ return dup.release();
+ return NULL;
+ }
+
+ case MatchExpression::LTE:
+ case MatchExpression::LT:
+ case MatchExpression::EQ:
+ case MatchExpression::GT:
+ case MatchExpression::GTE:
+ case MatchExpression::NE:
+ case MatchExpression::REGEX:
+ case MatchExpression::MOD:
+ case MatchExpression::IN: {
+ const LeafMatchExpression* lme = static_cast<const LeafMatchExpression*>( full );
+ if ( !keys.count( lme->getPath().toString() ) )
+ return NULL;
+ return lme->shallowClone();
+ }
+
+ case MatchExpression::ALL:
+ // TODO: conver to $in
+ return NULL;
+
+ case MatchExpression::ELEM_MATCH_OBJECT:
+ case MatchExpression::ELEM_MATCH_VALUE:
+ // future
+ return NULL;
+
+ case MatchExpression::GEO:
+ case MatchExpression::SIZE:
+ case MatchExpression::EXISTS:
+ case MatchExpression::NIN:
+ case MatchExpression::TYPE_OPERATOR:
+ case MatchExpression::ATOMIC:
+ case MatchExpression::WHERE:
+ // no go
+ return NULL;
+
+
+ }
+
+ return NULL;
+ }
+
}
diff --git a/src/mongo/db/matcher/matcher.h b/src/mongo/db/matcher/matcher.h
index 64201bd5dad..215cb99e592 100644
--- a/src/mongo/db/matcher/matcher.h
+++ b/src/mongo/db/matcher/matcher.h
@@ -21,6 +21,7 @@
#include <boost/scoped_ptr.hpp>
#include "mongo/base/disallow_copying.h"
+#include "mongo/base/status.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/match_details.h"
@@ -33,11 +34,43 @@ namespace mongo {
public:
explicit Matcher2( const BSONObj& pattern );
+ /**
+ * Generate a matcher for the provided index key format using the
+ * provided full doc matcher.
+ * THIS API WILL GO AWAY EVENTUALLY
+ */
+ explicit Matcher2( const Matcher2 &docMatcher, const BSONObj &constrainIndexKey );
+
+
bool matches(const BSONObj& doc, MatchDetails* details = NULL ) const;
+ bool atomic() const;
+
+ /*
+ * this is from old mature
+ * criteria is 1 equality expression, nothing else
+ */
+ bool singleSimpleCriterion() const;
+
+ const BSONObj* getQuery() const { return &_pattern; };
+ std::string toString() const { return _pattern.toString(); }
+
+ /**
+ * @return true if this key matcher will return the same true/false
+ * value as the provided doc matcher.
+ */
+ bool keyMatch( const Matcher2 &docMatcher ) const;
+
+ static MatchExpression* spliceForIndex( const BSONObj& key,
+ const MatchExpression* full );
+
private:
const BSONObj _pattern; // this is owned by who created us
boost::scoped_ptr<MatchExpression> _expression;
+
+ static MatchExpression* _spliceForIndex( const set<std::string>& keys,
+ const MatchExpression* full );
+
};
}
diff --git a/src/mongo/dbtests/matchertests.cpp b/src/mongo/dbtests/matchertests.cpp
index 1183fd7c53c..26c19d41db6 100644
--- a/src/mongo/dbtests/matchertests.cpp
+++ b/src/mongo/dbtests/matchertests.cpp
@@ -144,10 +144,11 @@ namespace MatcherTests {
}
};
+ template <typename M>
class WithinBox {
public:
void run() {
- Matcher m(fromjson("{loc:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"));
+ M m(fromjson("{loc:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"));
ASSERT(!m.matches(fromjson("{loc: [3,4]}")));
ASSERT(m.matches(fromjson("{loc: [4,4]}")));
ASSERT(m.matches(fromjson("{loc: [5,5]}")));
@@ -156,10 +157,11 @@ namespace MatcherTests {
}
};
+ template <typename M>
class WithinPolygon {
public:
void run() {
- Matcher m(fromjson("{loc:{$within:{$polygon:[{x:0,y:0},[0,5],[5,5],[5,0]]}}}"));
+ M m(fromjson("{loc:{$within:{$polygon:[{x:0,y:0},[0,5],[5,5],[5,0]]}}}"));
ASSERT(m.matches(fromjson("{loc: [3,4]}")));
ASSERT(m.matches(fromjson("{loc: [4,4]}")));
ASSERT(m.matches(fromjson("{loc: {x:5,y:5}}")));
@@ -168,10 +170,11 @@ namespace MatcherTests {
}
};
+ template <typename M>
class WithinCenter {
public:
void run() {
- Matcher m(fromjson("{loc:{$within:{$center:[{x:30,y:30},10]}}}"));
+ M m(fromjson("{loc:{$within:{$center:[{x:30,y:30},10]}}}"));
ASSERT(!m.matches(fromjson("{loc: [3,4]}")));
ASSERT(m.matches(fromjson("{loc: {x:30,y:30}}")));
ASSERT(m.matches(fromjson("{loc: [20,30]}")));
@@ -198,6 +201,17 @@ namespace MatcherTests {
}
};
+ template <typename M>
+ class WhereSimple1 {
+ public:
+ void run() {
+ Client::ReadContext ctx( "unittests.matchertests" );
+ M m( BSON( "$where" << "function(){ return this.a == 1; }" ) );
+ ASSERT( m.matches( BSON( "a" << 1 ) ) );
+ ASSERT( !m.matches( BSON( "a" << 2 ) ) );
+ }
+ };
+
namespace Covered { // Tests for CoveredIndexMatcher.
/**
@@ -409,7 +423,83 @@ namespace MatcherTests {
BSONObjBuilder _traversal;
};
};
-
+
+ template <typename M>
+ class AtomicMatchTest {
+ public:
+ void run() {
+
+ {
+ M m( BSON( "x" << 5 ) );
+ ASSERT( !m.atomic() );
+ }
+
+ {
+ M m( BSON( "x" << 5 << "$atomic" << false ) );
+ ASSERT( !m.atomic() );
+ }
+
+ {
+ M m( BSON( "x" << 5 << "$atomic" << true ) );
+ ASSERT( m.atomic() );
+ }
+
+ {
+ bool threwError = false;
+ try {
+ M m( BSON( "x" << 5 <<
+ "$or" << BSON_ARRAY( BSON( "$atomic" << true << "y" << 6 ) ) ) );
+ }
+ catch ( ... ) {
+ threwError = true;
+ }
+ ASSERT( threwError );
+ }
+ }
+ };
+
+ template <typename M>
+ class SingleSimpleCriterion {
+ public:
+ void run() {
+
+ {
+ M m( BSON( "x" << 5 ) );
+ ASSERT( m.singleSimpleCriterion() );
+ }
+
+ {
+ M m( BSON( "x" << 5 << "y" << 5 ) );
+ ASSERT( !m.singleSimpleCriterion() );
+ }
+
+ {
+ M m( BSON( "x" << BSON( "$gt" << 5 ) ) );
+ ASSERT( !m.singleSimpleCriterion() );
+ }
+
+ }
+ };
+
+ template <typename M>
+ class IndexPortion1 {
+ public:
+ void run() {
+ M full( BSON( "x" << 5 << "y" << 7 ) );
+ M partial( full, BSON( "x" << 1) );
+
+ ASSERT( full.matches( BSON( "x" << 5 << "y" << 7 ) ) );
+ ASSERT( partial.matches( BSON( "x" << 5 << "y" << 7 ) ) );
+
+ ASSERT( !full.matches( BSON( "x" << 5 << "y" << 8 ) ) );
+ ASSERT( partial.matches( BSON( "x" << 5 << "y" << 8 ) ) );
+
+ ASSERT( !full.keyMatch( partial ) );
+ ASSERT( full.keyMatch( full ) );
+ ASSERT( partial.keyMatch( partial ) );
+ }
+ };
+
class All : public Suite {
public:
All() : Suite( "matcher" ) {
@@ -428,14 +518,18 @@ namespace MatcherTests {
ADD_BOTH(Size);
ADD_BOTH(MixedNumericEmbedded);
ADD_BOTH(ElemMatchKey);
+ ADD_BOTH(WhereSimple1);
add<Covered::ElemMatchKeyUnindexed>();
add<Covered::ElemMatchKeyIndexed>();
add<Covered::ElemMatchKeyIndexedSingleKey>();
ADD_BOTH(AllTiming);
add<Visit>();
- add<WithinBox>();
- add<WithinCenter>();
- add<WithinPolygon>();
+ ADD_BOTH(WithinBox);
+ ADD_BOTH(WithinCenter);
+ ADD_BOTH(WithinPolygon);
+ ADD_BOTH(AtomicMatchTest);
+ ADD_BOTH(SingleSimpleCriterion);
+ ADD_BOTH(IndexPortion1);
}
} dball;