diff options
Diffstat (limited to 'src/mongo/db/matcher')
44 files changed, 9031 insertions, 8845 deletions
diff --git a/src/mongo/db/matcher/expression.cpp b/src/mongo/db/matcher/expression.cpp index 99975a88815..aa6a2ddbc30 100644 --- a/src/mongo/db/matcher/expression.cpp +++ b/src/mongo/db/matcher/expression.cpp @@ -35,46 +35,42 @@ namespace mongo { - using std::string; +using std::string; - MatchExpression::MatchExpression( MatchType type ) - : _matchType( type ) { } - - string MatchExpression::toString() const { - StringBuilder buf; - debugString( buf, 0 ); - return buf.str(); - } - - void MatchExpression::_debugAddSpace( StringBuilder& debug, int level ) const { - for ( int i = 0; i < level; i++ ) - debug << " "; - } - - bool MatchExpression::matchesBSON( const BSONObj& doc, MatchDetails* details ) const { - BSONMatchableDocument mydoc( doc ); - return matches( &mydoc, details ); - } +MatchExpression::MatchExpression(MatchType type) : _matchType(type) {} +string MatchExpression::toString() const { + StringBuilder buf; + debugString(buf, 0); + return buf.str(); +} - void AtomicMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << "$atomic\n"; - } +void MatchExpression::_debugAddSpace(StringBuilder& debug, int level) const { + for (int i = 0; i < level; i++) + debug << " "; +} - void AtomicMatchExpression::toBSON(BSONObjBuilder* out) const { - out->append("$isolated", 1); - } +bool MatchExpression::matchesBSON(const BSONObj& doc, MatchDetails* details) const { + BSONMatchableDocument mydoc(doc); + return matches(&mydoc, details); +} - void FalseMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << "$false\n"; - } - void FalseMatchExpression::toBSON(BSONObjBuilder* out) const { - out->append("$false", 1); - } +void AtomicMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "$atomic\n"; +} +void AtomicMatchExpression::toBSON(BSONObjBuilder* out) const { + out->append("$isolated", 1); } +void FalseMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "$false\n"; +} +void FalseMatchExpression::toBSON(BSONObjBuilder* out) const { + out->append("$false", 1); +} +} diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h index c3b11d74258..3911bd0c863 100644 --- a/src/mongo/db/matcher/expression.h +++ b/src/mongo/db/matcher/expression.h @@ -40,223 +40,254 @@ namespace mongo { - class TreeMatchExpression; +class TreeMatchExpression; + +class MatchExpression { + MONGO_DISALLOW_COPYING(MatchExpression); + +public: + enum MatchType { + // tree types + AND, + OR, + + // array types + ELEM_MATCH_OBJECT, + ELEM_MATCH_VALUE, + SIZE, + + // leaf types + EQ, + LTE, + LT, + GT, + GTE, + REGEX, + MOD, + EXISTS, + MATCH_IN, + + // Negations. + NOT, + NOR, + + // special types + TYPE_OPERATOR, + GEO, + WHERE, + + // things that maybe shouldn't even be nodes + ATOMIC, + ALWAYS_FALSE, + + // Things that we parse but cannot be answered without an index. + GEO_NEAR, + TEXT, + + // Expressions that are only created internally + INTERNAL_2DSPHERE_KEY_IN_REGION, + INTERNAL_2D_KEY_IN_REGION, + INTERNAL_2D_POINT_IN_ANNULUS + }; - class MatchExpression { - MONGO_DISALLOW_COPYING( MatchExpression ); - public: - enum MatchType { - // tree types - AND, OR, - - // array types - ELEM_MATCH_OBJECT, ELEM_MATCH_VALUE, SIZE, - - // leaf types - EQ, LTE, LT, GT, GTE, REGEX, MOD, EXISTS, MATCH_IN, - - // Negations. - NOT, NOR, - - // special types - TYPE_OPERATOR, GEO, WHERE, - - // things that maybe shouldn't even be nodes - ATOMIC, ALWAYS_FALSE, - - // Things that we parse but cannot be answered without an index. - GEO_NEAR, TEXT, - - // Expressions that are only created internally - INTERNAL_2DSPHERE_KEY_IN_REGION, INTERNAL_2D_KEY_IN_REGION, INTERNAL_2D_POINT_IN_ANNULUS - }; - - MatchExpression( MatchType type ); - virtual ~MatchExpression(){} - - // - // Structural/AST information - // - - /** - * What type is the node? See MatchType above. - */ - MatchType matchType() const { return _matchType; } - - /** - * How many children does the node have? Most nodes are leaves so the default impl. is for - * a leaf. - */ - virtual size_t numChildren() const { return 0; } - - /** - * Get the i-th child. - */ - virtual MatchExpression* getChild( size_t i ) const { return NULL; } - - /** - * Get all the children of a node - */ - virtual std::vector<MatchExpression*>* getChildVector() { return NULL; } - - /** - * Get the path of the leaf. Returns StringData() if there is no path (node is logical). - */ - virtual const StringData path() const { return StringData(); } - - /** - * Notes on structure: - * isLogical, isArray, and isLeaf define three partitions of all possible operators. - * - * isLogical can have children and its children can be arbitrary operators. - * - * isArray can have children and its children are predicates over one field. - * - * isLeaf is a predicate over one field. - */ - - /** - * Is this node a logical operator? All of these inherit from ListOfMatchExpression. - * AND, OR, NOT, NOR. - */ - bool isLogical() const { - return AND == _matchType || OR == _matchType || NOT == _matchType || NOR == _matchType; - } + MatchExpression(MatchType type); + virtual ~MatchExpression() {} - /** - * Is this node an array operator? Array operators have multiple clauses but operate on one - * field. - * - * ELEM_MATCH_VALUE, ELEM_MATCH_OBJECT, SIZE (ArrayMatchingMatchExpression) - */ - bool isArray() const { - return SIZE == _matchType - || ELEM_MATCH_VALUE == _matchType - || ELEM_MATCH_OBJECT == _matchType; - } + // + // Structural/AST information + // - /** - * Not-internal nodes, predicates over one field. Almost all of these inherit from - * LeafMatchExpression. - * - * Exceptions: WHERE, which doesn't have a field. - * TYPE_OPERATOR, which inherits from MatchExpression due to unique array - * semantics. - */ - bool isLeaf() const { - return !isArray() && !isLogical(); - } + /** + * What type is the node? See MatchType above. + */ + MatchType matchType() const { + return _matchType; + } - // XXX: document - virtual MatchExpression* shallowClone() const = 0; - - // XXX document - virtual bool equivalent( const MatchExpression* other ) const = 0; - - // - // Determine if a document satisfies the tree-predicate. - // - - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const = 0; - - virtual bool matchesBSON( const BSONObj& doc, MatchDetails* details = 0 ) const; - - /** - * Determines if the element satisfies the tree-predicate. - * Not valid for all expressions (e.g. $where); in those cases, returns false. - */ - virtual bool matchesSingleElement( const BSONElement& e ) const = 0; - - // - // Tagging mechanism: Hang data off of the tree for retrieval later. - // - - class TagData { - public: - virtual ~TagData() { } - virtual void debugString(StringBuilder* builder) const = 0; - virtual TagData* clone() const = 0; - }; - - /** - * Takes ownership - */ - void setTag(TagData* data) { _tagData.reset(data); } - TagData* getTag() const { return _tagData.get(); } - virtual void resetTag() { - setTag(NULL); - for (size_t i = 0; i < numChildren(); ++i) { - getChild(i)->resetTag(); - } - } + /** + * How many children does the node have? Most nodes are leaves so the default impl. is for + * a leaf. + */ + virtual size_t numChildren() const { + return 0; + } - // - // Debug information - // - virtual std::string toString() const; - virtual void debugString( StringBuilder& debug, int level = 0 ) const = 0; - virtual void toBSON(BSONObjBuilder* out) const = 0; + /** + * Get the i-th child. + */ + virtual MatchExpression* getChild(size_t i) const { + return NULL; + } - protected: - void _debugAddSpace( StringBuilder& debug, int level ) const; + /** + * Get all the children of a node + */ + virtual std::vector<MatchExpression*>* getChildVector() { + return NULL; + } - private: - MatchType _matchType; - std::unique_ptr<TagData> _tagData; - }; + /** + * Get the path of the leaf. Returns StringData() if there is no path (node is logical). + */ + virtual const StringData path() const { + return StringData(); + } /** - * this isn't really an expression, but a hint to other things - * not sure where to put it in the end + * Notes on structure: + * isLogical, isArray, and isLeaf define three partitions of all possible operators. + * + * isLogical can have children and its children can be arbitrary operators. + * + * isArray can have children and its children are predicates over one field. + * + * isLeaf is a predicate over one field. */ - class AtomicMatchExpression : public MatchExpression { - public: - AtomicMatchExpression() : MatchExpression( ATOMIC ){} - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const { - return true; - } + /** + * Is this node a logical operator? All of these inherit from ListOfMatchExpression. + * AND, OR, NOT, NOR. + */ + bool isLogical() const { + return AND == _matchType || OR == _matchType || NOT == _matchType || NOR == _matchType; + } - virtual bool matchesSingleElement( const BSONElement& e ) const { - return true; - } + /** + * Is this node an array operator? Array operators have multiple clauses but operate on one + * field. + * + * ELEM_MATCH_VALUE, ELEM_MATCH_OBJECT, SIZE (ArrayMatchingMatchExpression) + */ + bool isArray() const { + return SIZE == _matchType || ELEM_MATCH_VALUE == _matchType || + ELEM_MATCH_OBJECT == _matchType; + } - virtual MatchExpression* shallowClone() const { - return new AtomicMatchExpression(); - } + /** + * Not-internal nodes, predicates over one field. Almost all of these inherit from + * LeafMatchExpression. + * + * Exceptions: WHERE, which doesn't have a field. + * TYPE_OPERATOR, which inherits from MatchExpression due to unique array + * semantics. + */ + bool isLeaf() const { + return !isArray() && !isLogical(); + } - virtual void debugString( StringBuilder& debug, int level = 0 ) const; + // XXX: document + virtual MatchExpression* shallowClone() const = 0; - virtual void toBSON(BSONObjBuilder* out) const; + // XXX document + virtual bool equivalent(const MatchExpression* other) const = 0; - virtual bool equivalent( const MatchExpression* other ) const { - return other->matchType() == ATOMIC; - } - }; + // + // Determine if a document satisfies the tree-predicate. + // + + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const = 0; + + virtual bool matchesBSON(const BSONObj& doc, MatchDetails* details = 0) const; + + /** + * Determines if the element satisfies the tree-predicate. + * Not valid for all expressions (e.g. $where); in those cases, returns false. + */ + virtual bool matchesSingleElement(const BSONElement& e) const = 0; + + // + // Tagging mechanism: Hang data off of the tree for retrieval later. + // - class FalseMatchExpression : public MatchExpression { + class TagData { public: - FalseMatchExpression() : MatchExpression( ALWAYS_FALSE ){} + virtual ~TagData() {} + virtual void debugString(StringBuilder* builder) const = 0; + virtual TagData* clone() const = 0; + }; - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const { - return false; + /** + * Takes ownership + */ + void setTag(TagData* data) { + _tagData.reset(data); + } + TagData* getTag() const { + return _tagData.get(); + } + virtual void resetTag() { + setTag(NULL); + for (size_t i = 0; i < numChildren(); ++i) { + getChild(i)->resetTag(); } + } - virtual bool matchesSingleElement( const BSONElement& e ) const { - return false; - } + // + // Debug information + // + virtual std::string toString() const; + virtual void debugString(StringBuilder& debug, int level = 0) const = 0; + virtual void toBSON(BSONObjBuilder* out) const = 0; - virtual MatchExpression* shallowClone() const { - return new FalseMatchExpression(); - } +protected: + void _debugAddSpace(StringBuilder& debug, int level) const; - virtual void debugString( StringBuilder& debug, int level = 0 ) const; +private: + MatchType _matchType; + std::unique_ptr<TagData> _tagData; +}; - virtual void toBSON(BSONObjBuilder* out) const; +/** + * 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(ATOMIC) {} - virtual bool equivalent( const MatchExpression* other ) const { - return other->matchType() == ALWAYS_FALSE; - } - }; + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const { + return true; + } + + virtual bool matchesSingleElement(const BSONElement& e) const { + return true; + } + + virtual MatchExpression* shallowClone() const { + return new AtomicMatchExpression(); + } + + virtual void debugString(StringBuilder& debug, int level = 0) const; + + virtual void toBSON(BSONObjBuilder* out) const; + + virtual bool equivalent(const MatchExpression* other) const { + return other->matchType() == ATOMIC; + } +}; + +class FalseMatchExpression : public MatchExpression { +public: + FalseMatchExpression() : MatchExpression(ALWAYS_FALSE) {} + + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const { + return false; + } + + virtual bool matchesSingleElement(const BSONElement& e) const { + return false; + } + + virtual MatchExpression* shallowClone() const { + return new FalseMatchExpression(); + } + + virtual void debugString(StringBuilder& debug, int level = 0) const; + + virtual void toBSON(BSONObjBuilder* out) const; + virtual bool equivalent(const MatchExpression* other) const { + return other->matchType() == ALWAYS_FALSE; + } +}; } diff --git a/src/mongo/db/matcher/expression_algo.cpp b/src/mongo/db/matcher/expression_algo.cpp index 5e4880a1ae5..a8fb260b7d0 100644 --- a/src/mongo/db/matcher/expression_algo.cpp +++ b/src/mongo/db/matcher/expression_algo.cpp @@ -37,8 +37,8 @@ namespace mongo { namespace { - bool isComparisonMatchExpression(const MatchExpression* expr) { - switch (expr->matchType()) { +bool isComparisonMatchExpression(const MatchExpression* expr) { + switch (expr->matchType()) { case MatchExpression::LT: case MatchExpression::LTE: case MatchExpression::EQ: @@ -47,140 +47,139 @@ namespace { return true; default: return false; - } } +} - bool supportsEquality(const ComparisonMatchExpression* expr) { - switch (expr->matchType()) { +bool supportsEquality(const ComparisonMatchExpression* expr) { + switch (expr->matchType()) { case MatchExpression::LTE: case MatchExpression::EQ: case MatchExpression::GTE: return true; default: return false; - } } +} - /** - * Returns true if the documents matched by 'lhs' are a subset of the documents matched by - * 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise. - */ - bool _isSubsetOf(const ComparisonMatchExpression* lhs, const ComparisonMatchExpression* rhs) { - // An expression can only match a subset of the documents matched by another if they are - // comparing the same field. - if (lhs->path() != rhs->path()) { - return false; - } +/** + * Returns true if the documents matched by 'lhs' are a subset of the documents matched by + * 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise. + */ +bool _isSubsetOf(const ComparisonMatchExpression* lhs, const ComparisonMatchExpression* rhs) { + // An expression can only match a subset of the documents matched by another if they are + // comparing the same field. + if (lhs->path() != rhs->path()) { + return false; + } - const BSONElement lhsData = lhs->getData(); - const BSONElement rhsData = rhs->getData(); + const BSONElement lhsData = lhs->getData(); + const BSONElement rhsData = rhs->getData(); - if (lhsData.canonicalType() != rhsData.canonicalType()) { - return false; - } + if (lhsData.canonicalType() != rhsData.canonicalType()) { + return false; + } - // Special case the handling for NaN values: NaN compares equal only to itself. - if (std::isnan(lhsData.numberDouble()) || std::isnan(rhsData.numberDouble())) { - if (supportsEquality(lhs) && supportsEquality(rhs)) { - return std::isnan(lhsData.numberDouble()) && std::isnan(rhsData.numberDouble()); - } - return false; + // Special case the handling for NaN values: NaN compares equal only to itself. + if (std::isnan(lhsData.numberDouble()) || std::isnan(rhsData.numberDouble())) { + if (supportsEquality(lhs) && supportsEquality(rhs)) { + return std::isnan(lhsData.numberDouble()) && std::isnan(rhsData.numberDouble()); } + return false; + } - int cmp = compareElementValues(lhsData, rhsData); + int cmp = compareElementValues(lhsData, rhsData); - // Check whether the two expressions are equivalent. - if (lhs->matchType() == rhs->matchType() && cmp == 0) { - return true; - } + // Check whether the two expressions are equivalent. + if (lhs->matchType() == rhs->matchType() && cmp == 0) { + return true; + } - switch (rhs->matchType()) { + switch (rhs->matchType()) { case MatchExpression::LT: case MatchExpression::LTE: switch (lhs->matchType()) { - case MatchExpression::LT: - case MatchExpression::LTE: - case MatchExpression::EQ: - if (rhs->matchType() == MatchExpression::LTE) { - return cmp <= 0; - } - return cmp < 0; - default: - return false; + case MatchExpression::LT: + case MatchExpression::LTE: + case MatchExpression::EQ: + if (rhs->matchType() == MatchExpression::LTE) { + return cmp <= 0; + } + return cmp < 0; + default: + return false; } case MatchExpression::GT: case MatchExpression::GTE: switch (lhs->matchType()) { - case MatchExpression::GT: - case MatchExpression::GTE: - case MatchExpression::EQ: - if (rhs->matchType() == MatchExpression::GTE) { - return cmp >= 0; - } - return cmp > 0; - default: - return false; + case MatchExpression::GT: + case MatchExpression::GTE: + case MatchExpression::EQ: + if (rhs->matchType() == MatchExpression::GTE) { + return cmp >= 0; + } + return cmp > 0; + default: + return false; } default: return false; - } } +} - /** - * Returns true if the documents matched by 'lhs' are a subset of the documents matched by - * 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise. - */ - bool _isSubsetOf(const MatchExpression* lhs, const ComparisonMatchExpression* rhs) { - // An expression can only match a subset of the documents matched by another if they are - // comparing the same field. - if (lhs->path() != rhs->path()) { - return false; - } +/** + * Returns true if the documents matched by 'lhs' are a subset of the documents matched by + * 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise. + */ +bool _isSubsetOf(const MatchExpression* lhs, const ComparisonMatchExpression* rhs) { + // An expression can only match a subset of the documents matched by another if they are + // comparing the same field. + if (lhs->path() != rhs->path()) { + return false; + } - if (isComparisonMatchExpression(lhs)) { - return _isSubsetOf(static_cast<const ComparisonMatchExpression*>(lhs), rhs); - } + if (isComparisonMatchExpression(lhs)) { + return _isSubsetOf(static_cast<const ComparisonMatchExpression*>(lhs), rhs); + } - if (lhs->matchType() == MatchExpression::MATCH_IN) { - const InMatchExpression* ime = static_cast<const InMatchExpression*>(lhs); - const ArrayFilterEntries& arrayEntries = ime->getData(); - if (arrayEntries.numRegexes() > 0) { + if (lhs->matchType() == MatchExpression::MATCH_IN) { + const InMatchExpression* ime = static_cast<const InMatchExpression*>(lhs); + const ArrayFilterEntries& arrayEntries = ime->getData(); + if (arrayEntries.numRegexes() > 0) { + return false; + } + for (BSONElement elem : arrayEntries.equalities()) { + // Each element in the $in-array represents an equality predicate. + EqualityMatchExpression equality; + equality.init(lhs->path(), elem); + if (!_isSubsetOf(&equality, rhs)) { return false; } - for (BSONElement elem : arrayEntries.equalities()) { - // Each element in the $in-array represents an equality predicate. - EqualityMatchExpression equality; - equality.init(lhs->path(), elem); - if (!_isSubsetOf(&equality, rhs)) { - return false; - } - } - return true; } - return false; + return true; } + return false; +} - /** - * Returns true if the documents matched by 'lhs' are a subset of the documents matched by - * 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise. - */ - bool _isSubsetOf(const MatchExpression* lhs, const ExistsMatchExpression* rhs) { - // An expression can only match a subset of the documents matched by another if they are - // comparing the same field. Defer checking the path for $not expressions until the - // subexpression is examined. - if (lhs->matchType() != MatchExpression::NOT && lhs->path() != rhs->path()) { - return false; - } +/** + * Returns true if the documents matched by 'lhs' are a subset of the documents matched by + * 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise. + */ +bool _isSubsetOf(const MatchExpression* lhs, const ExistsMatchExpression* rhs) { + // An expression can only match a subset of the documents matched by another if they are + // comparing the same field. Defer checking the path for $not expressions until the + // subexpression is examined. + if (lhs->matchType() != MatchExpression::NOT && lhs->path() != rhs->path()) { + return false; + } - if (isComparisonMatchExpression(lhs)) { - const ComparisonMatchExpression* cme = - static_cast<const ComparisonMatchExpression*>(lhs); - // CompareMatchExpression::init() prohibits creating a match expression with EOO or - // Undefined types, so only need to ensure that the value is not of type jstNULL. - return cme->getData().type() != jstNULL; - } + if (isComparisonMatchExpression(lhs)) { + const ComparisonMatchExpression* cme = static_cast<const ComparisonMatchExpression*>(lhs); + // CompareMatchExpression::init() prohibits creating a match expression with EOO or + // Undefined types, so only need to ensure that the value is not of type jstNULL. + return cme->getData().type() != jstNULL; + } - switch (lhs->matchType()) { + switch (lhs->matchType()) { case MatchExpression::ELEM_MATCH_VALUE: case MatchExpression::ELEM_MATCH_OBJECT: case MatchExpression::EXISTS: @@ -202,76 +201,76 @@ namespace { } switch (lhs->getChild(0)->matchType()) { - case MatchExpression::EQ: { - const ComparisonMatchExpression* cme = - static_cast<const ComparisonMatchExpression*>(lhs->getChild(0)); - return cme->getData().type() == jstNULL; - } - case MatchExpression::MATCH_IN: { - const InMatchExpression* ime = - static_cast<const InMatchExpression*>(lhs->getChild(0)); - return ime->getData().hasNull(); - } - default: - return false; + case MatchExpression::EQ: { + const ComparisonMatchExpression* cme = + static_cast<const ComparisonMatchExpression*>(lhs->getChild(0)); + return cme->getData().type() == jstNULL; + } + case MatchExpression::MATCH_IN: { + const InMatchExpression* ime = + static_cast<const InMatchExpression*>(lhs->getChild(0)); + return ime->getData().hasNull(); + } + default: + return false; } default: return false; - } } +} } // namespace namespace expression { - bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs) { - invariant(lhs); - invariant(rhs); +bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs) { + invariant(lhs); + invariant(rhs); - if (lhs->equivalent(rhs)) { - return true; - } + if (lhs->equivalent(rhs)) { + return true; + } - if (rhs->matchType() == MatchExpression::AND) { - // 'lhs' must match a subset of the documents matched by each clause of 'rhs'. - for (size_t i = 0; i < rhs->numChildren(); i++) { - if (!isSubsetOf(lhs, rhs->getChild(i))) { - return false; - } + if (rhs->matchType() == MatchExpression::AND) { + // 'lhs' must match a subset of the documents matched by each clause of 'rhs'. + for (size_t i = 0; i < rhs->numChildren(); i++) { + if (!isSubsetOf(lhs, rhs->getChild(i))) { + return false; } - return true; } + return true; + } - if (lhs->matchType() == MatchExpression::AND) { - // At least one clause of 'lhs' must match a subset of the documents matched by 'rhs'. - for (size_t i = 0; i < lhs->numChildren(); i++) { - if (isSubsetOf(lhs->getChild(i), rhs)) { - return true; - } + if (lhs->matchType() == MatchExpression::AND) { + // At least one clause of 'lhs' must match a subset of the documents matched by 'rhs'. + for (size_t i = 0; i < lhs->numChildren(); i++) { + if (isSubsetOf(lhs->getChild(i), rhs)) { + return true; } - return false; } + return false; + } - if (lhs->matchType() == MatchExpression::OR) { - // Every clause of 'lhs' must match a subset of the documents matched by 'rhs'. - for (size_t i = 0; i < lhs->numChildren(); i++) { - if (!isSubsetOf(lhs->getChild(i), rhs)) { - return false; - } + if (lhs->matchType() == MatchExpression::OR) { + // Every clause of 'lhs' must match a subset of the documents matched by 'rhs'. + for (size_t i = 0; i < lhs->numChildren(); i++) { + if (!isSubsetOf(lhs->getChild(i), rhs)) { + return false; } - return true; - } - - if (isComparisonMatchExpression(rhs)) { - return _isSubsetOf(lhs, static_cast<const ComparisonMatchExpression*>(rhs)); } + return true; + } - if (rhs->matchType() == MatchExpression::EXISTS) { - return _isSubsetOf(lhs, static_cast<const ExistsMatchExpression*>(rhs)); - } + if (isComparisonMatchExpression(rhs)) { + return _isSubsetOf(lhs, static_cast<const ComparisonMatchExpression*>(rhs)); + } - return false; + if (rhs->matchType() == MatchExpression::EXISTS) { + return _isSubsetOf(lhs, static_cast<const ExistsMatchExpression*>(rhs)); } + return false; +} + } // namespace expression } // namespace mongo diff --git a/src/mongo/db/matcher/expression_algo.h b/src/mongo/db/matcher/expression_algo.h index 0ee4459c56f..baccf557104 100644 --- a/src/mongo/db/matcher/expression_algo.h +++ b/src/mongo/db/matcher/expression_algo.h @@ -30,34 +30,34 @@ namespace mongo { - class MatchExpression; +class MatchExpression; namespace expression { - /** - * Returns true if the documents matched by 'lhs' are a subset of the documents matched by - * 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise. - * - * With respect to partial indexes, 'lhs' corresponds to the query specification and 'rhs' - * corresponds to the filter specification. - * - * e.g. - * - * Suppose that - * - * lhs = { x : 4 } - * rhs = { x : { $lte : 5 } } - * - * ==> true - * - * Suppose that - * - * lhs = { x : { $gte: 6 } } - * rhs = { x : 7 } - * - * ==> false - */ - bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs); +/** + * Returns true if the documents matched by 'lhs' are a subset of the documents matched by + * 'rhs', i.e. a document matched by 'lhs' must also be matched by 'rhs', and false otherwise. + * + * With respect to partial indexes, 'lhs' corresponds to the query specification and 'rhs' + * corresponds to the filter specification. + * + * e.g. + * + * Suppose that + * + * lhs = { x : 4 } + * rhs = { x : { $lte : 5 } } + * + * ==> true + * + * Suppose that + * + * lhs = { x : { $gte: 6 } } + * rhs = { x : 7 } + * + * ==> false + */ +bool isSubsetOf(const MatchExpression* lhs, const MatchExpression* rhs); } // namespace expression } // namespace mongo diff --git a/src/mongo/db/matcher/expression_algo_test.cpp b/src/mongo/db/matcher/expression_algo_test.cpp index 63af7a52c96..a108a7a7f3b 100644 --- a/src/mongo/db/matcher/expression_algo_test.cpp +++ b/src/mongo/db/matcher/expression_algo_test.cpp @@ -40,591 +40,592 @@ namespace mongo { - /** - * A MatchExpression does not hold the memory for BSONElements, so use ParsedMatchExpression to - * ensure that the BSONObj outlives the MatchExpression. - */ - class ParsedMatchExpression { - public: - ParsedMatchExpression(const std::string& str) - : _obj(fromjson(str)) { - StatusWithMatchExpression result = MatchExpressionParser::parse(_obj); - ASSERT_OK(result.getStatus()); - _expr.reset(result.getValue()); - } - - const MatchExpression* get() const { return _expr.get(); } - - private: - const BSONObj _obj; - std::unique_ptr<MatchExpression> _expr; - }; - - TEST(ExpressionAlgoIsSubsetOf, NullAndOmittedField) { - // Verify that ComparisonMatchExpression::init() prohibits creating a match expression with - // an Undefined type. - BSONObj undefined = fromjson("{a: undefined}"); - ASSERT_EQUALS(ErrorCodes::BadValue, MatchExpressionParser::parse(undefined).getStatus()); - - ParsedMatchExpression empty("{}"); - ParsedMatchExpression null("{a: null}"); - - ASSERT_TRUE(expression::isSubsetOf(null.get(), empty.get())); - ASSERT_FALSE(expression::isSubsetOf(empty.get(), null.get())); - - ParsedMatchExpression b1("{b: 1}"); - ParsedMatchExpression aNullB1("{a: null, b: 1}"); - - ASSERT_TRUE(expression::isSubsetOf(aNullB1.get(), b1.get())); - ASSERT_FALSE(expression::isSubsetOf(b1.get(), aNullB1.get())); - - ParsedMatchExpression a1C3("{a: 1, c: 3}"); - ParsedMatchExpression a1BNullC3("{a: 1, b: null, c: 3}"); - - ASSERT_TRUE(expression::isSubsetOf(a1BNullC3.get(), a1C3.get())); - ASSERT_FALSE(expression::isSubsetOf(a1C3.get(), a1BNullC3.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, NullAndIn) { - ParsedMatchExpression eqNull("{x: null}"); - ParsedMatchExpression inNull("{x: {$in: [null]}}"); - ParsedMatchExpression inNullOr2("{x: {$in: [null, 2]}}"); - - ASSERT_TRUE(expression::isSubsetOf(inNull.get(), eqNull.get())); - ASSERT_FALSE(expression::isSubsetOf(inNullOr2.get(), eqNull.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, NullAndExists) { - ParsedMatchExpression null("{x: null}"); - ParsedMatchExpression exists("{x: {$exists: true}}"); - ASSERT_FALSE(expression::isSubsetOf(null.get(), exists.get())); - ASSERT_FALSE(expression::isSubsetOf(exists.get(), null.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) { - ParsedMatchExpression nan("{x: NaN}"); - ParsedMatchExpression lt("{x: {$lt: 5}}"); - ParsedMatchExpression lte("{x: {$lte: 5}}"); - ParsedMatchExpression gte("{x: {$gte: 5}}"); - ParsedMatchExpression gt("{x: {$gt: 5}}"); - ParsedMatchExpression in("{x: {$in: [5]}}"); - - ASSERT_TRUE(expression::isSubsetOf(nan.get(), nan.get())); - ASSERT_FALSE(expression::isSubsetOf(nan.get(), lt.get())); - ASSERT_FALSE(expression::isSubsetOf(lt.get(), nan.get())); - ASSERT_FALSE(expression::isSubsetOf(nan.get(), lte.get())); - ASSERT_FALSE(expression::isSubsetOf(lte.get(), nan.get())); - ASSERT_FALSE(expression::isSubsetOf(nan.get(), gte.get())); - ASSERT_FALSE(expression::isSubsetOf(gte.get(), nan.get())); - ASSERT_FALSE(expression::isSubsetOf(nan.get(), gt.get())); - ASSERT_FALSE(expression::isSubsetOf(gt.get(), nan.get())); - ASSERT_FALSE(expression::isSubsetOf(nan.get(), in.get())); - ASSERT_FALSE(expression::isSubsetOf(in.get(), nan.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_EQ) { - ParsedMatchExpression a5("{a: 5}"); - ParsedMatchExpression a6("{a: 6}"); - ParsedMatchExpression b5("{b: 5}"); - - ASSERT_TRUE(expression::isSubsetOf(a5.get(), a5.get())); - ASSERT_FALSE(expression::isSubsetOf(a5.get(), a6.get())); - ASSERT_FALSE(expression::isSubsetOf(a5.get(), b5.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, CompareAnd_EQ) { - ParsedMatchExpression a1B2("{a: 1, b: 2}"); - ParsedMatchExpression a1B7("{a: 1, b: 7}"); - ParsedMatchExpression a1("{a: 1}"); - ParsedMatchExpression b2("{b: 2}"); - - ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), a1B2.get())); - ASSERT_FALSE(expression::isSubsetOf(a1B2.get(), a1B7.get())); - - ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), a1.get())); - ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), b2.get())); - ASSERT_FALSE(expression::isSubsetOf(a1B7.get(), b2.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, CompareAnd_GT) { - ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}}"); - ParsedMatchExpression query("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); - - ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); - ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, CompareOr_LT) { - ParsedMatchExpression lt5("{a: {$lt: 5}}"); - ParsedMatchExpression eq2OrEq3("{$or: [{a: 2}, {a: 3}]}"); - ParsedMatchExpression eq4OrEq5("{$or: [{a: 4}, {a: 5}]}"); - ParsedMatchExpression eq4OrEq6("{$or: [{a: 4}, {a: 6}]}"); - - ASSERT_TRUE(expression::isSubsetOf(eq2OrEq3.get(), lt5.get())); - ASSERT_FALSE(expression::isSubsetOf(eq4OrEq5.get(), lt5.get())); - ASSERT_FALSE(expression::isSubsetOf(eq4OrEq6.get(), lt5.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, CompareOr_GTE) { - ParsedMatchExpression gte5("{a: {$gte: 5}}"); - ParsedMatchExpression eq4OrEq6("{$or: [{a: 4}, {a: 6}]}"); - ParsedMatchExpression eq5OrEq6("{$or: [{a: 5}, {a: 6}]}"); - ParsedMatchExpression eq7OrEq8("{$or: [{a: 7}, {a: 8}]}"); - - ASSERT_FALSE(expression::isSubsetOf(eq4OrEq6.get(), gte5.get())); - ASSERT_TRUE(expression::isSubsetOf(eq5OrEq6.get(), gte5.get())); - ASSERT_TRUE(expression::isSubsetOf(eq7OrEq8.get(), gte5.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, DifferentCanonicalTypes) { - ParsedMatchExpression number("{x: {$gt: 1}}"); - ParsedMatchExpression string("{x: {$gt: 'a'}}"); - ASSERT_FALSE(expression::isSubsetOf(number.get(), string.get())); - ASSERT_FALSE(expression::isSubsetOf(string.get(), number.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, DifferentNumberTypes) { - ParsedMatchExpression numberDouble("{x: 5.0}"); - ParsedMatchExpression numberInt("{x: NumberInt(5)}"); - ParsedMatchExpression numberLong("{x: NumberLong(5)}"); - - ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberInt.get())); - ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberLong.get())); - ASSERT_TRUE(expression::isSubsetOf(numberInt.get(), numberDouble.get())); - ASSERT_TRUE(expression::isSubsetOf(numberInt.get(), numberLong.get())); - ASSERT_TRUE(expression::isSubsetOf(numberLong.get(), numberDouble.get())); - ASSERT_TRUE(expression::isSubsetOf(numberLong.get(), numberInt.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, PointInUnboundedRange) { - ParsedMatchExpression a4("{a: 4}"); - ParsedMatchExpression a5("{a: 5}"); - ParsedMatchExpression a6("{a: 6}"); - ParsedMatchExpression b5("{b: 5}"); - - ParsedMatchExpression lt5("{a: {$lt: 5}}"); - ParsedMatchExpression lte5("{a: {$lte: 5}}"); - ParsedMatchExpression gte5("{a: {$gte: 5}}"); - ParsedMatchExpression gt5("{a: {$gt: 5}}"); - - ASSERT_TRUE(expression::isSubsetOf(a4.get(), lte5.get())); - ASSERT_TRUE(expression::isSubsetOf(a5.get(), lte5.get())); - ASSERT_FALSE(expression::isSubsetOf(a6.get(), lte5.get())); - - ASSERT_TRUE(expression::isSubsetOf(a4.get(), lt5.get())); - ASSERT_FALSE(expression::isSubsetOf(a5.get(), lt5.get())); - ASSERT_FALSE(expression::isSubsetOf(a6.get(), lt5.get())); - - ASSERT_FALSE(expression::isSubsetOf(a4.get(), gte5.get())); - ASSERT_TRUE(expression::isSubsetOf(a5.get(), gte5.get())); - ASSERT_TRUE(expression::isSubsetOf(a6.get(), gte5.get())); - - ASSERT_FALSE(expression::isSubsetOf(a4.get(), gt5.get())); - ASSERT_FALSE(expression::isSubsetOf(a5.get(), gt5.get())); - ASSERT_TRUE(expression::isSubsetOf(a6.get(), gt5.get())); - - // An unbounded range query does not match a subset of documents of a point query. - ASSERT_FALSE(expression::isSubsetOf(lt5.get(), a5.get())); - ASSERT_FALSE(expression::isSubsetOf(lte5.get(), a5.get())); - ASSERT_FALSE(expression::isSubsetOf(gte5.get(), a5.get())); - ASSERT_FALSE(expression::isSubsetOf(gt5.get(), a5.get())); - - // Cannot be a subset if comparing different field names. - ASSERT_FALSE(expression::isSubsetOf(b5.get(), lt5.get())); - ASSERT_FALSE(expression::isSubsetOf(b5.get(), lte5.get())); - ASSERT_FALSE(expression::isSubsetOf(b5.get(), gte5.get())); - ASSERT_FALSE(expression::isSubsetOf(b5.get(), gt5.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange) { - ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); - ParsedMatchExpression query("{a: 6}"); - - ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); - ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange_FakeAnd) { - ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); - ParsedMatchExpression query("{$and: [{a: 6}, {a: 6}]}"); - - ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); - ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, MultiplePointsInBoundedRange) { - ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); - ParsedMatchExpression queryAllInside("{a: {$in: [6, 7, 8]}}"); - ParsedMatchExpression queryStraddleLower("{a: {$in: [4.9, 5.1]}}"); - ParsedMatchExpression queryStraddleUpper("{a: {$in: [9.9, 10.1]}}"); - - ASSERT_TRUE(expression::isSubsetOf(queryAllInside.get(), filter.get())); - ASSERT_FALSE(expression::isSubsetOf(queryStraddleLower.get(), filter.get())); - ASSERT_FALSE(expression::isSubsetOf(queryStraddleUpper.get(), filter.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, PointInCompoundRange) { - ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); - ParsedMatchExpression query("{a: 10, b: 10, c: 10}"); - - ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); - ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_LT_LTE) { - ParsedMatchExpression lte4("{x: {$lte: 4}}"); - ParsedMatchExpression lt5("{x: {$lt: 5}}"); - ParsedMatchExpression lte5("{x: {$lte: 5}}"); - ParsedMatchExpression lt6("{x: {$lt: 6}}"); - - ASSERT_TRUE(expression::isSubsetOf(lte4.get(), lte5.get())); - ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lte5.get())); - ASSERT_TRUE(expression::isSubsetOf(lte5.get(), lte5.get())); - ASSERT_FALSE(expression::isSubsetOf(lt6.get(), lte5.get())); - - ASSERT_TRUE(expression::isSubsetOf(lte4.get(), lt5.get())); - ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lt5.get())); - ASSERT_FALSE(expression::isSubsetOf(lte5.get(), lt5.get())); - ASSERT_FALSE(expression::isSubsetOf(lt6.get(), lt5.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_GT_GTE) { - ParsedMatchExpression gte6("{x: {$gte: 6}}"); - ParsedMatchExpression gt5("{x: {$gt: 5}}"); - ParsedMatchExpression gte5("{x: {$gte: 5}}"); - ParsedMatchExpression gt4("{x: {$gt: 4}}"); - - ASSERT_TRUE(expression::isSubsetOf(gte6.get(), gte5.get())); - ASSERT_TRUE(expression::isSubsetOf(gt5.get(), gte5.get())); - ASSERT_TRUE(expression::isSubsetOf(gte5.get(), gte5.get())); - ASSERT_FALSE(expression::isSubsetOf(gt4.get(), gte5.get())); - - ASSERT_TRUE(expression::isSubsetOf(gte6.get(), gt5.get())); - ASSERT_TRUE(expression::isSubsetOf(gt5.get(), gt5.get())); - ASSERT_FALSE(expression::isSubsetOf(gte5.get(), gt5.get())); - ASSERT_FALSE(expression::isSubsetOf(gt4.get(), gt5.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, BoundedRangeInUnboundedRange) { - ParsedMatchExpression filter("{a: {$gt: 1}}"); - ParsedMatchExpression query("{a: {$gt: 5, $lt: 10}}"); - - ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); - ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, MultipleRangesInUnboundedRange) { - ParsedMatchExpression filter("{a: {$gt: 1}}"); - ParsedMatchExpression negative("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$lt: 0}}]}"); - ParsedMatchExpression unbounded("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$gt: 15}}]}"); - ParsedMatchExpression bounded("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$gt: 20, $lt: 30}}]}"); - - ASSERT_FALSE(expression::isSubsetOf(negative.get(), filter.get())); - ASSERT_TRUE(expression::isSubsetOf(unbounded.get(), filter.get())); - ASSERT_TRUE(expression::isSubsetOf(bounded.get(), filter.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, MultipleFields) { - ParsedMatchExpression filter("{a: {$gt: 5}, b: {$lt: 10}}"); - ParsedMatchExpression onlyA("{$or: [{a: 6, b: {$lt: 4}}, {a: {$gt: 11}}]}"); - ParsedMatchExpression onlyB("{$or: [{b: {$lt: 4}}, {a: {$gt: 11}, b: 9}]}"); - ParsedMatchExpression both("{$or: [{a: 6, b: {$lt: 4}}, {a: {$gt: 11}, b: 9}]}"); - - ASSERT_FALSE(expression::isSubsetOf(onlyA.get(), filter.get())); - ASSERT_FALSE(expression::isSubsetOf(onlyB.get(), filter.get())); - ASSERT_TRUE(expression::isSubsetOf(both.get(), filter.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_LT_In) { - ParsedMatchExpression lt("{a: {$lt: 5}}"); - - ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpression inEq("{a: {$in: [5]}}"); - ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpression inNull("{a: {$in: [null]}}"); - - ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpression inAllLte("{a: {$in: [4.9, 5]}}"); - ParsedMatchExpression inAllLt("{a: {$in: [2, 3, 4]}}"); - ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpression inLtAndNull("{a: {$in: [1, null]}}"); - - ASSERT_TRUE(expression::isSubsetOf(inLt.get(), lt.get())); - ASSERT_FALSE(expression::isSubsetOf(inEq.get(), lt.get())); - ASSERT_FALSE(expression::isSubsetOf(inGt.get(), lt.get())); - - ASSERT_FALSE(expression::isSubsetOf(inAllEq.get(), lt.get())); - ASSERT_FALSE(expression::isSubsetOf(inAllLte.get(), lt.get())); - ASSERT_TRUE(expression::isSubsetOf(inAllLt.get(), lt.get())); - ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), lt.get())); - ASSERT_FALSE(expression::isSubsetOf(inLtAndNull.get(), lt.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_LTE_In) { - ParsedMatchExpression lte("{a: {$lte: 5}}"); - - ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpression inEq("{a: {$in: [5]}}"); - ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpression inNull("{a: {$in: [null]}}"); - - ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpression inAllLte("{a: {$in: [4.9, 5]}}"); - ParsedMatchExpression inAllLt("{a: {$in: [2, 3, 4]}}"); - ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpression inLtAndNull("{a: {$in: [1, null]}}"); - - ASSERT_TRUE(expression::isSubsetOf(inLt.get(), lte.get())); - ASSERT_TRUE(expression::isSubsetOf(inEq.get(), lte.get())); - ASSERT_FALSE(expression::isSubsetOf(inGt.get(), lte.get())); - - ASSERT_TRUE(expression::isSubsetOf(inAllEq.get(), lte.get())); - ASSERT_TRUE(expression::isSubsetOf(inAllLte.get(), lte.get())); - ASSERT_TRUE(expression::isSubsetOf(inAllLt.get(), lte.get())); - ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), lte.get())); - ASSERT_FALSE(expression::isSubsetOf(inLtAndNull.get(), lte.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_EQ_In) { - ParsedMatchExpression eq("{a: 5}"); - - ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpression inEq("{a: {$in: [5]}}"); - ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpression inNull("{a: {$in: [null]}}"); - - ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpression inEqAndNull("{a: {$in: [5, null]}}"); - - ASSERT_FALSE(expression::isSubsetOf(inLt.get(), eq.get())); - ASSERT_TRUE(expression::isSubsetOf(inEq.get(), eq.get())); - ASSERT_FALSE(expression::isSubsetOf(inGt.get(), eq.get())); - - ASSERT_TRUE(expression::isSubsetOf(inAllEq.get(), eq.get())); - ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), eq.get())); - ASSERT_FALSE(expression::isSubsetOf(inEqAndNull.get(), eq.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_GT_In) { - ParsedMatchExpression gt("{a: {$gt: 5}}"); - - ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpression inEq("{a: {$in: [5]}}"); - ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpression inNull("{a: {$in: [null]}}"); - - ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpression inAllGte("{a: {$in: [5, 5.1]}}"); - ParsedMatchExpression inAllGt("{a: {$in: [6, 7, 8]}}"); - ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpression inGtAndNull("{a: {$in: [9, null]}}"); - - ASSERT_FALSE(expression::isSubsetOf(inLt.get(), gt.get())); - ASSERT_FALSE(expression::isSubsetOf(inEq.get(), gt.get())); - ASSERT_TRUE(expression::isSubsetOf(inGt.get(), gt.get())); - - ASSERT_FALSE(expression::isSubsetOf(inAllEq.get(), gt.get())); - ASSERT_FALSE(expression::isSubsetOf(inAllGte.get(), gt.get())); - ASSERT_TRUE(expression::isSubsetOf(inAllGt.get(), gt.get())); - ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), gt.get())); - ASSERT_FALSE(expression::isSubsetOf(inGtAndNull.get(), gt.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_GTE_In) { - ParsedMatchExpression gte("{a: {$gte: 5}}"); - - ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); - ParsedMatchExpression inEq("{a: {$in: [5]}}"); - ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); - ParsedMatchExpression inNull("{a: {$in: [null]}}"); - - ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); - ParsedMatchExpression inAllGte("{a: {$in: [5, 5.1]}}"); - ParsedMatchExpression inAllGt("{a: {$in: [6, 7, 8]}}"); - ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); - ParsedMatchExpression inGtAndNull("{a: {$in: [9, null]}}"); - - ASSERT_FALSE(expression::isSubsetOf(inLt.get(), gte.get())); - ASSERT_TRUE(expression::isSubsetOf(inEq.get(), gte.get())); - ASSERT_TRUE(expression::isSubsetOf(inGt.get(), gte.get())); - - ASSERT_TRUE(expression::isSubsetOf(inAllEq.get(), gte.get())); - ASSERT_TRUE(expression::isSubsetOf(inAllGte.get(), gte.get())); - ASSERT_TRUE(expression::isSubsetOf(inAllGt.get(), gte.get())); - ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), gte.get())); - ASSERT_FALSE(expression::isSubsetOf(inGtAndNull.get(), gte.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, RegexAndIn) { - ParsedMatchExpression eq1("{x: 1}"); - ParsedMatchExpression eqA("{x: 'a'}"); - ParsedMatchExpression inRegexA("{x: {$in: [/a/]}}"); - ParsedMatchExpression inRegexAbc("{x: {$in: [/abc/]}}"); - ParsedMatchExpression inRegexAOrEq1("{x: {$in: [/a/, 1]}}"); - ParsedMatchExpression inRegexAOrNull("{x: {$in: [/a/, null]}}"); - - ASSERT_TRUE(expression::isSubsetOf(inRegexA.get(), inRegexA.get())); - ASSERT_FALSE(expression::isSubsetOf(inRegexAbc.get(), inRegexA.get())); - ASSERT_FALSE(expression::isSubsetOf(inRegexA.get(), inRegexAOrEq1.get())); - ASSERT_FALSE(expression::isSubsetOf(inRegexAOrEq1.get(), eq1.get())); - ASSERT_FALSE(expression::isSubsetOf(inRegexA.get(), eqA.get())); - ASSERT_FALSE(expression::isSubsetOf(inRegexAOrNull.get(), eqA.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Exists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression bExists("{b: {$exists: true}}"); - ParsedMatchExpression aExistsBExists("{a: {$exists: true}, b: {$exists: true}}"); - ParsedMatchExpression aExistsBExistsC5("{a: {$exists: true}, b: {$exists: true}, c: 5}"); - - ASSERT_TRUE(expression::isSubsetOf(aExists.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(aExists.get(), bExists.get())); - - ASSERT_TRUE(expression::isSubsetOf(aExistsBExists.get(), aExists.get())); - ASSERT_TRUE(expression::isSubsetOf(aExistsBExists.get(), bExists.get())); - ASSERT_FALSE(expression::isSubsetOf(aExistsBExists.get(), aExistsBExistsC5.get())); - - ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), aExists.get())); - ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), bExists.get())); - ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), aExistsBExists.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_Exists) { - ParsedMatchExpression exists("{a: {$exists: true}}"); - ParsedMatchExpression eq("{a: 1}"); - ParsedMatchExpression gt("{a: {$gt: 4}}"); - ParsedMatchExpression lte("{a: {$lte: 7}}"); - - ASSERT_TRUE(expression::isSubsetOf(eq.get(), exists.get())); - ASSERT_TRUE(expression::isSubsetOf(gt.get(), exists.get())); - ASSERT_TRUE(expression::isSubsetOf(lte.get(), exists.get())); - - ASSERT_FALSE(expression::isSubsetOf(exists.get(), eq.get())); - ASSERT_FALSE(expression::isSubsetOf(exists.get(), gt.get())); - ASSERT_FALSE(expression::isSubsetOf(exists.get(), lte.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Type) { - ParsedMatchExpression aType1("{a: {$type: 1}}"); - ParsedMatchExpression aType2("{a: {$type: 2}}"); - ParsedMatchExpression bType2("{b: {$type: 2}}"); - - ASSERT_FALSE(expression::isSubsetOf(aType1.get(), aType2.get())); - ASSERT_FALSE(expression::isSubsetOf(aType2.get(), aType1.get())); - - ASSERT_TRUE(expression::isSubsetOf(aType2.get(), aType2.get())); - ASSERT_FALSE(expression::isSubsetOf(aType2.get(), bType2.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, TypeAndExists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aType2("{a: {$type: 2}}"); - ParsedMatchExpression bType2("{b: {$type: 2}}"); - - ASSERT_TRUE(expression::isSubsetOf(aType2.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aType2.get())); - ASSERT_FALSE(expression::isSubsetOf(bType2.get(), aExists.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, AllAndExists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aAll("{a: {$all: ['x', 'y', 'z']}}"); - ParsedMatchExpression bAll("{b: {$all: ['x', 'y', 'z']}}"); - ParsedMatchExpression aAllWithNull("{a: {$all: ['x', null, 'z']}}"); - - ASSERT_TRUE(expression::isSubsetOf(aAll.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(bAll.get(), aExists.get())); - ASSERT_TRUE(expression::isSubsetOf(aAllWithNull.get(), aExists.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Value) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aElemMatch("{a: {$elemMatch: {$gt: 5, $lte: 10}}}"); - ParsedMatchExpression bElemMatch("{b: {$elemMatch: {$gt: 5, $lte: 10}}}"); - ParsedMatchExpression aElemMatchNull("{a: {$elemMatch: {$eq: null}}}"); - - ASSERT_TRUE(expression::isSubsetOf(aElemMatch.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aElemMatch.get())); - ASSERT_FALSE(expression::isSubsetOf(bElemMatch.get(), aExists.get())); - ASSERT_TRUE(expression::isSubsetOf(aElemMatchNull.get(), aExists.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Object) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aElemMatch("{a: {$elemMatch: {x: {$gt: 5}, y: {$lte: 10}}}}"); - ParsedMatchExpression bElemMatch("{b: {$elemMatch: {x: {$gt: 5}, y: {$lte: 10}}}}"); - ParsedMatchExpression aElemMatchNull("{a: {$elemMatch: {x: null, y: null}}}"); - - ASSERT_TRUE(expression::isSubsetOf(aElemMatch.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aElemMatch.get())); - ASSERT_FALSE(expression::isSubsetOf(bElemMatch.get(), aExists.get())); - ASSERT_TRUE(expression::isSubsetOf(aElemMatchNull.get(), aExists.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, SizeAndExists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aSize0("{a: {$size: 0}}"); - ParsedMatchExpression aSize1("{a: {$size: 1}}"); - ParsedMatchExpression aSize3("{a: {$size: 3}}"); - ParsedMatchExpression bSize3("{b: {$size: 3}}"); - - ASSERT_TRUE(expression::isSubsetOf(aSize0.get(), aExists.get())); - ASSERT_TRUE(expression::isSubsetOf(aSize1.get(), aExists.get())); - ASSERT_TRUE(expression::isSubsetOf(aSize3.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aSize3.get())); - ASSERT_FALSE(expression::isSubsetOf(bSize3.get(), aExists.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, ModAndExists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aMod5("{a: {$mod: [5, 0]}}"); - ParsedMatchExpression bMod5("{b: {$mod: [5, 0]}}"); - - ASSERT_TRUE(expression::isSubsetOf(aMod5.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(bMod5.get(), aExists.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, RegexAndExists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aRegex("{a: {$regex: 'pattern'}}"); - ParsedMatchExpression bRegex("{b: {$regex: 'pattern'}}"); - - ASSERT_TRUE(expression::isSubsetOf(aRegex.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(bRegex.get(), aExists.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, InAndExists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aIn("{a: {$in: [1, 2, 3]}}"); - ParsedMatchExpression bIn("{b: {$in: [1, 2, 3]}}"); - ParsedMatchExpression aInWithNull("{a: {$in: [1, null, 3]}}"); - - ASSERT_TRUE(expression::isSubsetOf(aIn.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(bIn.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(aInWithNull.get(), aExists.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, NinAndExists) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aNin("{a: {$nin: [1, 2, 3]}}"); - ParsedMatchExpression bNin("{b: {$nin: [1, 2, 3]}}"); - ParsedMatchExpression aNinWithNull("{a: {$nin: [1, null, 3]}}"); - - ASSERT_FALSE(expression::isSubsetOf(aNin.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(bNin.get(), aExists.get())); - ASSERT_TRUE(expression::isSubsetOf(aNinWithNull.get(), aExists.get())); - } - - TEST(ExpressionAlgoIsSubsetOf, Compare_Exists_NE) { - ParsedMatchExpression aExists("{a: {$exists: true}}"); - ParsedMatchExpression aNotEqual1("{a: {$ne: 1}}"); - ParsedMatchExpression bNotEqual1("{b: {$ne: 1}}"); - ParsedMatchExpression aNotEqualNull("{a: {$ne: null}}"); - - ASSERT_FALSE(expression::isSubsetOf(aNotEqual1.get(), aExists.get())); - ASSERT_FALSE(expression::isSubsetOf(bNotEqual1.get(), aExists.get())); - ASSERT_TRUE(expression::isSubsetOf(aNotEqualNull.get(), aExists.get())); - } +/** + * A MatchExpression does not hold the memory for BSONElements, so use ParsedMatchExpression to + * ensure that the BSONObj outlives the MatchExpression. + */ +class ParsedMatchExpression { +public: + ParsedMatchExpression(const std::string& str) : _obj(fromjson(str)) { + StatusWithMatchExpression result = MatchExpressionParser::parse(_obj); + ASSERT_OK(result.getStatus()); + _expr.reset(result.getValue()); + } + + const MatchExpression* get() const { + return _expr.get(); + } + +private: + const BSONObj _obj; + std::unique_ptr<MatchExpression> _expr; +}; + +TEST(ExpressionAlgoIsSubsetOf, NullAndOmittedField) { + // Verify that ComparisonMatchExpression::init() prohibits creating a match expression with + // an Undefined type. + BSONObj undefined = fromjson("{a: undefined}"); + ASSERT_EQUALS(ErrorCodes::BadValue, MatchExpressionParser::parse(undefined).getStatus()); + + ParsedMatchExpression empty("{}"); + ParsedMatchExpression null("{a: null}"); + + ASSERT_TRUE(expression::isSubsetOf(null.get(), empty.get())); + ASSERT_FALSE(expression::isSubsetOf(empty.get(), null.get())); + + ParsedMatchExpression b1("{b: 1}"); + ParsedMatchExpression aNullB1("{a: null, b: 1}"); + + ASSERT_TRUE(expression::isSubsetOf(aNullB1.get(), b1.get())); + ASSERT_FALSE(expression::isSubsetOf(b1.get(), aNullB1.get())); + + ParsedMatchExpression a1C3("{a: 1, c: 3}"); + ParsedMatchExpression a1BNullC3("{a: 1, b: null, c: 3}"); + + ASSERT_TRUE(expression::isSubsetOf(a1BNullC3.get(), a1C3.get())); + ASSERT_FALSE(expression::isSubsetOf(a1C3.get(), a1BNullC3.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, NullAndIn) { + ParsedMatchExpression eqNull("{x: null}"); + ParsedMatchExpression inNull("{x: {$in: [null]}}"); + ParsedMatchExpression inNullOr2("{x: {$in: [null, 2]}}"); + + ASSERT_TRUE(expression::isSubsetOf(inNull.get(), eqNull.get())); + ASSERT_FALSE(expression::isSubsetOf(inNullOr2.get(), eqNull.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, NullAndExists) { + ParsedMatchExpression null("{x: null}"); + ParsedMatchExpression exists("{x: {$exists: true}}"); + ASSERT_FALSE(expression::isSubsetOf(null.get(), exists.get())); + ASSERT_FALSE(expression::isSubsetOf(exists.get(), null.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_NaN) { + ParsedMatchExpression nan("{x: NaN}"); + ParsedMatchExpression lt("{x: {$lt: 5}}"); + ParsedMatchExpression lte("{x: {$lte: 5}}"); + ParsedMatchExpression gte("{x: {$gte: 5}}"); + ParsedMatchExpression gt("{x: {$gt: 5}}"); + ParsedMatchExpression in("{x: {$in: [5]}}"); + + ASSERT_TRUE(expression::isSubsetOf(nan.get(), nan.get())); + ASSERT_FALSE(expression::isSubsetOf(nan.get(), lt.get())); + ASSERT_FALSE(expression::isSubsetOf(lt.get(), nan.get())); + ASSERT_FALSE(expression::isSubsetOf(nan.get(), lte.get())); + ASSERT_FALSE(expression::isSubsetOf(lte.get(), nan.get())); + ASSERT_FALSE(expression::isSubsetOf(nan.get(), gte.get())); + ASSERT_FALSE(expression::isSubsetOf(gte.get(), nan.get())); + ASSERT_FALSE(expression::isSubsetOf(nan.get(), gt.get())); + ASSERT_FALSE(expression::isSubsetOf(gt.get(), nan.get())); + ASSERT_FALSE(expression::isSubsetOf(nan.get(), in.get())); + ASSERT_FALSE(expression::isSubsetOf(in.get(), nan.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_EQ) { + ParsedMatchExpression a5("{a: 5}"); + ParsedMatchExpression a6("{a: 6}"); + ParsedMatchExpression b5("{b: 5}"); + + ASSERT_TRUE(expression::isSubsetOf(a5.get(), a5.get())); + ASSERT_FALSE(expression::isSubsetOf(a5.get(), a6.get())); + ASSERT_FALSE(expression::isSubsetOf(a5.get(), b5.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, CompareAnd_EQ) { + ParsedMatchExpression a1B2("{a: 1, b: 2}"); + ParsedMatchExpression a1B7("{a: 1, b: 7}"); + ParsedMatchExpression a1("{a: 1}"); + ParsedMatchExpression b2("{b: 2}"); + + ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), a1B2.get())); + ASSERT_FALSE(expression::isSubsetOf(a1B2.get(), a1B7.get())); + + ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), a1.get())); + ASSERT_TRUE(expression::isSubsetOf(a1B2.get(), b2.get())); + ASSERT_FALSE(expression::isSubsetOf(a1B7.get(), b2.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, CompareAnd_GT) { + ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}}"); + ParsedMatchExpression query("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); + + ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); + ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, CompareOr_LT) { + ParsedMatchExpression lt5("{a: {$lt: 5}}"); + ParsedMatchExpression eq2OrEq3("{$or: [{a: 2}, {a: 3}]}"); + ParsedMatchExpression eq4OrEq5("{$or: [{a: 4}, {a: 5}]}"); + ParsedMatchExpression eq4OrEq6("{$or: [{a: 4}, {a: 6}]}"); + + ASSERT_TRUE(expression::isSubsetOf(eq2OrEq3.get(), lt5.get())); + ASSERT_FALSE(expression::isSubsetOf(eq4OrEq5.get(), lt5.get())); + ASSERT_FALSE(expression::isSubsetOf(eq4OrEq6.get(), lt5.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, CompareOr_GTE) { + ParsedMatchExpression gte5("{a: {$gte: 5}}"); + ParsedMatchExpression eq4OrEq6("{$or: [{a: 4}, {a: 6}]}"); + ParsedMatchExpression eq5OrEq6("{$or: [{a: 5}, {a: 6}]}"); + ParsedMatchExpression eq7OrEq8("{$or: [{a: 7}, {a: 8}]}"); + + ASSERT_FALSE(expression::isSubsetOf(eq4OrEq6.get(), gte5.get())); + ASSERT_TRUE(expression::isSubsetOf(eq5OrEq6.get(), gte5.get())); + ASSERT_TRUE(expression::isSubsetOf(eq7OrEq8.get(), gte5.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, DifferentCanonicalTypes) { + ParsedMatchExpression number("{x: {$gt: 1}}"); + ParsedMatchExpression string("{x: {$gt: 'a'}}"); + ASSERT_FALSE(expression::isSubsetOf(number.get(), string.get())); + ASSERT_FALSE(expression::isSubsetOf(string.get(), number.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, DifferentNumberTypes) { + ParsedMatchExpression numberDouble("{x: 5.0}"); + ParsedMatchExpression numberInt("{x: NumberInt(5)}"); + ParsedMatchExpression numberLong("{x: NumberLong(5)}"); + + ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberInt.get())); + ASSERT_TRUE(expression::isSubsetOf(numberDouble.get(), numberLong.get())); + ASSERT_TRUE(expression::isSubsetOf(numberInt.get(), numberDouble.get())); + ASSERT_TRUE(expression::isSubsetOf(numberInt.get(), numberLong.get())); + ASSERT_TRUE(expression::isSubsetOf(numberLong.get(), numberDouble.get())); + ASSERT_TRUE(expression::isSubsetOf(numberLong.get(), numberInt.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, PointInUnboundedRange) { + ParsedMatchExpression a4("{a: 4}"); + ParsedMatchExpression a5("{a: 5}"); + ParsedMatchExpression a6("{a: 6}"); + ParsedMatchExpression b5("{b: 5}"); + + ParsedMatchExpression lt5("{a: {$lt: 5}}"); + ParsedMatchExpression lte5("{a: {$lte: 5}}"); + ParsedMatchExpression gte5("{a: {$gte: 5}}"); + ParsedMatchExpression gt5("{a: {$gt: 5}}"); + + ASSERT_TRUE(expression::isSubsetOf(a4.get(), lte5.get())); + ASSERT_TRUE(expression::isSubsetOf(a5.get(), lte5.get())); + ASSERT_FALSE(expression::isSubsetOf(a6.get(), lte5.get())); + + ASSERT_TRUE(expression::isSubsetOf(a4.get(), lt5.get())); + ASSERT_FALSE(expression::isSubsetOf(a5.get(), lt5.get())); + ASSERT_FALSE(expression::isSubsetOf(a6.get(), lt5.get())); + + ASSERT_FALSE(expression::isSubsetOf(a4.get(), gte5.get())); + ASSERT_TRUE(expression::isSubsetOf(a5.get(), gte5.get())); + ASSERT_TRUE(expression::isSubsetOf(a6.get(), gte5.get())); + + ASSERT_FALSE(expression::isSubsetOf(a4.get(), gt5.get())); + ASSERT_FALSE(expression::isSubsetOf(a5.get(), gt5.get())); + ASSERT_TRUE(expression::isSubsetOf(a6.get(), gt5.get())); + + // An unbounded range query does not match a subset of documents of a point query. + ASSERT_FALSE(expression::isSubsetOf(lt5.get(), a5.get())); + ASSERT_FALSE(expression::isSubsetOf(lte5.get(), a5.get())); + ASSERT_FALSE(expression::isSubsetOf(gte5.get(), a5.get())); + ASSERT_FALSE(expression::isSubsetOf(gt5.get(), a5.get())); + + // Cannot be a subset if comparing different field names. + ASSERT_FALSE(expression::isSubsetOf(b5.get(), lt5.get())); + ASSERT_FALSE(expression::isSubsetOf(b5.get(), lte5.get())); + ASSERT_FALSE(expression::isSubsetOf(b5.get(), gte5.get())); + ASSERT_FALSE(expression::isSubsetOf(b5.get(), gt5.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange) { + ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); + ParsedMatchExpression query("{a: 6}"); + + ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); + ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, PointInBoundedRange_FakeAnd) { + ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); + ParsedMatchExpression query("{$and: [{a: 6}, {a: 6}]}"); + + ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); + ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, MultiplePointsInBoundedRange) { + ParsedMatchExpression filter("{a: {$gt: 5, $lt: 10}}"); + ParsedMatchExpression queryAllInside("{a: {$in: [6, 7, 8]}}"); + ParsedMatchExpression queryStraddleLower("{a: {$in: [4.9, 5.1]}}"); + ParsedMatchExpression queryStraddleUpper("{a: {$in: [9.9, 10.1]}}"); + + ASSERT_TRUE(expression::isSubsetOf(queryAllInside.get(), filter.get())); + ASSERT_FALSE(expression::isSubsetOf(queryStraddleLower.get(), filter.get())); + ASSERT_FALSE(expression::isSubsetOf(queryStraddleUpper.get(), filter.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, PointInCompoundRange) { + ParsedMatchExpression filter("{a: {$gt: 5}, b: {$gt: 6}, c: {$gt: 7}}"); + ParsedMatchExpression query("{a: 10, b: 10, c: 10}"); + + ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); + ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_LT_LTE) { + ParsedMatchExpression lte4("{x: {$lte: 4}}"); + ParsedMatchExpression lt5("{x: {$lt: 5}}"); + ParsedMatchExpression lte5("{x: {$lte: 5}}"); + ParsedMatchExpression lt6("{x: {$lt: 6}}"); + + ASSERT_TRUE(expression::isSubsetOf(lte4.get(), lte5.get())); + ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lte5.get())); + ASSERT_TRUE(expression::isSubsetOf(lte5.get(), lte5.get())); + ASSERT_FALSE(expression::isSubsetOf(lt6.get(), lte5.get())); + + ASSERT_TRUE(expression::isSubsetOf(lte4.get(), lt5.get())); + ASSERT_TRUE(expression::isSubsetOf(lt5.get(), lt5.get())); + ASSERT_FALSE(expression::isSubsetOf(lte5.get(), lt5.get())); + ASSERT_FALSE(expression::isSubsetOf(lt6.get(), lt5.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_GT_GTE) { + ParsedMatchExpression gte6("{x: {$gte: 6}}"); + ParsedMatchExpression gt5("{x: {$gt: 5}}"); + ParsedMatchExpression gte5("{x: {$gte: 5}}"); + ParsedMatchExpression gt4("{x: {$gt: 4}}"); + + ASSERT_TRUE(expression::isSubsetOf(gte6.get(), gte5.get())); + ASSERT_TRUE(expression::isSubsetOf(gt5.get(), gte5.get())); + ASSERT_TRUE(expression::isSubsetOf(gte5.get(), gte5.get())); + ASSERT_FALSE(expression::isSubsetOf(gt4.get(), gte5.get())); + + ASSERT_TRUE(expression::isSubsetOf(gte6.get(), gt5.get())); + ASSERT_TRUE(expression::isSubsetOf(gt5.get(), gt5.get())); + ASSERT_FALSE(expression::isSubsetOf(gte5.get(), gt5.get())); + ASSERT_FALSE(expression::isSubsetOf(gt4.get(), gt5.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, BoundedRangeInUnboundedRange) { + ParsedMatchExpression filter("{a: {$gt: 1}}"); + ParsedMatchExpression query("{a: {$gt: 5, $lt: 10}}"); + + ASSERT_TRUE(expression::isSubsetOf(query.get(), filter.get())); + ASSERT_FALSE(expression::isSubsetOf(filter.get(), query.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, MultipleRangesInUnboundedRange) { + ParsedMatchExpression filter("{a: {$gt: 1}}"); + ParsedMatchExpression negative("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$lt: 0}}]}"); + ParsedMatchExpression unbounded("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$gt: 15}}]}"); + ParsedMatchExpression bounded("{$or: [{a: {$gt: 5, $lt: 10}}, {a: {$gt: 20, $lt: 30}}]}"); + + ASSERT_FALSE(expression::isSubsetOf(negative.get(), filter.get())); + ASSERT_TRUE(expression::isSubsetOf(unbounded.get(), filter.get())); + ASSERT_TRUE(expression::isSubsetOf(bounded.get(), filter.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, MultipleFields) { + ParsedMatchExpression filter("{a: {$gt: 5}, b: {$lt: 10}}"); + ParsedMatchExpression onlyA("{$or: [{a: 6, b: {$lt: 4}}, {a: {$gt: 11}}]}"); + ParsedMatchExpression onlyB("{$or: [{b: {$lt: 4}}, {a: {$gt: 11}, b: 9}]}"); + ParsedMatchExpression both("{$or: [{a: 6, b: {$lt: 4}}, {a: {$gt: 11}, b: 9}]}"); + + ASSERT_FALSE(expression::isSubsetOf(onlyA.get(), filter.get())); + ASSERT_FALSE(expression::isSubsetOf(onlyB.get(), filter.get())); + ASSERT_TRUE(expression::isSubsetOf(both.get(), filter.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_LT_In) { + ParsedMatchExpression lt("{a: {$lt: 5}}"); + + ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpression inEq("{a: {$in: [5]}}"); + ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpression inNull("{a: {$in: [null]}}"); + + ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpression inAllLte("{a: {$in: [4.9, 5]}}"); + ParsedMatchExpression inAllLt("{a: {$in: [2, 3, 4]}}"); + ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpression inLtAndNull("{a: {$in: [1, null]}}"); + + ASSERT_TRUE(expression::isSubsetOf(inLt.get(), lt.get())); + ASSERT_FALSE(expression::isSubsetOf(inEq.get(), lt.get())); + ASSERT_FALSE(expression::isSubsetOf(inGt.get(), lt.get())); + + ASSERT_FALSE(expression::isSubsetOf(inAllEq.get(), lt.get())); + ASSERT_FALSE(expression::isSubsetOf(inAllLte.get(), lt.get())); + ASSERT_TRUE(expression::isSubsetOf(inAllLt.get(), lt.get())); + ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), lt.get())); + ASSERT_FALSE(expression::isSubsetOf(inLtAndNull.get(), lt.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_LTE_In) { + ParsedMatchExpression lte("{a: {$lte: 5}}"); + + ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpression inEq("{a: {$in: [5]}}"); + ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpression inNull("{a: {$in: [null]}}"); + + ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpression inAllLte("{a: {$in: [4.9, 5]}}"); + ParsedMatchExpression inAllLt("{a: {$in: [2, 3, 4]}}"); + ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpression inLtAndNull("{a: {$in: [1, null]}}"); + + ASSERT_TRUE(expression::isSubsetOf(inLt.get(), lte.get())); + ASSERT_TRUE(expression::isSubsetOf(inEq.get(), lte.get())); + ASSERT_FALSE(expression::isSubsetOf(inGt.get(), lte.get())); + + ASSERT_TRUE(expression::isSubsetOf(inAllEq.get(), lte.get())); + ASSERT_TRUE(expression::isSubsetOf(inAllLte.get(), lte.get())); + ASSERT_TRUE(expression::isSubsetOf(inAllLt.get(), lte.get())); + ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), lte.get())); + ASSERT_FALSE(expression::isSubsetOf(inLtAndNull.get(), lte.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_EQ_In) { + ParsedMatchExpression eq("{a: 5}"); + + ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpression inEq("{a: {$in: [5]}}"); + ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpression inNull("{a: {$in: [null]}}"); + + ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpression inEqAndNull("{a: {$in: [5, null]}}"); + + ASSERT_FALSE(expression::isSubsetOf(inLt.get(), eq.get())); + ASSERT_TRUE(expression::isSubsetOf(inEq.get(), eq.get())); + ASSERT_FALSE(expression::isSubsetOf(inGt.get(), eq.get())); + + ASSERT_TRUE(expression::isSubsetOf(inAllEq.get(), eq.get())); + ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), eq.get())); + ASSERT_FALSE(expression::isSubsetOf(inEqAndNull.get(), eq.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_GT_In) { + ParsedMatchExpression gt("{a: {$gt: 5}}"); + + ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpression inEq("{a: {$in: [5]}}"); + ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpression inNull("{a: {$in: [null]}}"); + + ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpression inAllGte("{a: {$in: [5, 5.1]}}"); + ParsedMatchExpression inAllGt("{a: {$in: [6, 7, 8]}}"); + ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpression inGtAndNull("{a: {$in: [9, null]}}"); + + ASSERT_FALSE(expression::isSubsetOf(inLt.get(), gt.get())); + ASSERT_FALSE(expression::isSubsetOf(inEq.get(), gt.get())); + ASSERT_TRUE(expression::isSubsetOf(inGt.get(), gt.get())); + + ASSERT_FALSE(expression::isSubsetOf(inAllEq.get(), gt.get())); + ASSERT_FALSE(expression::isSubsetOf(inAllGte.get(), gt.get())); + ASSERT_TRUE(expression::isSubsetOf(inAllGt.get(), gt.get())); + ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), gt.get())); + ASSERT_FALSE(expression::isSubsetOf(inGtAndNull.get(), gt.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_GTE_In) { + ParsedMatchExpression gte("{a: {$gte: 5}}"); + + ParsedMatchExpression inLt("{a: {$in: [4.9]}}"); + ParsedMatchExpression inEq("{a: {$in: [5]}}"); + ParsedMatchExpression inGt("{a: {$in: [5.1]}}"); + ParsedMatchExpression inNull("{a: {$in: [null]}}"); + + ParsedMatchExpression inAllEq("{a: {$in: [5, 5.0]}}"); + ParsedMatchExpression inAllGte("{a: {$in: [5, 5.1]}}"); + ParsedMatchExpression inAllGt("{a: {$in: [6, 7, 8]}}"); + ParsedMatchExpression inStraddle("{a: {$in: [4, 6]}}"); + ParsedMatchExpression inGtAndNull("{a: {$in: [9, null]}}"); + + ASSERT_FALSE(expression::isSubsetOf(inLt.get(), gte.get())); + ASSERT_TRUE(expression::isSubsetOf(inEq.get(), gte.get())); + ASSERT_TRUE(expression::isSubsetOf(inGt.get(), gte.get())); + + ASSERT_TRUE(expression::isSubsetOf(inAllEq.get(), gte.get())); + ASSERT_TRUE(expression::isSubsetOf(inAllGte.get(), gte.get())); + ASSERT_TRUE(expression::isSubsetOf(inAllGt.get(), gte.get())); + ASSERT_FALSE(expression::isSubsetOf(inStraddle.get(), gte.get())); + ASSERT_FALSE(expression::isSubsetOf(inGtAndNull.get(), gte.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, RegexAndIn) { + ParsedMatchExpression eq1("{x: 1}"); + ParsedMatchExpression eqA("{x: 'a'}"); + ParsedMatchExpression inRegexA("{x: {$in: [/a/]}}"); + ParsedMatchExpression inRegexAbc("{x: {$in: [/abc/]}}"); + ParsedMatchExpression inRegexAOrEq1("{x: {$in: [/a/, 1]}}"); + ParsedMatchExpression inRegexAOrNull("{x: {$in: [/a/, null]}}"); + + ASSERT_TRUE(expression::isSubsetOf(inRegexA.get(), inRegexA.get())); + ASSERT_FALSE(expression::isSubsetOf(inRegexAbc.get(), inRegexA.get())); + ASSERT_FALSE(expression::isSubsetOf(inRegexA.get(), inRegexAOrEq1.get())); + ASSERT_FALSE(expression::isSubsetOf(inRegexAOrEq1.get(), eq1.get())); + ASSERT_FALSE(expression::isSubsetOf(inRegexA.get(), eqA.get())); + ASSERT_FALSE(expression::isSubsetOf(inRegexAOrNull.get(), eqA.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Exists) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression bExists("{b: {$exists: true}}"); + ParsedMatchExpression aExistsBExists("{a: {$exists: true}, b: {$exists: true}}"); + ParsedMatchExpression aExistsBExistsC5("{a: {$exists: true}, b: {$exists: true}, c: 5}"); + + ASSERT_TRUE(expression::isSubsetOf(aExists.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(aExists.get(), bExists.get())); + + ASSERT_TRUE(expression::isSubsetOf(aExistsBExists.get(), aExists.get())); + ASSERT_TRUE(expression::isSubsetOf(aExistsBExists.get(), bExists.get())); + ASSERT_FALSE(expression::isSubsetOf(aExistsBExists.get(), aExistsBExistsC5.get())); + + ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), aExists.get())); + ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), bExists.get())); + ASSERT_TRUE(expression::isSubsetOf(aExistsBExistsC5.get(), aExistsBExists.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_Exists) { + ParsedMatchExpression exists("{a: {$exists: true}}"); + ParsedMatchExpression eq("{a: 1}"); + ParsedMatchExpression gt("{a: {$gt: 4}}"); + ParsedMatchExpression lte("{a: {$lte: 7}}"); + + ASSERT_TRUE(expression::isSubsetOf(eq.get(), exists.get())); + ASSERT_TRUE(expression::isSubsetOf(gt.get(), exists.get())); + ASSERT_TRUE(expression::isSubsetOf(lte.get(), exists.get())); + + ASSERT_FALSE(expression::isSubsetOf(exists.get(), eq.get())); + ASSERT_FALSE(expression::isSubsetOf(exists.get(), gt.get())); + ASSERT_FALSE(expression::isSubsetOf(exists.get(), lte.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Type) { + ParsedMatchExpression aType1("{a: {$type: 1}}"); + ParsedMatchExpression aType2("{a: {$type: 2}}"); + ParsedMatchExpression bType2("{b: {$type: 2}}"); + + ASSERT_FALSE(expression::isSubsetOf(aType1.get(), aType2.get())); + ASSERT_FALSE(expression::isSubsetOf(aType2.get(), aType1.get())); + + ASSERT_TRUE(expression::isSubsetOf(aType2.get(), aType2.get())); + ASSERT_FALSE(expression::isSubsetOf(aType2.get(), bType2.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, TypeAndExists) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aType2("{a: {$type: 2}}"); + ParsedMatchExpression bType2("{b: {$type: 2}}"); + + ASSERT_TRUE(expression::isSubsetOf(aType2.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aType2.get())); + ASSERT_FALSE(expression::isSubsetOf(bType2.get(), aExists.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, AllAndExists) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aAll("{a: {$all: ['x', 'y', 'z']}}"); + ParsedMatchExpression bAll("{b: {$all: ['x', 'y', 'z']}}"); + ParsedMatchExpression aAllWithNull("{a: {$all: ['x', null, 'z']}}"); + + ASSERT_TRUE(expression::isSubsetOf(aAll.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(bAll.get(), aExists.get())); + ASSERT_TRUE(expression::isSubsetOf(aAllWithNull.get(), aExists.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Value) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aElemMatch("{a: {$elemMatch: {$gt: 5, $lte: 10}}}"); + ParsedMatchExpression bElemMatch("{b: {$elemMatch: {$gt: 5, $lte: 10}}}"); + ParsedMatchExpression aElemMatchNull("{a: {$elemMatch: {$eq: null}}}"); + + ASSERT_TRUE(expression::isSubsetOf(aElemMatch.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aElemMatch.get())); + ASSERT_FALSE(expression::isSubsetOf(bElemMatch.get(), aExists.get())); + ASSERT_TRUE(expression::isSubsetOf(aElemMatchNull.get(), aExists.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, ElemMatchAndExists_Object) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aElemMatch("{a: {$elemMatch: {x: {$gt: 5}, y: {$lte: 10}}}}"); + ParsedMatchExpression bElemMatch("{b: {$elemMatch: {x: {$gt: 5}, y: {$lte: 10}}}}"); + ParsedMatchExpression aElemMatchNull("{a: {$elemMatch: {x: null, y: null}}}"); + + ASSERT_TRUE(expression::isSubsetOf(aElemMatch.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aElemMatch.get())); + ASSERT_FALSE(expression::isSubsetOf(bElemMatch.get(), aExists.get())); + ASSERT_TRUE(expression::isSubsetOf(aElemMatchNull.get(), aExists.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, SizeAndExists) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aSize0("{a: {$size: 0}}"); + ParsedMatchExpression aSize1("{a: {$size: 1}}"); + ParsedMatchExpression aSize3("{a: {$size: 3}}"); + ParsedMatchExpression bSize3("{b: {$size: 3}}"); + + ASSERT_TRUE(expression::isSubsetOf(aSize0.get(), aExists.get())); + ASSERT_TRUE(expression::isSubsetOf(aSize1.get(), aExists.get())); + ASSERT_TRUE(expression::isSubsetOf(aSize3.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(aExists.get(), aSize3.get())); + ASSERT_FALSE(expression::isSubsetOf(bSize3.get(), aExists.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, ModAndExists) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aMod5("{a: {$mod: [5, 0]}}"); + ParsedMatchExpression bMod5("{b: {$mod: [5, 0]}}"); + + ASSERT_TRUE(expression::isSubsetOf(aMod5.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(bMod5.get(), aExists.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, RegexAndExists) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aRegex("{a: {$regex: 'pattern'}}"); + ParsedMatchExpression bRegex("{b: {$regex: 'pattern'}}"); + + ASSERT_TRUE(expression::isSubsetOf(aRegex.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(bRegex.get(), aExists.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, InAndExists) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aIn("{a: {$in: [1, 2, 3]}}"); + ParsedMatchExpression bIn("{b: {$in: [1, 2, 3]}}"); + ParsedMatchExpression aInWithNull("{a: {$in: [1, null, 3]}}"); + + ASSERT_TRUE(expression::isSubsetOf(aIn.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(bIn.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(aInWithNull.get(), aExists.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, NinAndExists) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aNin("{a: {$nin: [1, 2, 3]}}"); + ParsedMatchExpression bNin("{b: {$nin: [1, 2, 3]}}"); + ParsedMatchExpression aNinWithNull("{a: {$nin: [1, null, 3]}}"); + + ASSERT_FALSE(expression::isSubsetOf(aNin.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(bNin.get(), aExists.get())); + ASSERT_TRUE(expression::isSubsetOf(aNinWithNull.get(), aExists.get())); +} + +TEST(ExpressionAlgoIsSubsetOf, Compare_Exists_NE) { + ParsedMatchExpression aExists("{a: {$exists: true}}"); + ParsedMatchExpression aNotEqual1("{a: {$ne: 1}}"); + ParsedMatchExpression bNotEqual1("{b: {$ne: 1}}"); + ParsedMatchExpression aNotEqualNull("{a: {$ne: null}}"); + + ASSERT_FALSE(expression::isSubsetOf(aNotEqual1.get(), aExists.get())); + ASSERT_FALSE(expression::isSubsetOf(bNotEqual1.get(), aExists.get())); + ASSERT_TRUE(expression::isSubsetOf(aNotEqualNull.get(), aExists.get())); +} } // namespace mongo diff --git a/src/mongo/db/matcher/expression_array.cpp b/src/mongo/db/matcher/expression_array.cpp index 265ac57956c..4ca68653e3d 100644 --- a/src/mongo/db/matcher/expression_array.cpp +++ b/src/mongo/db/matcher/expression_array.cpp @@ -35,224 +35,222 @@ namespace mongo { - Status ArrayMatchingMatchExpression::initPath( StringData path ) { - _path = path; - Status s = _elementPath.init( _path ); - _elementPath.setTraverseLeafArray( false ); - return s; - } +Status ArrayMatchingMatchExpression::initPath(StringData path) { + _path = path; + Status s = _elementPath.init(_path); + _elementPath.setTraverseLeafArray(false); + return s; +} - bool ArrayMatchingMatchExpression::matches( const MatchableDocument* doc, MatchDetails* details ) const { - MatchableDocument::IteratorHolder cursor( doc, &_elementPath ); +bool ArrayMatchingMatchExpression::matches(const MatchableDocument* doc, + MatchDetails* details) const { + MatchableDocument::IteratorHolder cursor(doc, &_elementPath); - while ( cursor->more() ) { - ElementIterator::Context e = cursor->next(); - if ( e.element().type() != Array ) - continue; + while (cursor->more()) { + ElementIterator::Context e = cursor->next(); + if (e.element().type() != Array) + continue; - bool amIRoot = e.arrayOffset().eoo(); + bool amIRoot = e.arrayOffset().eoo(); - if ( !matchesArray( e.element().Obj(), amIRoot ? details : NULL ) ) - continue; + if (!matchesArray(e.element().Obj(), amIRoot ? details : NULL)) + continue; - if ( !amIRoot && details && details->needRecord() && !e.arrayOffset().eoo() ) { - details->setElemMatchKey( e.arrayOffset().fieldName() ); - } - return true; + if (!amIRoot && details && details->needRecord() && !e.arrayOffset().eoo()) { + details->setElemMatchKey(e.arrayOffset().fieldName()); } - return false; + return true; } + return false; +} - bool ArrayMatchingMatchExpression::matchesSingleElement( const BSONElement& e ) const { - if ( e.type() != Array ) - return false; - return matchesArray( e.Obj(), NULL ); - } +bool ArrayMatchingMatchExpression::matchesSingleElement(const BSONElement& e) const { + if (e.type() != Array) + return false; + return matchesArray(e.Obj(), NULL); +} - bool ArrayMatchingMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; +bool ArrayMatchingMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) + return false; - const ArrayMatchingMatchExpression* realOther = - static_cast<const ArrayMatchingMatchExpression*>( other ); + const ArrayMatchingMatchExpression* realOther = + static_cast<const ArrayMatchingMatchExpression*>(other); - if ( _path != realOther->_path ) - return false; + if (_path != realOther->_path) + return false; - if ( numChildren() != realOther->numChildren() ) - 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; - } + for (unsigned i = 0; i < numChildren(); i++) + if (!getChild(i)->equivalent(realOther->getChild(i))) + return false; + return true; +} - // ------- +// ------- - Status ElemMatchObjectMatchExpression::init( StringData path, MatchExpression* sub ) { - _sub.reset( sub ); - return initPath( path ); - } +Status ElemMatchObjectMatchExpression::init(StringData path, MatchExpression* sub) { + _sub.reset(sub); + return initPath(path); +} - bool ElemMatchObjectMatchExpression::matchesArray( const BSONObj& anArray, MatchDetails* details ) const { - BSONObjIterator i( anArray ); - while ( i.more() ) { - BSONElement inner = i.next(); - if ( !inner.isABSONObj() ) - continue; - if ( _sub->matchesBSON( inner.Obj(), NULL ) ) { - if ( details && details->needRecord() ) { - details->setElemMatchKey( inner.fieldName() ); - } - return true; +bool ElemMatchObjectMatchExpression::matchesArray(const BSONObj& anArray, + MatchDetails* details) const { + BSONObjIterator i(anArray); + while (i.more()) { + BSONElement inner = i.next(); + if (!inner.isABSONObj()) + continue; + if (_sub->matchesBSON(inner.Obj(), NULL)) { + if (details && details->needRecord()) { + details->setElemMatchKey(inner.fieldName()); } + return true; } - return false; } + return false; +} - void ElemMatchObjectMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << path() << " $elemMatch (obj)"; +void ElemMatchObjectMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << path() << " $elemMatch (obj)"; - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); - } - debug << "\n"; - _sub->debugString( debug, level + 1 ); + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } + debug << "\n"; + _sub->debugString(debug, level + 1); +} - void ElemMatchObjectMatchExpression::toBSON(BSONObjBuilder* out) const { - BSONObjBuilder subBob; - _sub->toBSON(&subBob); - if (path().empty()) { - out->append("$elemMatch", subBob.obj()); - } - else { - out->append(path(), BSON("$elemMatch" << subBob.obj())); - } +void ElemMatchObjectMatchExpression::toBSON(BSONObjBuilder* out) const { + BSONObjBuilder subBob; + _sub->toBSON(&subBob); + if (path().empty()) { + out->append("$elemMatch", subBob.obj()); + } else { + out->append(path(), BSON("$elemMatch" << subBob.obj())); } +} - // ------- +// ------- - ElemMatchValueMatchExpression::~ElemMatchValueMatchExpression() { - for ( unsigned i = 0; i < _subs.size(); i++ ) - delete _subs[i]; - _subs.clear(); - } +ElemMatchValueMatchExpression::~ElemMatchValueMatchExpression() { + for (unsigned i = 0; i < _subs.size(); i++) + delete _subs[i]; + _subs.clear(); +} - Status ElemMatchValueMatchExpression::init( StringData path, MatchExpression* sub ) { - init( path ); - add( sub ); - return Status::OK(); - } +Status ElemMatchValueMatchExpression::init(StringData path, MatchExpression* sub) { + init(path); + add(sub); + return Status::OK(); +} - Status ElemMatchValueMatchExpression::init( StringData path ) { - return initPath( path ); - } +Status ElemMatchValueMatchExpression::init(StringData path) { + return initPath(path); +} - void ElemMatchValueMatchExpression::add( MatchExpression* sub ) { - verify( sub ); - _subs.push_back( sub ); - } +void ElemMatchValueMatchExpression::add(MatchExpression* sub) { + verify(sub); + _subs.push_back(sub); +} - bool ElemMatchValueMatchExpression::matchesArray( const BSONObj& anArray, MatchDetails* details ) const { - BSONObjIterator i( anArray ); - while ( i.more() ) { - BSONElement inner = i.next(); +bool ElemMatchValueMatchExpression::matchesArray(const BSONObj& anArray, + MatchDetails* details) const { + BSONObjIterator i(anArray); + while (i.more()) { + BSONElement inner = i.next(); - if ( _arrayElementMatchesAll( inner ) ) { - if ( details && details->needRecord() ) { - details->setElemMatchKey( inner.fieldName() ); - } - return true; + if (_arrayElementMatchesAll(inner)) { + if (details && details->needRecord()) { + details->setElemMatchKey(inner.fieldName()); } + return true; } - return false; } + return false; +} - bool ElemMatchValueMatchExpression::_arrayElementMatchesAll( const BSONElement& e ) const { - for ( unsigned i = 0; i < _subs.size(); i++ ) { - if ( !_subs[i]->matchesSingleElement( e ) ) - return false; - } - return true; +bool ElemMatchValueMatchExpression::_arrayElementMatchesAll(const BSONElement& e) const { + for (unsigned i = 0; i < _subs.size(); i++) { + if (!_subs[i]->matchesSingleElement(e)) + return false; } + return true; +} - void ElemMatchValueMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << path() << " $elemMatch (value)"; +void ElemMatchValueMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << path() << " $elemMatch (value)"; - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); - } - debug << "\n"; - for ( unsigned i = 0; i < _subs.size(); i++ ) { - _subs[i]->debugString( debug, level + 1 ); - } + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } - - void ElemMatchValueMatchExpression::toBSON(BSONObjBuilder* out) const { - BSONObjBuilder emBob; - for ( unsigned i = 0; i < _subs.size(); i++ ) { - _subs[i]->toBSON(&emBob); - } - if (path().empty()) { - out->append("$elemMatch", emBob.obj()); - } - else { - out->append(path(), BSON("$elemMatch" << emBob.obj())); - } + debug << "\n"; + for (unsigned i = 0; i < _subs.size(); i++) { + _subs[i]->debugString(debug, level + 1); } +} - - // --------- - - Status SizeMatchExpression::init( StringData path, int size ) { - _size = size; - return initPath( path ); +void ElemMatchValueMatchExpression::toBSON(BSONObjBuilder* out) const { + BSONObjBuilder emBob; + for (unsigned i = 0; i < _subs.size(); i++) { + _subs[i]->toBSON(&emBob); } - - bool SizeMatchExpression::matchesArray( const BSONObj& anArray, MatchDetails* details ) const { - if ( _size < 0 ) - return false; - return anArray.nFields() == _size; + if (path().empty()) { + out->append("$elemMatch", emBob.obj()); + } else { + out->append(path(), BSON("$elemMatch" << emBob.obj())); } +} - void SizeMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << path() << " $size : " << _size << "\n"; - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); - } - } +// --------- - void SizeMatchExpression::toBSON(BSONObjBuilder* out) const { - out->append(path(), BSON("$size" << _size)); - } +Status SizeMatchExpression::init(StringData path, int size) { + _size = size; + return initPath(path); +} - bool SizeMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; +bool SizeMatchExpression::matchesArray(const BSONObj& anArray, MatchDetails* details) const { + if (_size < 0) + return false; + return anArray.nFields() == _size; +} + +void SizeMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << path() << " $size : " << _size << "\n"; - const SizeMatchExpression* realOther = static_cast<const SizeMatchExpression*>( other ); - return path() == realOther->path() && _size == realOther->_size; + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } +} +void SizeMatchExpression::toBSON(BSONObjBuilder* out) const { + out->append(path(), BSON("$size" << _size)); +} - // ------------------ +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 4bc14230eb9..f9bcfd53167 100644 --- a/src/mongo/db/matcher/expression_array.h +++ b/src/mongo/db/matcher/expression_array.h @@ -40,124 +40,137 @@ namespace mongo { - class ArrayMatchingMatchExpression : public MatchExpression { - public: - ArrayMatchingMatchExpression( MatchType matchType ) : MatchExpression( matchType ){} - virtual ~ArrayMatchingMatchExpression(){} +class ArrayMatchingMatchExpression : public MatchExpression { +public: + ArrayMatchingMatchExpression(MatchType matchType) : MatchExpression(matchType) {} + virtual ~ArrayMatchingMatchExpression() {} - Status initPath( StringData path ); + Status initPath(StringData path); - virtual bool matches( const MatchableDocument* doc, MatchDetails* details ) const; + virtual bool matches(const MatchableDocument* doc, MatchDetails* details) const; - /** - * @param e - has to be an array. calls matchesArray with e as an array - */ - virtual bool matchesSingleElement( const BSONElement& e ) const; + /** + * @param e - has to be an array. calls matchesArray with e as an array + */ + virtual bool matchesSingleElement(const BSONElement& e) const; - virtual bool matchesArray( const BSONObj& anArray, MatchDetails* details ) const = 0; + virtual bool matchesArray(const BSONObj& anArray, MatchDetails* details) const = 0; - bool equivalent( const MatchExpression* other ) const; + bool equivalent(const MatchExpression* other) const; - const StringData path() const { return _path; } + const StringData path() const { + return _path; + } - private: - StringData _path; - ElementPath _elementPath; - }; +private: + StringData _path; + ElementPath _elementPath; +}; - class ElemMatchObjectMatchExpression : public ArrayMatchingMatchExpression { - public: - ElemMatchObjectMatchExpression() : ArrayMatchingMatchExpression( ELEM_MATCH_OBJECT ){} - Status init( StringData path, MatchExpression* sub ); +class ElemMatchObjectMatchExpression : public ArrayMatchingMatchExpression { +public: + ElemMatchObjectMatchExpression() : ArrayMatchingMatchExpression(ELEM_MATCH_OBJECT) {} + Status init(StringData path, MatchExpression* sub); - bool matchesArray( const BSONObj& anArray, MatchDetails* details ) const; + bool matchesArray(const BSONObj& anArray, MatchDetails* details) const; - virtual ElemMatchObjectMatchExpression* shallowClone() const { - ElemMatchObjectMatchExpression* e = new ElemMatchObjectMatchExpression(); - e->init(path(), _sub->shallowClone()); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; + virtual ElemMatchObjectMatchExpression* shallowClone() const { + ElemMatchObjectMatchExpression* e = new ElemMatchObjectMatchExpression(); + e->init(path(), _sub->shallowClone()); + if (getTag()) { + e->setTag(getTag()->clone()); } + return e; + } - virtual void debugString( StringBuilder& debug, int level ) const; + virtual void debugString(StringBuilder& debug, int level) const; - virtual void toBSON(BSONObjBuilder* out) const; + virtual void toBSON(BSONObjBuilder* out) const; - virtual size_t numChildren() const { return 1; } + virtual size_t numChildren() const { + return 1; + } - virtual MatchExpression* getChild( size_t i ) const { return _sub.get(); } + virtual MatchExpression* getChild(size_t i) const { + return _sub.get(); + } - private: - std::unique_ptr<MatchExpression> _sub; - }; +private: + std::unique_ptr<MatchExpression> _sub; +}; - class ElemMatchValueMatchExpression : public ArrayMatchingMatchExpression { - public: - ElemMatchValueMatchExpression() : ArrayMatchingMatchExpression( ELEM_MATCH_VALUE ){} - virtual ~ElemMatchValueMatchExpression(); +class ElemMatchValueMatchExpression : public ArrayMatchingMatchExpression { +public: + ElemMatchValueMatchExpression() : ArrayMatchingMatchExpression(ELEM_MATCH_VALUE) {} + virtual ~ElemMatchValueMatchExpression(); - Status init( StringData path ); - Status init( StringData path, MatchExpression* sub ); - void add( MatchExpression* sub ); + Status init(StringData path); + Status init(StringData path, MatchExpression* sub); + void add(MatchExpression* sub); - bool matchesArray( const BSONObj& anArray, MatchDetails* details ) const; + bool matchesArray(const BSONObj& anArray, MatchDetails* details) const; - virtual ElemMatchValueMatchExpression* shallowClone() const { - ElemMatchValueMatchExpression* e = new ElemMatchValueMatchExpression(); - e->init(path()); - for (size_t i = 0; i < _subs.size(); ++i) { - e->add(_subs[i]->shallowClone()); - } - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; + virtual ElemMatchValueMatchExpression* shallowClone() const { + ElemMatchValueMatchExpression* e = new ElemMatchValueMatchExpression(); + e->init(path()); + for (size_t i = 0; i < _subs.size(); ++i) { + e->add(_subs[i]->shallowClone()); } + if (getTag()) { + e->setTag(getTag()->clone()); + } + return e; + } - virtual void debugString( StringBuilder& debug, int level ) const; + virtual void debugString(StringBuilder& debug, int level) const; - virtual void toBSON(BSONObjBuilder* out) const; + virtual void toBSON(BSONObjBuilder* out) const; - virtual std::vector<MatchExpression*>* getChildVector() { return &_subs; } + virtual std::vector<MatchExpression*>* getChildVector() { + return &_subs; + } - virtual size_t numChildren() const { return _subs.size(); } + virtual size_t numChildren() const { + return _subs.size(); + } - virtual MatchExpression* getChild( size_t i ) const { return _subs[i]; } + virtual MatchExpression* getChild(size_t i) const { + return _subs[i]; + } - private: - bool _arrayElementMatchesAll( const BSONElement& e ) const; +private: + bool _arrayElementMatchesAll(const BSONElement& e) const; - std::vector<MatchExpression*> _subs; - }; + std::vector<MatchExpression*> _subs; +}; - class SizeMatchExpression : public ArrayMatchingMatchExpression { - public: - SizeMatchExpression() : ArrayMatchingMatchExpression( SIZE ){} - Status init( StringData path, int size ); +class SizeMatchExpression : public ArrayMatchingMatchExpression { +public: + SizeMatchExpression() : ArrayMatchingMatchExpression(SIZE) {} + Status init(StringData path, int size); - virtual SizeMatchExpression* shallowClone() const { - SizeMatchExpression* e = new SizeMatchExpression(); - e->init(path(), _size); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; + virtual SizeMatchExpression* shallowClone() const { + SizeMatchExpression* e = new SizeMatchExpression(); + e->init(path(), _size); + if (getTag()) { + e->setTag(getTag()->clone()); } + return e; + } - virtual bool matchesArray( const BSONObj& anArray, MatchDetails* details ) const; - - virtual void debugString( StringBuilder& debug, int level ) const; + virtual bool matchesArray(const BSONObj& anArray, MatchDetails* details) const; - virtual void toBSON(BSONObjBuilder* out) const; + virtual void debugString(StringBuilder& debug, int level) const; - virtual bool equivalent( const MatchExpression* other ) const; + virtual void toBSON(BSONObjBuilder* out) const; - int getData() const { return _size; } + virtual bool equivalent(const MatchExpression* other) const; - private: - int _size; // >= 0 real, < 0, nothing will match - }; + int getData() const { + return _size; + } +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 891d12752f6..201d11fbed4 100644 --- a/src/mongo/db/matcher/expression_array_test.cpp +++ b/src/mongo/db/matcher/expression_array_test.cpp @@ -38,448 +38,422 @@ namespace mongo { - using std::unique_ptr; - - TEST( ElemMatchObjectMatchExpression, MatchesElementSingle ) { - BSONObj baseOperand = BSON( "b" << 5 ); - BSONObj match = BSON( "a" << BSON_ARRAY( BSON( "b" << 5.0 ) ) ); - BSONObj notMatch = BSON( "a" << BSON_ARRAY( BSON( "b" << 6 ) ) ); - unique_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" ] ) ); - ASSERT( !op.matchesSingleElement( notMatch[ "a" ] ) ); - } - - TEST( ElemMatchObjectMatchExpression, MatchesElementArray ) { - 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 ) ) ); - unique_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" ] ) ); - ASSERT( !op.matchesSingleElement( notMatch[ "a" ] ) ); - } - - TEST( ElemMatchObjectMatchExpression, MatchesElementMultiple ) { - BSONObj baseOperand1 = BSON( "b" << 5 ); - BSONObj baseOperand2 = BSON( "b" << 6 ); - BSONObj baseOperand3 = BSON( "c" << 7 ); - BSONObj notMatch1 = BSON( "a" << BSON_ARRAY( BSON( "b" << 5 << "c" << 7 ) ) ); - BSONObj notMatch2 = BSON( "a" << BSON_ARRAY( BSON( "b" << 6 << "c" << 7 ) ) ); - 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 ) ) ); - unique_ptr<ComparisonMatchExpression> eq1( new EqualityMatchExpression() ); - ASSERT( eq1->init( "b", baseOperand1[ "b" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> eq2( new EqualityMatchExpression() ); - ASSERT( eq2->init( "b", baseOperand2[ "b" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> eq3( new EqualityMatchExpression() ); - ASSERT( eq3->init( "c", baseOperand3[ "c" ] ).isOK() ); - - unique_ptr<AndMatchExpression> andOp( new AndMatchExpression() ); - andOp->add( eq1.release() ); - andOp->add( eq2.release() ); - andOp->add( eq3.release() ); - - ElemMatchObjectMatchExpression op; - ASSERT( op.init( "a", andOp.release() ).isOK() ); - ASSERT( !op.matchesSingleElement( notMatch1[ "a" ] ) ); - ASSERT( !op.matchesSingleElement( notMatch2[ "a" ] ) ); - ASSERT( !op.matchesSingleElement( notMatch3[ "a" ] ) ); - ASSERT( op.matchesSingleElement( match[ "a" ] ) ); - } - - TEST( ElemMatchObjectMatchExpression, MatchesNonArray ) { - BSONObj baseOperand = BSON( "b" << 5 ); - unique_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 - // required. - ASSERT( !op.matchesBSON( BSON( "a" << BSON( "b" << 5 ) ), NULL ) ); - ASSERT( !op.matchesBSON( BSON( "a" << BSON( "0" << ( BSON( "b" << 5 ) ) ) ), NULL ) ); - ASSERT( !op.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( ElemMatchObjectMatchExpression, MatchesArrayObject ) { - BSONObj baseOperand = BSON( "b" << 5 ); - unique_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); - ASSERT( eq->init( "b", baseOperand[ "b" ] ).isOK() ); - ElemMatchObjectMatchExpression op; - ASSERT( op.init( "a", eq.release() ).isOK() ); - ASSERT( op.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << 5 ) ) ), NULL ) ); - ASSERT( op.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << BSON( "b" << 5 ) ) ), NULL ) ); - ASSERT( op.matchesBSON( BSON( "a" << BSON_ARRAY( BSONObj() << BSON( "b" << 5 ) ) ), NULL ) ); - ASSERT( op.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << 6 ) << BSON( "b" << 5 ) ) ), - NULL ) ); - } - - TEST( ElemMatchObjectMatchExpression, MatchesMultipleNamedValues ) { - BSONObj baseOperand = BSON( "c" << 5 ); - unique_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); - ASSERT( eq->init( "c", baseOperand[ "c" ] ).isOK() ); - ElemMatchObjectMatchExpression op; - ASSERT( op.init( "a.b", eq.release() ).isOK() ); - ASSERT( op.matchesBSON( BSON( "a" << - BSON_ARRAY( BSON( "b" << - BSON_ARRAY( BSON( "c" << - 5 ) ) ) ) ), - NULL ) ); - ASSERT( op.matchesBSON( BSON( "a" << - BSON_ARRAY( BSON( "b" << - BSON_ARRAY( BSON( "c" << - 1 ) ) ) << - BSON( "b" << - BSON_ARRAY( BSON( "c" << - 5 ) ) ) ) ), - NULL ) ); - } - - TEST( ElemMatchObjectMatchExpression, ElemMatchKey ) { - BSONObj baseOperand = BSON( "c" << 6 ); - unique_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); - ASSERT( eq->init( "c", baseOperand[ "c" ] ).isOK() ); - ElemMatchObjectMatchExpression op; - ASSERT( op.init( "a.b", eq.release() ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !op.matchesBSON( BSONObj(), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( !op.matchesBSON( BSON( "a" << BSON( "b" << BSON_ARRAY( BSON( "c" << 7 ) ) ) ), - &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( op.matchesBSON( BSON( "a" << BSON( "b" << BSON_ARRAY( 3 << BSON( "c" << 6 ) ) ) ), - &details ) ); - ASSERT( details.hasElemMatchKey() ); - // The entry within the $elemMatch array is reported. - ASSERT_EQUALS( "1", details.elemMatchKey() ); - ASSERT( op.matchesBSON( BSON( "a" << - BSON_ARRAY( 1 << 2 << - BSON( "b" << BSON_ARRAY( 3 << - 5 << - BSON( "c" << 6 ) ) ) ) ), - &details ) ); - ASSERT( details.hasElemMatchKey() ); - // The entry within a parent of the $elemMatch array is reported. - ASSERT_EQUALS( "2", details.elemMatchKey() ); - } - - /** - TEST( ElemMatchObjectMatchExpression, MatchesIndexKey ) { - BSONObj baseOperand = BSON( "b" << 5 ); - unique_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() ); - ASSERT( eq->init( "b", baseOperand[ "b" ] ).isOK() ); - ElemMatchObjectMatchExpression op; - ASSERT( op.init( "a", eq.release() ).isOK() ); - IndexSpec indexSpec( BSON( "a.b" << 1 ) ); - BSONObj indexKey = BSON( "" << "5" ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - op.matchesIndexKey( indexKey, indexSpec ) ); - } - */ - - TEST( ElemMatchValueMatchExpression, MatchesElementSingle ) { - BSONObj baseOperand = BSON( "$gt" << 5 ); - BSONObj match = BSON( "a" << BSON_ARRAY( 6 ) ); - BSONObj notMatch = BSON( "a" << BSON_ARRAY( 4 ) ); - unique_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" ] ) ); - ASSERT( !op.matchesSingleElement( notMatch[ "a" ] ) ); - } - - TEST( ElemMatchValueMatchExpression, MatchesElementMultiple ) { - BSONObj baseOperand1 = BSON( "$gt" << 1 ); - BSONObj baseOperand2 = BSON( "$lt" << 10 ); - 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 ) ); - unique_ptr<ComparisonMatchExpression> gt( new GTMatchExpression() ); - ASSERT( gt->init( "", baseOperand1[ "$gt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> lt( new LTMatchExpression() ); - ASSERT( lt->init( "", baseOperand2[ "$lt" ] ).isOK() ); - - ElemMatchValueMatchExpression op; - ASSERT( op.init( "a" ).isOK() ); - op.add( gt.release() ); - op.add( lt.release() ); - - ASSERT( !op.matchesSingleElement( notMatch1[ "a" ] ) ); - ASSERT( !op.matchesSingleElement( notMatch2[ "a" ] ) ); - ASSERT( op.matchesSingleElement( match[ "a" ] ) ); - } - - TEST( ElemMatchValueMatchExpression, MatchesNonArray ) { - BSONObj baseOperand = BSON( "$gt" << 5 ); - unique_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 - // required. - ASSERT( !op.matchesBSON( BSON( "a" << 6 ), NULL ) ); - ASSERT( !op.matchesBSON( BSON( "a" << BSON( "0" << 6 ) ), NULL ) ); - } - - TEST( ElemMatchValueMatchExpression, MatchesArrayScalar ) { - BSONObj baseOperand = BSON( "$gt" << 5 ); - unique_ptr<ComparisonMatchExpression> gt( new GTMatchExpression() ); - ASSERT( gt->init( "", baseOperand[ "$gt" ] ).isOK() ); - ElemMatchValueMatchExpression op; - ASSERT( op.init( "a", gt.release() ).isOK() ); - ASSERT( op.matchesBSON( BSON( "a" << BSON_ARRAY( 6 ) ), NULL ) ); - ASSERT( op.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 6 ) ), NULL ) ); - ASSERT( op.matchesBSON( BSON( "a" << BSON_ARRAY( BSONObj() << 7 ) ), NULL ) ); - } - - TEST( ElemMatchValueMatchExpression, MatchesMultipleNamedValues ) { - BSONObj baseOperand = BSON( "$gt" << 5 ); - unique_ptr<ComparisonMatchExpression> gt( new GTMatchExpression() ); - ASSERT( gt->init( "", baseOperand[ "$gt" ] ).isOK() ); - ElemMatchValueMatchExpression op; - ASSERT( op.init( "a.b", gt.release() ).isOK() ); - ASSERT( op.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << BSON_ARRAY( 6 ) ) ) ), NULL ) ); - ASSERT( op.matchesBSON( BSON( "a" << - BSON_ARRAY( BSON( "b" << BSON_ARRAY( 4 ) ) << - BSON( "b" << BSON_ARRAY( 4 << 6 ) ) ) ), - NULL ) ); - } - - TEST( ElemMatchValueMatchExpression, ElemMatchKey ) { - BSONObj baseOperand = BSON( "$gt" << 6 ); - unique_ptr<ComparisonMatchExpression> gt( new GTMatchExpression() ); - ASSERT( gt->init( "", baseOperand[ "$gt" ] ).isOK() ); - ElemMatchValueMatchExpression op; - ASSERT( op.init( "a.b", gt.release() ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !op.matchesBSON( BSONObj(), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( !op.matchesBSON( BSON( "a" << BSON( "b" << BSON_ARRAY( 2 ) ) ), - &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( op.matchesBSON( BSON( "a" << BSON( "b" << BSON_ARRAY( 3 << 7 ) ) ), - &details ) ); - ASSERT( details.hasElemMatchKey() ); - // The entry within the $elemMatch array is reported. - ASSERT_EQUALS( "1", details.elemMatchKey() ); - ASSERT( op.matchesBSON( BSON( "a" << - BSON_ARRAY( 1 << 2 << - BSON( "b" << BSON_ARRAY( 3 << 7 ) ) ) ), - &details ) ); - ASSERT( details.hasElemMatchKey() ); - // The entry within a parent of the $elemMatch array is reported. - ASSERT_EQUALS( "2", details.elemMatchKey() ); - } - - /** - TEST( ElemMatchValueMatchExpression, MatchesIndexKey ) { - BSONObj baseOperand = BSON( "$lt" << 5 ); - unique_ptr<LtOp> lt( new ComparisonMatchExpression() ); - ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); - ElemMatchValueMatchExpression op; - ASSERT( op.init( "a", lt.release() ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - BSONObj indexKey = BSON( "" << "3" ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - op.matchesIndexKey( indexKey, indexSpec ) ); - } - */ - - TEST( AndOfElemMatch, MatchesElement ) { - - - BSONObj baseOperanda1 = BSON( "a" << 1 ); - unique_ptr<ComparisonMatchExpression> eqa1( new EqualityMatchExpression() ); - ASSERT( eqa1->init( "a", baseOperanda1[ "a" ] ).isOK() ); - - BSONObj baseOperandb1 = BSON( "b" << 1 ); - unique_ptr<ComparisonMatchExpression> eqb1( new EqualityMatchExpression() ); - ASSERT( eqb1->init( "b", baseOperandb1[ "b" ] ).isOK() ); - - unique_ptr<AndMatchExpression> and1( new AndMatchExpression() ); - and1->add( eqa1.release() ); - and1->add( eqb1.release() ); - // and1 = { a : 1, b : 1 } - - unique_ptr<ElemMatchObjectMatchExpression> elemMatch1( new ElemMatchObjectMatchExpression() ); - elemMatch1->init( "x", and1.release() ); - // elemMatch1 = { x : { $elemMatch : { a : 1, b : 1 } } } - - BSONObj baseOperanda2 = BSON( "a" << 2 ); - unique_ptr<ComparisonMatchExpression> eqa2( new EqualityMatchExpression() ); - ASSERT( eqa2->init( "a", baseOperanda2[ "a" ] ).isOK() ); - - BSONObj baseOperandb2 = BSON( "b" << 2 ); - unique_ptr<ComparisonMatchExpression> eqb2( new EqualityMatchExpression() ); - ASSERT( eqb2->init( "b", baseOperandb2[ "b" ] ).isOK() ); - - unique_ptr<AndMatchExpression> and2( new AndMatchExpression() ); - and2->add( eqa2.release() ); - and2->add( eqb2.release() ); - // and2 = { a : 2, b : 2 } - - unique_ptr<ElemMatchObjectMatchExpression> elemMatch2( new ElemMatchObjectMatchExpression() ); - elemMatch2->init( "x", and2.release() ); - // elemMatch2 = { x : { $elemMatch : { a : 2, b : 2 } } } - - unique_ptr<AndMatchExpression> andOfEM( new AndMatchExpression() ); - andOfEM->add( elemMatch1.release() ); - andOfEM->add( elemMatch2.release() ); - - BSONObj nonArray = BSON( "x" << 4 ); - ASSERT( !andOfEM->matchesSingleElement( nonArray[ "x" ] ) ); - BSONObj emptyArray = BSON( "x" << BSONArray() ); - ASSERT( !andOfEM->matchesSingleElement( emptyArray[ "x" ] ) ); - BSONObj nonObjArray = BSON( "x" << BSON_ARRAY( 4 ) ); - ASSERT( !andOfEM->matchesSingleElement( nonObjArray[ "x" ] ) ); - BSONObj singleObjMatch = BSON( "x" << BSON_ARRAY( BSON( "a" << 1 << "b" << 1 ) ) ); - ASSERT( !andOfEM->matchesSingleElement( singleObjMatch[ "x" ] ) ); - BSONObj otherObjMatch = BSON( "x" << BSON_ARRAY( BSON( "a" << 2 << "b" << 2 ) ) ); - ASSERT( !andOfEM->matchesSingleElement( otherObjMatch[ "x" ] ) ); - BSONObj bothObjMatch = BSON( "x" << BSON_ARRAY( BSON( "a" << 1 << "b" << 1 ) << - BSON( "a" << 2 << "b" << 2 ) ) ); - ASSERT( andOfEM->matchesSingleElement( bothObjMatch[ "x" ] ) ); - BSONObj noObjMatch = BSON( "x" << BSON_ARRAY( BSON( "a" << 1 << "b" << 2 ) << - BSON( "a" << 2 << "b" << 1 ) ) ); - ASSERT( !andOfEM->matchesSingleElement( noObjMatch[ "x" ] ) ); - } - - TEST( AndOfElemMatch, Matches ) { - BSONObj baseOperandgt1 = BSON( "$gt" << 1 ); - unique_ptr<ComparisonMatchExpression> gt1( new GTMatchExpression() ); - ASSERT( gt1->init( "", baseOperandgt1[ "$gt" ] ).isOK() ); - - BSONObj baseOperandlt1 = BSON( "$lt" << 10 ); - unique_ptr<ComparisonMatchExpression> lt1( new LTMatchExpression() ); - ASSERT( lt1->init( "", baseOperandlt1[ "$lt" ] ).isOK() ); - - unique_ptr<ElemMatchValueMatchExpression> elemMatch1( new ElemMatchValueMatchExpression() ); - elemMatch1->init( "x" ); - elemMatch1->add( gt1.release() ); - elemMatch1->add( lt1.release() ); - // elemMatch1 = { x : { $elemMatch : { $gt : 1 , $lt : 10 } } } - - BSONObj baseOperandgt2 = BSON( "$gt" << 101 ); - unique_ptr<ComparisonMatchExpression> gt2( new GTMatchExpression() ); - ASSERT( gt2->init( "", baseOperandgt2[ "$gt" ] ).isOK() ); - - BSONObj baseOperandlt2 = BSON( "$lt" << 110 ); - unique_ptr<ComparisonMatchExpression> lt2( new LTMatchExpression() ); - ASSERT( lt2->init( "", baseOperandlt2[ "$lt" ] ).isOK() ); - - unique_ptr<ElemMatchValueMatchExpression> elemMatch2( new ElemMatchValueMatchExpression() ); - elemMatch2->init( "x" ); - elemMatch2->add( gt2.release() ); - elemMatch2->add( lt2.release() ); - // elemMatch2 = { x : { $elemMatch : { $gt : 101 , $lt : 110 } } } - - unique_ptr<AndMatchExpression> andOfEM( new AndMatchExpression() ); - andOfEM->add( elemMatch1.release() ); - andOfEM->add( elemMatch2.release() ); - - BSONObj nonArray = BSON( "x" << 4 ); - ASSERT( !andOfEM->matchesBSON( nonArray, NULL ) ); - BSONObj emptyArray = BSON( "x" << BSONArray() ); - ASSERT( !andOfEM->matchesBSON( emptyArray, NULL ) ); - BSONObj nonNumberArray = BSON( "x" << BSON_ARRAY( "q" ) ); - ASSERT( !andOfEM->matchesBSON( nonNumberArray, NULL ) ); - BSONObj singleMatch = BSON( "x" << BSON_ARRAY( 5 ) ); - ASSERT( !andOfEM->matchesBSON( singleMatch, NULL ) ); - BSONObj otherMatch = BSON( "x" << BSON_ARRAY( 105 ) ); - ASSERT( !andOfEM->matchesBSON( otherMatch, NULL ) ); - BSONObj bothMatch = BSON( "x" << BSON_ARRAY( 5 << 105 ) ); - ASSERT( andOfEM->matchesBSON( bothMatch, NULL ) ); - BSONObj neitherMatch = BSON( "x" << BSON_ARRAY( 0 << 200 ) ); - ASSERT( !andOfEM->matchesBSON( neitherMatch, NULL ) ); - } - - TEST( SizeMatchExpression, MatchesElement ) { - BSONObj match = BSON( "a" << BSON_ARRAY( 5 << 6 ) ); - BSONObj notMatch = BSON( "a" << BSON_ARRAY( 5 ) ); - SizeMatchExpression size; - ASSERT( size.init( "", 2 ).isOK() ); - ASSERT( size.matchesSingleElement( match.firstElement() ) ); - ASSERT( !size.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( SizeMatchExpression, MatchesNonArray ) { - // Non arrays do not match. - BSONObj stringValue = BSON( "a" << "z" ); - BSONObj numberValue = BSON( "a" << 0 ); - BSONObj arrayValue = BSON( "a" << BSONArray() ); - SizeMatchExpression size; - ASSERT( size.init( "", 0 ).isOK() ); - ASSERT( !size.matchesSingleElement( stringValue.firstElement() ) ); - ASSERT( !size.matchesSingleElement( numberValue.firstElement() ) ); - ASSERT( size.matchesSingleElement( arrayValue.firstElement() ) ); - } - - TEST( SizeMatchExpression, MatchesArray ) { - SizeMatchExpression size; - ASSERT( size.init( "a", 2 ).isOK() ); - ASSERT( size.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 5.5 ) ), NULL ) ); - // Arrays are not unwound to look for matching subarrays. - ASSERT( !size.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 5.5 << BSON_ARRAY( 1 << 2 ) ) ), - NULL ) ); - } - - TEST( SizeMatchExpression, MatchesNestedArray ) { - SizeMatchExpression size; - ASSERT( size.init( "a.2", 2 ).isOK() ); - // A numerically referenced nested array is matched. - ASSERT( size.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 5.5 << BSON_ARRAY( 1 << 2 ) ) ), - NULL ) ); - } - - TEST( SizeMatchExpression, ElemMatchKey ) { - SizeMatchExpression size; - ASSERT( size.init( "a.b", 3 ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !size.matchesBSON( BSON( "a" << 1 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( size.matchesBSON( BSON( "a" << BSON( "b" << BSON_ARRAY( 1 << 2 << 3 ) ) ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( size.matchesBSON( BSON( "a" << - BSON_ARRAY( 2 << - BSON( "b" << BSON_ARRAY( 1 << 2 << 3 ) ) ) ), - &details ) ); - ASSERT( details.hasElemMatchKey() ); - 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 ); - SizeMatchExpression size; - ASSERT( size.init( "a", operand[ "$size" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - BSONObj indexKey = BSON( "" << 1 ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - size.matchesIndexKey( indexKey, indexSpec ) ); - } - */ - -} // namespace mongo +using std::unique_ptr; + +TEST(ElemMatchObjectMatchExpression, MatchesElementSingle) { + BSONObj baseOperand = BSON("b" << 5); + BSONObj match = BSON("a" << BSON_ARRAY(BSON("b" << 5.0))); + BSONObj notMatch = BSON("a" << BSON_ARRAY(BSON("b" << 6))); + unique_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"])); + ASSERT(!op.matchesSingleElement(notMatch["a"])); +} + +TEST(ElemMatchObjectMatchExpression, MatchesElementArray) { + 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))); + unique_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"])); + ASSERT(!op.matchesSingleElement(notMatch["a"])); +} + +TEST(ElemMatchObjectMatchExpression, MatchesElementMultiple) { + BSONObj baseOperand1 = BSON("b" << 5); + BSONObj baseOperand2 = BSON("b" << 6); + BSONObj baseOperand3 = BSON("c" << 7); + BSONObj notMatch1 = BSON("a" << BSON_ARRAY(BSON("b" << 5 << "c" << 7))); + BSONObj notMatch2 = BSON("a" << BSON_ARRAY(BSON("b" << 6 << "c" << 7))); + 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))); + unique_ptr<ComparisonMatchExpression> eq1(new EqualityMatchExpression()); + ASSERT(eq1->init("b", baseOperand1["b"]).isOK()); + unique_ptr<ComparisonMatchExpression> eq2(new EqualityMatchExpression()); + ASSERT(eq2->init("b", baseOperand2["b"]).isOK()); + unique_ptr<ComparisonMatchExpression> eq3(new EqualityMatchExpression()); + ASSERT(eq3->init("c", baseOperand3["c"]).isOK()); + + unique_ptr<AndMatchExpression> andOp(new AndMatchExpression()); + andOp->add(eq1.release()); + andOp->add(eq2.release()); + andOp->add(eq3.release()); + + ElemMatchObjectMatchExpression op; + ASSERT(op.init("a", andOp.release()).isOK()); + ASSERT(!op.matchesSingleElement(notMatch1["a"])); + ASSERT(!op.matchesSingleElement(notMatch2["a"])); + ASSERT(!op.matchesSingleElement(notMatch3["a"])); + ASSERT(op.matchesSingleElement(match["a"])); +} + +TEST(ElemMatchObjectMatchExpression, MatchesNonArray) { + BSONObj baseOperand = BSON("b" << 5); + unique_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 + // required. + ASSERT(!op.matchesBSON(BSON("a" << BSON("b" << 5)), NULL)); + ASSERT(!op.matchesBSON(BSON("a" << BSON("0" << (BSON("b" << 5)))), NULL)); + ASSERT(!op.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(ElemMatchObjectMatchExpression, MatchesArrayObject) { + BSONObj baseOperand = BSON("b" << 5); + unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); + ASSERT(eq->init("b", baseOperand["b"]).isOK()); + ElemMatchObjectMatchExpression op; + ASSERT(op.init("a", eq.release()).isOK()); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 5))), NULL)); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(4 << BSON("b" << 5))), NULL)); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSONObj() << BSON("b" << 5))), NULL)); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 6) << BSON("b" << 5))), NULL)); +} + +TEST(ElemMatchObjectMatchExpression, MatchesMultipleNamedValues) { + BSONObj baseOperand = BSON("c" << 5); + unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); + ASSERT(eq->init("c", baseOperand["c"]).isOK()); + ElemMatchObjectMatchExpression op; + ASSERT(op.init("a.b", eq.release()).isOK()); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(BSON("c" << 5))))), NULL)); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(BSON("c" << 1))) + << BSON("b" << BSON_ARRAY(BSON("c" << 5))))), + NULL)); +} + +TEST(ElemMatchObjectMatchExpression, ElemMatchKey) { + BSONObj baseOperand = BSON("c" << 6); + unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); + ASSERT(eq->init("c", baseOperand["c"]).isOK()); + ElemMatchObjectMatchExpression op; + ASSERT(op.init("a.b", eq.release()).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!op.matchesBSON(BSONObj(), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(!op.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(BSON("c" << 7)))), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(op.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(3 << BSON("c" << 6)))), &details)); + ASSERT(details.hasElemMatchKey()); + // The entry within the $elemMatch array is reported. + ASSERT_EQUALS("1", details.elemMatchKey()); + ASSERT(op.matchesBSON( + BSON("a" << BSON_ARRAY(1 << 2 << BSON("b" << BSON_ARRAY(3 << 5 << BSON("c" << 6))))), + &details)); + ASSERT(details.hasElemMatchKey()); + // The entry within a parent of the $elemMatch array is reported. + ASSERT_EQUALS("2", details.elemMatchKey()); +} + +/** +TEST( ElemMatchObjectMatchExpression, MatchesIndexKey ) { + BSONObj baseOperand = BSON( "b" << 5 ); + unique_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() ); + ASSERT( eq->init( "b", baseOperand[ "b" ] ).isOK() ); + ElemMatchObjectMatchExpression op; + ASSERT( op.init( "a", eq.release() ).isOK() ); + IndexSpec indexSpec( BSON( "a.b" << 1 ) ); + BSONObj indexKey = BSON( "" << "5" ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + op.matchesIndexKey( indexKey, indexSpec ) ); +} +*/ + +TEST(ElemMatchValueMatchExpression, MatchesElementSingle) { + BSONObj baseOperand = BSON("$gt" << 5); + BSONObj match = BSON("a" << BSON_ARRAY(6)); + BSONObj notMatch = BSON("a" << BSON_ARRAY(4)); + unique_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"])); + ASSERT(!op.matchesSingleElement(notMatch["a"])); +} + +TEST(ElemMatchValueMatchExpression, MatchesElementMultiple) { + BSONObj baseOperand1 = BSON("$gt" << 1); + BSONObj baseOperand2 = BSON("$lt" << 10); + 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)); + unique_ptr<ComparisonMatchExpression> gt(new GTMatchExpression()); + ASSERT(gt->init("", baseOperand1["$gt"]).isOK()); + unique_ptr<ComparisonMatchExpression> lt(new LTMatchExpression()); + ASSERT(lt->init("", baseOperand2["$lt"]).isOK()); + + ElemMatchValueMatchExpression op; + ASSERT(op.init("a").isOK()); + op.add(gt.release()); + op.add(lt.release()); + + ASSERT(!op.matchesSingleElement(notMatch1["a"])); + ASSERT(!op.matchesSingleElement(notMatch2["a"])); + ASSERT(op.matchesSingleElement(match["a"])); +} + +TEST(ElemMatchValueMatchExpression, MatchesNonArray) { + BSONObj baseOperand = BSON("$gt" << 5); + unique_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 + // required. + ASSERT(!op.matchesBSON(BSON("a" << 6), NULL)); + ASSERT(!op.matchesBSON(BSON("a" << BSON("0" << 6)), NULL)); +} + +TEST(ElemMatchValueMatchExpression, MatchesArrayScalar) { + BSONObj baseOperand = BSON("$gt" << 5); + unique_ptr<ComparisonMatchExpression> gt(new GTMatchExpression()); + ASSERT(gt->init("", baseOperand["$gt"]).isOK()); + ElemMatchValueMatchExpression op; + ASSERT(op.init("a", gt.release()).isOK()); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(6)), NULL)); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(4 << 6)), NULL)); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSONObj() << 7)), NULL)); +} + +TEST(ElemMatchValueMatchExpression, MatchesMultipleNamedValues) { + BSONObj baseOperand = BSON("$gt" << 5); + unique_ptr<ComparisonMatchExpression> gt(new GTMatchExpression()); + ASSERT(gt->init("", baseOperand["$gt"]).isOK()); + ElemMatchValueMatchExpression op; + ASSERT(op.init("a.b", gt.release()).isOK()); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(6)))), NULL)); + ASSERT(op.matchesBSON( + BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(4)) << BSON("b" << BSON_ARRAY(4 << 6)))), + NULL)); +} + +TEST(ElemMatchValueMatchExpression, ElemMatchKey) { + BSONObj baseOperand = BSON("$gt" << 6); + unique_ptr<ComparisonMatchExpression> gt(new GTMatchExpression()); + ASSERT(gt->init("", baseOperand["$gt"]).isOK()); + ElemMatchValueMatchExpression op; + ASSERT(op.init("a.b", gt.release()).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!op.matchesBSON(BSONObj(), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(!op.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(2))), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(op.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(3 << 7))), &details)); + ASSERT(details.hasElemMatchKey()); + // The entry within the $elemMatch array is reported. + ASSERT_EQUALS("1", details.elemMatchKey()); + ASSERT(op.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << BSON("b" << BSON_ARRAY(3 << 7)))), + &details)); + ASSERT(details.hasElemMatchKey()); + // The entry within a parent of the $elemMatch array is reported. + ASSERT_EQUALS("2", details.elemMatchKey()); +} + +/** +TEST( ElemMatchValueMatchExpression, MatchesIndexKey ) { + BSONObj baseOperand = BSON( "$lt" << 5 ); + unique_ptr<LtOp> lt( new ComparisonMatchExpression() ); + ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); + ElemMatchValueMatchExpression op; + ASSERT( op.init( "a", lt.release() ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + BSONObj indexKey = BSON( "" << "3" ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + op.matchesIndexKey( indexKey, indexSpec ) ); +} +*/ + +TEST(AndOfElemMatch, MatchesElement) { + BSONObj baseOperanda1 = BSON("a" << 1); + unique_ptr<ComparisonMatchExpression> eqa1(new EqualityMatchExpression()); + ASSERT(eqa1->init("a", baseOperanda1["a"]).isOK()); + + BSONObj baseOperandb1 = BSON("b" << 1); + unique_ptr<ComparisonMatchExpression> eqb1(new EqualityMatchExpression()); + ASSERT(eqb1->init("b", baseOperandb1["b"]).isOK()); + + unique_ptr<AndMatchExpression> and1(new AndMatchExpression()); + and1->add(eqa1.release()); + and1->add(eqb1.release()); + // and1 = { a : 1, b : 1 } + + unique_ptr<ElemMatchObjectMatchExpression> elemMatch1(new ElemMatchObjectMatchExpression()); + elemMatch1->init("x", and1.release()); + // elemMatch1 = { x : { $elemMatch : { a : 1, b : 1 } } } + + BSONObj baseOperanda2 = BSON("a" << 2); + unique_ptr<ComparisonMatchExpression> eqa2(new EqualityMatchExpression()); + ASSERT(eqa2->init("a", baseOperanda2["a"]).isOK()); + + BSONObj baseOperandb2 = BSON("b" << 2); + unique_ptr<ComparisonMatchExpression> eqb2(new EqualityMatchExpression()); + ASSERT(eqb2->init("b", baseOperandb2["b"]).isOK()); + + unique_ptr<AndMatchExpression> and2(new AndMatchExpression()); + and2->add(eqa2.release()); + and2->add(eqb2.release()); + // and2 = { a : 2, b : 2 } + + unique_ptr<ElemMatchObjectMatchExpression> elemMatch2(new ElemMatchObjectMatchExpression()); + elemMatch2->init("x", and2.release()); + // elemMatch2 = { x : { $elemMatch : { a : 2, b : 2 } } } + + unique_ptr<AndMatchExpression> andOfEM(new AndMatchExpression()); + andOfEM->add(elemMatch1.release()); + andOfEM->add(elemMatch2.release()); + + BSONObj nonArray = BSON("x" << 4); + ASSERT(!andOfEM->matchesSingleElement(nonArray["x"])); + BSONObj emptyArray = BSON("x" << BSONArray()); + ASSERT(!andOfEM->matchesSingleElement(emptyArray["x"])); + BSONObj nonObjArray = BSON("x" << BSON_ARRAY(4)); + ASSERT(!andOfEM->matchesSingleElement(nonObjArray["x"])); + BSONObj singleObjMatch = BSON("x" << BSON_ARRAY(BSON("a" << 1 << "b" << 1))); + ASSERT(!andOfEM->matchesSingleElement(singleObjMatch["x"])); + BSONObj otherObjMatch = BSON("x" << BSON_ARRAY(BSON("a" << 2 << "b" << 2))); + ASSERT(!andOfEM->matchesSingleElement(otherObjMatch["x"])); + BSONObj bothObjMatch = + BSON("x" << BSON_ARRAY(BSON("a" << 1 << "b" << 1) << BSON("a" << 2 << "b" << 2))); + ASSERT(andOfEM->matchesSingleElement(bothObjMatch["x"])); + BSONObj noObjMatch = + BSON("x" << BSON_ARRAY(BSON("a" << 1 << "b" << 2) << BSON("a" << 2 << "b" << 1))); + ASSERT(!andOfEM->matchesSingleElement(noObjMatch["x"])); +} + +TEST(AndOfElemMatch, Matches) { + BSONObj baseOperandgt1 = BSON("$gt" << 1); + unique_ptr<ComparisonMatchExpression> gt1(new GTMatchExpression()); + ASSERT(gt1->init("", baseOperandgt1["$gt"]).isOK()); + + BSONObj baseOperandlt1 = BSON("$lt" << 10); + unique_ptr<ComparisonMatchExpression> lt1(new LTMatchExpression()); + ASSERT(lt1->init("", baseOperandlt1["$lt"]).isOK()); + + unique_ptr<ElemMatchValueMatchExpression> elemMatch1(new ElemMatchValueMatchExpression()); + elemMatch1->init("x"); + elemMatch1->add(gt1.release()); + elemMatch1->add(lt1.release()); + // elemMatch1 = { x : { $elemMatch : { $gt : 1 , $lt : 10 } } } + + BSONObj baseOperandgt2 = BSON("$gt" << 101); + unique_ptr<ComparisonMatchExpression> gt2(new GTMatchExpression()); + ASSERT(gt2->init("", baseOperandgt2["$gt"]).isOK()); + + BSONObj baseOperandlt2 = BSON("$lt" << 110); + unique_ptr<ComparisonMatchExpression> lt2(new LTMatchExpression()); + ASSERT(lt2->init("", baseOperandlt2["$lt"]).isOK()); + + unique_ptr<ElemMatchValueMatchExpression> elemMatch2(new ElemMatchValueMatchExpression()); + elemMatch2->init("x"); + elemMatch2->add(gt2.release()); + elemMatch2->add(lt2.release()); + // elemMatch2 = { x : { $elemMatch : { $gt : 101 , $lt : 110 } } } + + unique_ptr<AndMatchExpression> andOfEM(new AndMatchExpression()); + andOfEM->add(elemMatch1.release()); + andOfEM->add(elemMatch2.release()); + + BSONObj nonArray = BSON("x" << 4); + ASSERT(!andOfEM->matchesBSON(nonArray, NULL)); + BSONObj emptyArray = BSON("x" << BSONArray()); + ASSERT(!andOfEM->matchesBSON(emptyArray, NULL)); + BSONObj nonNumberArray = BSON("x" << BSON_ARRAY("q")); + ASSERT(!andOfEM->matchesBSON(nonNumberArray, NULL)); + BSONObj singleMatch = BSON("x" << BSON_ARRAY(5)); + ASSERT(!andOfEM->matchesBSON(singleMatch, NULL)); + BSONObj otherMatch = BSON("x" << BSON_ARRAY(105)); + ASSERT(!andOfEM->matchesBSON(otherMatch, NULL)); + BSONObj bothMatch = BSON("x" << BSON_ARRAY(5 << 105)); + ASSERT(andOfEM->matchesBSON(bothMatch, NULL)); + BSONObj neitherMatch = BSON("x" << BSON_ARRAY(0 << 200)); + ASSERT(!andOfEM->matchesBSON(neitherMatch, NULL)); +} + +TEST(SizeMatchExpression, MatchesElement) { + BSONObj match = BSON("a" << BSON_ARRAY(5 << 6)); + BSONObj notMatch = BSON("a" << BSON_ARRAY(5)); + SizeMatchExpression size; + ASSERT(size.init("", 2).isOK()); + ASSERT(size.matchesSingleElement(match.firstElement())); + ASSERT(!size.matchesSingleElement(notMatch.firstElement())); +} + +TEST(SizeMatchExpression, MatchesNonArray) { + // Non arrays do not match. + BSONObj stringValue = BSON("a" + << "z"); + BSONObj numberValue = BSON("a" << 0); + BSONObj arrayValue = BSON("a" << BSONArray()); + SizeMatchExpression size; + ASSERT(size.init("", 0).isOK()); + ASSERT(!size.matchesSingleElement(stringValue.firstElement())); + ASSERT(!size.matchesSingleElement(numberValue.firstElement())); + ASSERT(size.matchesSingleElement(arrayValue.firstElement())); +} + +TEST(SizeMatchExpression, MatchesArray) { + SizeMatchExpression size; + ASSERT(size.init("a", 2).isOK()); + ASSERT(size.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5.5)), NULL)); + // Arrays are not unwound to look for matching subarrays. + ASSERT(!size.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5.5 << BSON_ARRAY(1 << 2))), NULL)); +} + +TEST(SizeMatchExpression, MatchesNestedArray) { + SizeMatchExpression size; + ASSERT(size.init("a.2", 2).isOK()); + // A numerically referenced nested array is matched. + ASSERT(size.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5.5 << BSON_ARRAY(1 << 2))), NULL)); +} + +TEST(SizeMatchExpression, ElemMatchKey) { + SizeMatchExpression size; + ASSERT(size.init("a.b", 3).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!size.matchesBSON(BSON("a" << 1), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(size.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(1 << 2 << 3))), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(size.matchesBSON(BSON("a" << BSON_ARRAY(2 << BSON("b" << BSON_ARRAY(1 << 2 << 3)))), + &details)); + ASSERT(details.hasElemMatchKey()); + 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 ); + SizeMatchExpression size; + ASSERT( size.init( "a", operand[ "$size" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + BSONObj indexKey = BSON( "" << 1 ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + size.matchesIndexKey( indexKey, indexSpec ) ); + } +*/ + +} // namespace mongo diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp index d61dba70343..1cca795b743 100644 --- a/src/mongo/db/matcher/expression_geo.cpp +++ b/src/mongo/db/matcher/expression_geo.cpp @@ -39,404 +39,406 @@ namespace mongo { - using mongoutils::str::equals; +using mongoutils::str::equals; + +// +// GeoExpression +// + +// Put simple constructors here for unique_ptr. +GeoExpression::GeoExpression() : field(""), predicate(INVALID) {} +GeoExpression::GeoExpression(const std::string& f) : field(f), predicate(INVALID) {} + +Status GeoExpression::parseQuery(const BSONObj& obj) { + BSONObjIterator outerIt(obj); + // "within" / "geoWithin" / "geoIntersects" + BSONElement queryElt = outerIt.next(); + if (outerIt.more()) { + return Status(ErrorCodes::BadValue, + str::stream() << "can't parse extra field: " << outerIt.next()); + } - // - // GeoExpression - // + BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(queryElt.getGtLtOp()); + if (BSONObj::opGEO_INTERSECTS == matchType) { + predicate = GeoExpression::INTERSECT; + } else if (BSONObj::opWITHIN == matchType) { + predicate = GeoExpression::WITHIN; + } else { + // eoo() or unknown query predicate. + return Status(ErrorCodes::BadValue, + str::stream() << "invalid geo query predicate: " << obj); + } - // Put simple constructors here for unique_ptr. - GeoExpression::GeoExpression() : field(""), predicate(INVALID) {} - GeoExpression::GeoExpression(const std::string& f) : field(f), predicate(INVALID) {} + // Parse geometry after predicates. + if (Object != queryElt.type()) + return Status(ErrorCodes::BadValue, "geometry must be an object"); + BSONObj geoObj = queryElt.Obj(); - Status GeoExpression::parseQuery(const BSONObj &obj) { - BSONObjIterator outerIt(obj); - // "within" / "geoWithin" / "geoIntersects" - BSONElement queryElt = outerIt.next(); - if (outerIt.more()) { - return Status(ErrorCodes::BadValue, - str::stream() << "can't parse extra field: " << outerIt.next()); - } + BSONObjIterator geoIt(geoObj); - BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(queryElt.getGtLtOp()); - if (BSONObj::opGEO_INTERSECTS == matchType) { - predicate = GeoExpression::INTERSECT; - } else if (BSONObj::opWITHIN == matchType) { - predicate = GeoExpression::WITHIN; + while (geoIt.more()) { + BSONElement elt = geoIt.next(); + if (str::equals(elt.fieldName(), "$uniqueDocs")) { + // Deprecated "$uniqueDocs" field + warning() << "deprecated $uniqueDocs option: " << obj.toString() << endl; } else { - // eoo() or unknown query predicate. - return Status(ErrorCodes::BadValue, - str::stream() << "invalid geo query predicate: " << obj); + // The element must be a geo specifier. "$box", "$center", "$geometry", etc. + geoContainer.reset(new GeometryContainer()); + Status status = geoContainer->parseFromQuery(elt); + if (!status.isOK()) + return status; } + } - // Parse geometry after predicates. - if (Object != queryElt.type()) return Status(ErrorCodes::BadValue, "geometry must be an object"); - BSONObj geoObj = queryElt.Obj(); - - BSONObjIterator geoIt(geoObj); - - while (geoIt.more()) { - BSONElement elt = geoIt.next(); - if (str::equals(elt.fieldName(), "$uniqueDocs")) { - // Deprecated "$uniqueDocs" field - warning() << "deprecated $uniqueDocs option: " << obj.toString() << endl; - } else { - // The element must be a geo specifier. "$box", "$center", "$geometry", etc. - geoContainer.reset(new GeometryContainer()); - Status status = geoContainer->parseFromQuery(elt); - if (!status.isOK()) return status; - } - } + if (geoContainer == NULL) { + return Status(ErrorCodes::BadValue, "geo query doesn't have any geometry"); + } - if (geoContainer == NULL) { - return Status(ErrorCodes::BadValue, "geo query doesn't have any geometry"); - } + return Status::OK(); +} - return Status::OK(); - } +Status GeoExpression::parseFrom(const BSONObj& obj) { + // Initialize geoContainer and parse BSON object + Status status = parseQuery(obj); + if (!status.isOK()) + return status; - Status GeoExpression::parseFrom(const BSONObj &obj) { - // Initialize geoContainer and parse BSON object - Status status = parseQuery(obj); - if (!status.isOK()) return status; - - // Why do we only deal with $within {polygon}? - // 1. Finding things within a point is silly and only valid - // for points and degenerate lines/polys. - // - // 2. Finding points within a line is easy but that's called intersect. - // Finding lines within a line is kind of tricky given what S2 gives us. - // Doing line-within-line is a valid yet unsupported feature, - // though I wonder if we want to preserve orientation for lines or - // allow (a,b),(c,d) to be within (c,d),(a,b). Anyway, punt on - // this for now. - if (GeoExpression::WITHIN == predicate && !geoContainer->supportsContains()) { - return Status(ErrorCodes::BadValue, - str::stream() << "$within not supported with provided geometry: " << obj); - } + // Why do we only deal with $within {polygon}? + // 1. Finding things within a point is silly and only valid + // for points and degenerate lines/polys. + // + // 2. Finding points within a line is easy but that's called intersect. + // Finding lines within a line is kind of tricky given what S2 gives us. + // Doing line-within-line is a valid yet unsupported feature, + // though I wonder if we want to preserve orientation for lines or + // allow (a,b),(c,d) to be within (c,d),(a,b). Anyway, punt on + // this for now. + if (GeoExpression::WITHIN == predicate && !geoContainer->supportsContains()) { + return Status(ErrorCodes::BadValue, + str::stream() << "$within not supported with provided geometry: " << obj); + } - // Big polygon with strict winding order is represented as an S2Loop in SPHERE CRS. - // So converting the query to SPHERE CRS makes things easier than projecting all the data - // into STRICT_SPHERE CRS. - if (STRICT_SPHERE == geoContainer->getNativeCRS()) { - if (!geoContainer->supportsProject(SPHERE)) { - return Status(ErrorCodes::BadValue, - "only polygon supported with strict winding order"); - } - geoContainer->projectInto(SPHERE); + // Big polygon with strict winding order is represented as an S2Loop in SPHERE CRS. + // So converting the query to SPHERE CRS makes things easier than projecting all the data + // into STRICT_SPHERE CRS. + if (STRICT_SPHERE == geoContainer->getNativeCRS()) { + if (!geoContainer->supportsProject(SPHERE)) { + return Status(ErrorCodes::BadValue, "only polygon supported with strict winding order"); } + geoContainer->projectInto(SPHERE); + } - // $geoIntersect queries are hardcoded to *always* be in SPHERE CRS - // TODO: This is probably bad semantics, should not do this - if (GeoExpression::INTERSECT == predicate) { - if (!geoContainer->supportsProject(SPHERE)) { - return Status(ErrorCodes::BadValue, - str::stream() - << "$geoIntersect not supported with provided geometry: " - << obj); - } - geoContainer->projectInto(SPHERE); + // $geoIntersect queries are hardcoded to *always* be in SPHERE CRS + // TODO: This is probably bad semantics, should not do this + if (GeoExpression::INTERSECT == predicate) { + if (!geoContainer->supportsProject(SPHERE)) { + return Status(ErrorCodes::BadValue, + str::stream() + << "$geoIntersect not supported with provided geometry: " << obj); } - - return Status::OK(); + geoContainer->projectInto(SPHERE); } - // - // GeoNearExpression - // + return Status::OK(); +} - GeoNearExpression::GeoNearExpression() - : minDistance(0), - maxDistance(std::numeric_limits<double>::max()), - isNearSphere(false), - unitsAreRadians(false), - isWrappingQuery(false) { } - - GeoNearExpression::GeoNearExpression(const std::string& f) - : field(f), - minDistance(0), - maxDistance(std::numeric_limits<double>::max()), - isNearSphere(false), - unitsAreRadians(false), - isWrappingQuery(false) { } - - bool GeoNearExpression::parseLegacyQuery(const BSONObj &obj) { - - bool hasGeometry = false; - - // First, try legacy near, e.g.: - // t.find({ loc : { $nearSphere: [0,0], $minDistance: 1, $maxDistance: 3 }}) - // t.find({ loc : { $nearSphere: [0,0] }}) - // t.find({ loc : { $near : [0, 0, 1] } }); - // t.find({ loc : { $near: { someGeoJSONPoint}}) - // t.find({ loc : { $geoNear: { someGeoJSONPoint}}) - BSONObjIterator it(obj); - while (it.more()) { - BSONElement e = it.next(); - if (equals(e.fieldName(), "$near") || equals(e.fieldName(), "$geoNear") - || equals(e.fieldName(), "$nearSphere")) { - if (!e.isABSONObj()) { return false; } - BSONObj embeddedObj = e.embeddedObject(); +// +// GeoNearExpression +// + +GeoNearExpression::GeoNearExpression() + : minDistance(0), + maxDistance(std::numeric_limits<double>::max()), + isNearSphere(false), + unitsAreRadians(false), + isWrappingQuery(false) {} + +GeoNearExpression::GeoNearExpression(const std::string& f) + : field(f), + minDistance(0), + maxDistance(std::numeric_limits<double>::max()), + isNearSphere(false), + unitsAreRadians(false), + isWrappingQuery(false) {} + +bool GeoNearExpression::parseLegacyQuery(const BSONObj& obj) { + bool hasGeometry = false; + + // First, try legacy near, e.g.: + // t.find({ loc : { $nearSphere: [0,0], $minDistance: 1, $maxDistance: 3 }}) + // t.find({ loc : { $nearSphere: [0,0] }}) + // t.find({ loc : { $near : [0, 0, 1] } }); + // t.find({ loc : { $near: { someGeoJSONPoint}}) + // t.find({ loc : { $geoNear: { someGeoJSONPoint}}) + BSONObjIterator it(obj); + while (it.more()) { + BSONElement e = it.next(); + if (equals(e.fieldName(), "$near") || equals(e.fieldName(), "$geoNear") || + equals(e.fieldName(), "$nearSphere")) { + if (!e.isABSONObj()) { + return false; + } + BSONObj embeddedObj = e.embeddedObject(); - if (GeoParser::parseQueryPoint(e, centroid.get()).isOK() - || GeoParser::parsePointWithMaxDistance(embeddedObj, centroid.get(), &maxDistance)) { - uassert(18522, "max distance must be non-negative", maxDistance >= 0.0); - hasGeometry = true; - isNearSphere = equals(e.fieldName(), "$nearSphere"); - } - } else if (equals(e.fieldName(), "$minDistance")) { - uassert(16893, "$minDistance must be a number", e.isNumber()); - minDistance = e.Number(); - uassert(16894, "$minDistance must be non-negative", minDistance >= 0.0); - } else if (equals(e.fieldName(), "$maxDistance")) { - uassert(16895, "$maxDistance must be a number", e.isNumber()); - maxDistance = e.Number(); - uassert(16896, "$maxDistance must be non-negative", maxDistance >= 0.0); - } else if (equals(e.fieldName(), "$uniqueDocs")) { - warning() << "ignoring deprecated option $uniqueDocs"; + if (GeoParser::parseQueryPoint(e, centroid.get()).isOK() || + GeoParser::parsePointWithMaxDistance(embeddedObj, centroid.get(), &maxDistance)) { + uassert(18522, "max distance must be non-negative", maxDistance >= 0.0); + hasGeometry = true; + isNearSphere = equals(e.fieldName(), "$nearSphere"); } + } else if (equals(e.fieldName(), "$minDistance")) { + uassert(16893, "$minDistance must be a number", e.isNumber()); + minDistance = e.Number(); + uassert(16894, "$minDistance must be non-negative", minDistance >= 0.0); + } else if (equals(e.fieldName(), "$maxDistance")) { + uassert(16895, "$maxDistance must be a number", e.isNumber()); + maxDistance = e.Number(); + uassert(16896, "$maxDistance must be non-negative", maxDistance >= 0.0); + } else if (equals(e.fieldName(), "$uniqueDocs")) { + warning() << "ignoring deprecated option $uniqueDocs"; } - - return hasGeometry; } - Status GeoNearExpression::parseNewQuery(const BSONObj &obj) { - bool hasGeometry = false; + return hasGeometry; +} - BSONObjIterator objIt(obj); - if (!objIt.more()) { - return Status(ErrorCodes::BadValue, "empty geo near query object"); - } - BSONElement e = objIt.next(); - // Just one arg. to $geoNear. - if (objIt.more()) { - return Status(ErrorCodes::BadValue, mongoutils::str::stream() << - "geo near accepts just one argument when querying for a GeoJSON " << - "point. Extra field found: " << objIt.next()); - } +Status GeoNearExpression::parseNewQuery(const BSONObj& obj) { + bool hasGeometry = false; - // Parse "new" near: - // t.find({"geo" : {"$near" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}}) - // t.find({"geo" : {"$geoNear" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}}) - if (!e.isABSONObj()) { - return Status(ErrorCodes::BadValue, "geo near query argument is not an object"); - } - BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp()); - if (BSONObj::opNEAR != matchType) { - return Status(ErrorCodes::BadValue, mongoutils::str::stream() << - "invalid geo near query operator: " << e.fieldName()); - } + BSONObjIterator objIt(obj); + if (!objIt.more()) { + return Status(ErrorCodes::BadValue, "empty geo near query object"); + } + BSONElement e = objIt.next(); + // Just one arg. to $geoNear. + if (objIt.more()) { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() + << "geo near accepts just one argument when querying for a GeoJSON " + << "point. Extra field found: " << objIt.next()); + } - // Iterate over the argument. - BSONObjIterator it(e.embeddedObject()); - while (it.more()) { - BSONElement e = it.next(); - if (equals(e.fieldName(), "$geometry")) { - if (e.isABSONObj()) { - BSONObj embeddedObj = e.embeddedObject(); - Status status = GeoParser::parseQueryPoint(e, centroid.get()); - if (!status.isOK()) { - return Status(ErrorCodes::BadValue, - str::stream() - << "invalid point in geo near query $geometry argument: " - << embeddedObj << " " << status.reason()); - } - uassert(16681, "$near requires geojson point, given " + embeddedObj.toString(), - (SPHERE == centroid->crs)); - hasGeometry = true; + // Parse "new" near: + // t.find({"geo" : {"$near" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}}) + // t.find({"geo" : {"$geoNear" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}}) + if (!e.isABSONObj()) { + return Status(ErrorCodes::BadValue, "geo near query argument is not an object"); + } + BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp()); + if (BSONObj::opNEAR != matchType) { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() + << "invalid geo near query operator: " << e.fieldName()); + } + + // Iterate over the argument. + BSONObjIterator it(e.embeddedObject()); + while (it.more()) { + BSONElement e = it.next(); + if (equals(e.fieldName(), "$geometry")) { + if (e.isABSONObj()) { + BSONObj embeddedObj = e.embeddedObject(); + Status status = GeoParser::parseQueryPoint(e, centroid.get()); + if (!status.isOK()) { + return Status(ErrorCodes::BadValue, + str::stream() + << "invalid point in geo near query $geometry argument: " + << embeddedObj << " " << status.reason()); } - } else if (equals(e.fieldName(), "$minDistance")) { - uassert(16897, "$minDistance must be a number", e.isNumber()); - minDistance = e.Number(); - uassert(16898, "$minDistance must be non-negative", minDistance >= 0.0); - } else if (equals(e.fieldName(), "$maxDistance")) { - uassert(16899, "$maxDistance must be a number", e.isNumber()); - maxDistance = e.Number(); - uassert(16900, "$maxDistance must be non-negative", maxDistance >= 0.0); + uassert(16681, + "$near requires geojson point, given " + embeddedObj.toString(), + (SPHERE == centroid->crs)); + hasGeometry = true; } + } else if (equals(e.fieldName(), "$minDistance")) { + uassert(16897, "$minDistance must be a number", e.isNumber()); + minDistance = e.Number(); + uassert(16898, "$minDistance must be non-negative", minDistance >= 0.0); + } else if (equals(e.fieldName(), "$maxDistance")) { + uassert(16899, "$maxDistance must be a number", e.isNumber()); + maxDistance = e.Number(); + uassert(16900, "$maxDistance must be non-negative", maxDistance >= 0.0); } - - if (!hasGeometry) { - return Status(ErrorCodes::BadValue, "$geometry is required for geo near query"); - } - - return Status::OK(); } + if (!hasGeometry) { + return Status(ErrorCodes::BadValue, "$geometry is required for geo near query"); + } - Status GeoNearExpression::parseFrom(const BSONObj &obj) { - - Status status = Status::OK(); - centroid.reset(new PointWithCRS()); - - if (!parseLegacyQuery(obj)) { - // Clear out any half-baked data. - minDistance = 0; - isNearSphere = false; - maxDistance = std::numeric_limits<double>::max(); - // ...and try parsing new format. - status = parseNewQuery(obj); - } - - if (!status.isOK()) - return status; - - // Fixup the near query for anonoyances caused by $nearSphere - if (isNearSphere) { + return Status::OK(); +} - // The user-provided point can be flat for a spherical query - needs to be projectable - uassert(17444, - "Legacy point is out of bounds for spherical query", - ShapeProjection::supportsProject(*centroid, SPHERE)); - unitsAreRadians = SPHERE != centroid->crs; - // GeoJSON points imply wrapping queries - isWrappingQuery = SPHERE == centroid->crs; +Status GeoNearExpression::parseFrom(const BSONObj& obj) { + Status status = Status::OK(); + centroid.reset(new PointWithCRS()); - // Project the point to a spherical CRS now that we've got the settings we need - // We need to manually project here since we aren't using GeometryContainer - ShapeProjection::projectInto(centroid.get(), SPHERE); - } - else { - unitsAreRadians = false; - isWrappingQuery = SPHERE == centroid->crs; - } + if (!parseLegacyQuery(obj)) { + // Clear out any half-baked data. + minDistance = 0; + isNearSphere = false; + maxDistance = std::numeric_limits<double>::max(); + // ...and try parsing new format. + status = parseNewQuery(obj); + } + if (!status.isOK()) return status; + + // Fixup the near query for anonoyances caused by $nearSphere + if (isNearSphere) { + // The user-provided point can be flat for a spherical query - needs to be projectable + uassert(17444, + "Legacy point is out of bounds for spherical query", + ShapeProjection::supportsProject(*centroid, SPHERE)); + + unitsAreRadians = SPHERE != centroid->crs; + // GeoJSON points imply wrapping queries + isWrappingQuery = SPHERE == centroid->crs; + + // Project the point to a spherical CRS now that we've got the settings we need + // We need to manually project here since we aren't using GeometryContainer + ShapeProjection::projectInto(centroid.get(), SPHERE); + } else { + unitsAreRadians = false; + isWrappingQuery = SPHERE == centroid->crs; } - // - // GeoMatchExpression and GeoNearMatchExpression - // + return status; +} - // - // Geo queries we don't need an index to answer: geoWithin and geoIntersects - // +// +// GeoMatchExpression and GeoNearMatchExpression +// - Status GeoMatchExpression::init( StringData path, const GeoExpression* query, - const BSONObj& rawObj ) { - _query.reset(query); - _rawObj = rawObj; - return initPath( path ); - } +// +// Geo queries we don't need an index to answer: geoWithin and geoIntersects +// - bool GeoMatchExpression::matchesSingleElement( const BSONElement& e ) const { - if ( !e.isABSONObj()) - return false; +Status GeoMatchExpression::init(StringData path, + const GeoExpression* query, + const BSONObj& rawObj) { + _query.reset(query); + _rawObj = rawObj; + return initPath(path); +} - GeometryContainer geometry; - if ( !geometry.parseFromStorage( e ).isOK() ) - return false; +bool GeoMatchExpression::matchesSingleElement(const BSONElement& e) const { + if (!e.isABSONObj()) + return false; - // Never match big polygon - if (geometry.getNativeCRS() == STRICT_SPHERE) - return false; + GeometryContainer geometry; + if (!geometry.parseFromStorage(e).isOK()) + return false; - // Project this geometry into the CRS of the query - if (!geometry.supportsProject(_query->getGeometry().getNativeCRS())) - return false; + // Never match big polygon + if (geometry.getNativeCRS() == STRICT_SPHERE) + return false; - geometry.projectInto(_query->getGeometry().getNativeCRS()); + // Project this geometry into the CRS of the query + if (!geometry.supportsProject(_query->getGeometry().getNativeCRS())) + return false; - if (GeoExpression::WITHIN == _query->getPred()) { - return _query->getGeometry().contains(geometry); - } - else { - verify(GeoExpression::INTERSECT == _query->getPred()); - return _query->getGeometry().intersects(geometry); - } - } + geometry.projectInto(_query->getGeometry().getNativeCRS()); - void GeoMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << "GEO raw = " << _rawObj.toString(); - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); - } - debug << "\n"; + if (GeoExpression::WITHIN == _query->getPred()) { + return _query->getGeometry().contains(geometry); + } else { + verify(GeoExpression::INTERSECT == _query->getPred()); + return _query->getGeometry().intersects(geometry); } +} - void GeoMatchExpression::toBSON(BSONObjBuilder* out) const { - out->appendElements(_rawObj); +void GeoMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "GEO raw = " << _rawObj.toString(); + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } + debug << "\n"; +} - bool GeoMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; +void GeoMatchExpression::toBSON(BSONObjBuilder* out) const { + out->appendElements(_rawObj); +} - const GeoMatchExpression* realOther = static_cast<const GeoMatchExpression*>( other ); +bool GeoMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) + return false; - if ( path() != realOther->path() ) - return false; + const GeoMatchExpression* realOther = static_cast<const GeoMatchExpression*>(other); - return _rawObj == realOther->_rawObj; - } + if (path() != realOther->path()) + return false; - LeafMatchExpression* GeoMatchExpression::shallowClone() const { - GeoMatchExpression* next = new GeoMatchExpression(); - next->init( path(), NULL, _rawObj); - next->_query = _query; - if (getTag()) { - next->setTag(getTag()->clone()); - } - return next; + return _rawObj == realOther->_rawObj; +} + +LeafMatchExpression* GeoMatchExpression::shallowClone() const { + GeoMatchExpression* next = new GeoMatchExpression(); + next->init(path(), NULL, _rawObj); + next->_query = _query; + if (getTag()) { + next->setTag(getTag()->clone()); } + return next; +} - // - // Parse-only geo expressions: geoNear (formerly known as near). - // +// +// Parse-only geo expressions: geoNear (formerly known as near). +// - Status GeoNearMatchExpression::init( StringData path, const GeoNearExpression* query, - const BSONObj& rawObj ) { - _query.reset(query); - _rawObj = rawObj; - return initPath( path ); - } +Status GeoNearMatchExpression::init(StringData path, + const GeoNearExpression* query, + const BSONObj& rawObj) { + _query.reset(query); + _rawObj = rawObj; + return initPath(path); +} - bool GeoNearMatchExpression::matchesSingleElement( const BSONElement& e ) const { - // See ops/update.cpp. - // This node is removed by the query planner. It's only ever called if we're getting an - // elemMatchKey. - return true; - } +bool GeoNearMatchExpression::matchesSingleElement(const BSONElement& e) const { + // See ops/update.cpp. + // This node is removed by the query planner. It's only ever called if we're getting an + // elemMatchKey. + return true; +} - void GeoNearMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << "GEONEAR " << _query->toString(); - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); - } - debug << "\n"; +void GeoNearMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "GEONEAR " << _query->toString(); + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } + debug << "\n"; +} - void GeoNearMatchExpression::toBSON(BSONObjBuilder* out) const { - out->appendElements(_rawObj); - } +void GeoNearMatchExpression::toBSON(BSONObjBuilder* out) const { + out->appendElements(_rawObj); +} - bool GeoNearMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; +bool GeoNearMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) + return false; - const GeoNearMatchExpression* realOther = static_cast<const GeoNearMatchExpression*>(other); + const GeoNearMatchExpression* realOther = static_cast<const GeoNearMatchExpression*>(other); - if ( path() != realOther->path() ) - return false; + if (path() != realOther->path()) + return false; - return _rawObj == realOther->_rawObj; - } + return _rawObj == realOther->_rawObj; +} - LeafMatchExpression* GeoNearMatchExpression::shallowClone() const { - GeoNearMatchExpression* next = new GeoNearMatchExpression(); - next->init( path(), NULL, _rawObj ); - next->_query = _query; - if (getTag()) { - next->setTag(getTag()->clone()); - } - return next; +LeafMatchExpression* GeoNearMatchExpression::shallowClone() const { + GeoNearMatchExpression* next = new GeoNearMatchExpression(); + next->init(path(), NULL, _rawObj); + next->_query = _query; + if (getTag()) { + next->setTag(getTag()->clone()); } - + return next; +} } diff --git a/src/mongo/db/matcher/expression_geo.h b/src/mongo/db/matcher/expression_geo.h index 01fa07bf08b..197bb794973 100644 --- a/src/mongo/db/matcher/expression_geo.h +++ b/src/mongo/db/matcher/expression_geo.h @@ -38,138 +38,149 @@ namespace mongo { - struct PointWithCRS; - class GeometryContainer; +struct PointWithCRS; +class GeometryContainer; - // This represents either a $within or a $geoIntersects. - class GeoExpression { - MONGO_DISALLOW_COPYING(GeoExpression); +// This represents either a $within or a $geoIntersects. +class GeoExpression { + MONGO_DISALLOW_COPYING(GeoExpression); + +public: + GeoExpression(); + GeoExpression(const std::string& f); + + enum Predicate { WITHIN, INTERSECT, INVALID }; + + // parseFrom() must be called before getGeometry() to ensure initialization of geoContainer + Status parseFrom(const BSONObj& obj); + + std::string getField() const { + return field; + } + Predicate getPred() const { + return predicate; + } + const GeometryContainer& getGeometry() const { + return *geoContainer; + } - public: - GeoExpression(); - GeoExpression(const std::string& f); +private: + // Parse geospatial query + // e.g. + // { "$intersect" : { "$geometry" : { "type" : "Point", "coordinates": [ 40, 5 ] } } } + Status parseQuery(const BSONObj& obj); - enum Predicate { - WITHIN, - INTERSECT, - INVALID - }; + // Name of the field in the query. + std::string field; + std::unique_ptr<GeometryContainer> geoContainer; + Predicate predicate; +}; - // parseFrom() must be called before getGeometry() to ensure initialization of geoContainer - Status parseFrom(const BSONObj &obj); +class GeoMatchExpression : public LeafMatchExpression { +public: + GeoMatchExpression() : LeafMatchExpression(GEO) {} + virtual ~GeoMatchExpression() {} - std::string getField() const { return field; } - Predicate getPred() const { return predicate; } - const GeometryContainer& getGeometry() const { return *geoContainer; } + /** + * Takes ownership of the passed-in GeoExpression. + */ + Status init(StringData path, const GeoExpression* query, const BSONObj& rawObj); + + virtual bool matchesSingleElement(const BSONElement& e) const; + + virtual void debugString(StringBuilder& debug, int level = 0) const; + + virtual void toBSON(BSONObjBuilder* out) const; + + virtual bool equivalent(const MatchExpression* other) const; + + virtual LeafMatchExpression* shallowClone() const; + + const GeoExpression& getGeoExpression() const { + return *_query; + } + const BSONObj getRawObj() const { + return _rawObj; + } + +private: + BSONObj _rawObj; + // Share ownership of our query with all of our clones + std::shared_ptr<const GeoExpression> _query; +}; + + +// TODO: Make a struct, turn parse stuff into something like +// static Status parseNearQuery(const BSONObj& obj, NearQuery** out); +class GeoNearExpression { + MONGO_DISALLOW_COPYING(GeoNearExpression); + +public: + GeoNearExpression(); + GeoNearExpression(const std::string& f); + + Status parseFrom(const BSONObj& obj); + + // The name of the field that contains the geometry. + std::string field; + + // The starting point of the near search. Use forward declaration of geometries. + std::unique_ptr<PointWithCRS> centroid; + + // Min and max distance from centroid that we're willing to search. + // Distance is in units of the geometry's CRS, except SPHERE and isNearSphere => radians + double minDistance; + double maxDistance; + + // Is this a $nearSphere query + bool isNearSphere; + // $nearSphere with a legacy point implies units are radians + bool unitsAreRadians; + // $near with a non-legacy point implies a wrapping query, otherwise the query doesn't wrap + bool isWrappingQuery; + + std::string toString() const { + std::stringstream ss; + ss << " field=" << field; + ss << " maxdist=" << maxDistance; + ss << " isNearSphere=" << isNearSphere; + return ss.str(); + } + +private: + bool parseLegacyQuery(const BSONObj& obj); + Status parseNewQuery(const BSONObj& obj); +}; + +class GeoNearMatchExpression : public LeafMatchExpression { +public: + GeoNearMatchExpression() : LeafMatchExpression(GEO_NEAR) {} + virtual ~GeoNearMatchExpression() {} + + Status init(StringData path, const GeoNearExpression* query, const BSONObj& rawObj); + + // This shouldn't be called and as such will crash. GeoNear always requires an index. + virtual bool matchesSingleElement(const BSONElement& e) const; - private: - // Parse geospatial query - // e.g. - // { "$intersect" : { "$geometry" : { "type" : "Point", "coordinates": [ 40, 5 ] } } } - Status parseQuery(const BSONObj &obj); + virtual void debugString(StringBuilder& debug, int level = 0) const; - // Name of the field in the query. - std::string field; - std::unique_ptr<GeometryContainer> geoContainer; - Predicate predicate; - }; + virtual void toBSON(BSONObjBuilder* out) const; - class GeoMatchExpression : public LeafMatchExpression { - public: - GeoMatchExpression() : LeafMatchExpression( GEO ){} - virtual ~GeoMatchExpression(){} + virtual bool equivalent(const MatchExpression* other) const; - /** - * Takes ownership of the passed-in GeoExpression. - */ - Status init( StringData path, const GeoExpression* query, const BSONObj& rawObj ); + virtual LeafMatchExpression* shallowClone() const; - virtual bool matchesSingleElement( const BSONElement& e ) const; + const GeoNearExpression& getData() const { + return *_query; + } + const BSONObj getRawObj() const { + return _rawObj; + } - virtual void debugString( StringBuilder& debug, int level = 0 ) const; - - virtual void toBSON(BSONObjBuilder* out) const; - - virtual bool equivalent( const MatchExpression* other ) const; - - virtual LeafMatchExpression* shallowClone() const; - - const GeoExpression& getGeoExpression() const { return *_query; } - const BSONObj getRawObj() const { return _rawObj; } - - private: - BSONObj _rawObj; - // Share ownership of our query with all of our clones - std::shared_ptr<const GeoExpression> _query; - }; - - - // TODO: Make a struct, turn parse stuff into something like - // static Status parseNearQuery(const BSONObj& obj, NearQuery** out); - class GeoNearExpression { - MONGO_DISALLOW_COPYING(GeoNearExpression); - - public: - GeoNearExpression(); - GeoNearExpression(const std::string& f); - - Status parseFrom(const BSONObj &obj); - - // The name of the field that contains the geometry. - std::string field; - - // The starting point of the near search. Use forward declaration of geometries. - std::unique_ptr<PointWithCRS> centroid; - - // Min and max distance from centroid that we're willing to search. - // Distance is in units of the geometry's CRS, except SPHERE and isNearSphere => radians - double minDistance; - double maxDistance; - - // Is this a $nearSphere query - bool isNearSphere; - // $nearSphere with a legacy point implies units are radians - bool unitsAreRadians; - // $near with a non-legacy point implies a wrapping query, otherwise the query doesn't wrap - bool isWrappingQuery; - - std::string toString() const { - std::stringstream ss; - ss << " field=" << field; - ss << " maxdist=" << maxDistance; - ss << " isNearSphere=" << isNearSphere; - return ss.str(); - } - - private: - bool parseLegacyQuery(const BSONObj &obj); - Status parseNewQuery(const BSONObj &obj); - }; - - class GeoNearMatchExpression : public LeafMatchExpression { - public: - GeoNearMatchExpression() : LeafMatchExpression( GEO_NEAR ){} - virtual ~GeoNearMatchExpression(){} - - Status init( StringData path, const GeoNearExpression* query, const BSONObj& rawObj ); - - // This shouldn't be called and as such will crash. GeoNear always requires an index. - virtual bool matchesSingleElement( const BSONElement& e ) const; - - virtual void debugString( StringBuilder& debug, int level = 0 ) const; - - virtual void toBSON(BSONObjBuilder* out) const; - - virtual bool equivalent( const MatchExpression* other ) const; - - virtual LeafMatchExpression* shallowClone() const; - - const GeoNearExpression& getData() const { return *_query; } - const BSONObj getRawObj() const { return _rawObj; } - private: - BSONObj _rawObj; - // Share ownership of our query with all of our clones - std::shared_ptr<const GeoNearExpression> _query; - }; +private: + BSONObj _rawObj; + // Share ownership of our query with all of our clones + std::shared_ptr<const GeoNearExpression> _query; +}; } // namespace mongo diff --git a/src/mongo/db/matcher/expression_geo_test.cpp b/src/mongo/db/matcher/expression_geo_test.cpp index 67d51f5580c..0bc96f33e7f 100644 --- a/src/mongo/db/matcher/expression_geo_test.cpp +++ b/src/mongo/db/matcher/expression_geo_test.cpp @@ -41,137 +41,148 @@ namespace mongo { - TEST( ExpressionGeoTest, Geo1 ) { - BSONObj query = fromjson("{loc:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); +TEST(ExpressionGeoTest, Geo1) { + BSONObj query = fromjson("{loc:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); - std::unique_ptr<GeoExpression> gq(new GeoExpression); - ASSERT_OK( gq->parseFrom( query["loc"].Obj() ) ); + std::unique_ptr<GeoExpression> gq(new GeoExpression); + ASSERT_OK(gq->parseFrom(query["loc"].Obj())); - GeoMatchExpression ge; - ASSERT( ge.init("a", gq.release(), query ).isOK() ); + GeoMatchExpression ge; + ASSERT(ge.init("a", gq.release(), query).isOK()); - ASSERT(!ge.matchesBSON(fromjson("{a: [3,4]}"))); - ASSERT(ge.matchesBSON(fromjson("{a: [4,4]}"))); - ASSERT(ge.matchesBSON(fromjson("{a: [5,5]}"))); - ASSERT(ge.matchesBSON(fromjson("{a: [5,5.1]}"))); - ASSERT(ge.matchesBSON(fromjson("{a: {x: 5, y:5.1}}"))); - - } + ASSERT(!ge.matchesBSON(fromjson("{a: [3,4]}"))); + ASSERT(ge.matchesBSON(fromjson("{a: [4,4]}"))); + ASSERT(ge.matchesBSON(fromjson("{a: [5,5]}"))); + ASSERT(ge.matchesBSON(fromjson("{a: [5,5.1]}"))); + ASSERT(ge.matchesBSON(fromjson("{a: {x: 5, y:5.1}}"))); +} - TEST(ExpressionGeoTest, GeoNear1) { - BSONObj query = fromjson("{loc:{$near:{$maxDistance:100, " - "$geometry:{type:\"Point\", coordinates:[0,0]}}}}"); - std::unique_ptr<GeoNearExpression> nq(new GeoNearExpression); - ASSERT_OK(nq->parseFrom(query["loc"].Obj())); +TEST(ExpressionGeoTest, GeoNear1) { + BSONObj query = fromjson( + "{loc:{$near:{$maxDistance:100, " + "$geometry:{type:\"Point\", coordinates:[0,0]}}}}"); + std::unique_ptr<GeoNearExpression> nq(new GeoNearExpression); + ASSERT_OK(nq->parseFrom(query["loc"].Obj())); - GeoNearMatchExpression gne; - ASSERT(gne.init("a", nq.release(), query).isOK()); + GeoNearMatchExpression gne; + ASSERT(gne.init("a", nq.release(), query).isOK()); - // We can't match the data but we can make sure it was parsed OK. - ASSERT_EQUALS(gne.getData().centroid->crs, SPHERE); - ASSERT_EQUALS(gne.getData().minDistance, 0); - ASSERT_EQUALS(gne.getData().maxDistance, 100); - } + // We can't match the data but we can make sure it was parsed OK. + ASSERT_EQUALS(gne.getData().centroid->crs, SPHERE); + ASSERT_EQUALS(gne.getData().minDistance, 0); + ASSERT_EQUALS(gne.getData().maxDistance, 100); +} - std::unique_ptr<GeoMatchExpression> makeGeoMatchExpression(const BSONObj& locQuery) { - std::unique_ptr<GeoExpression> gq(new GeoExpression); - ASSERT_OK(gq->parseFrom(locQuery)); +std::unique_ptr<GeoMatchExpression> makeGeoMatchExpression(const BSONObj& locQuery) { + std::unique_ptr<GeoExpression> gq(new GeoExpression); + ASSERT_OK(gq->parseFrom(locQuery)); - std::unique_ptr<GeoMatchExpression> ge = stdx::make_unique<GeoMatchExpression>(); - ASSERT_OK(ge->init("a", gq.release(), locQuery)); + std::unique_ptr<GeoMatchExpression> ge = stdx::make_unique<GeoMatchExpression>(); + ASSERT_OK(ge->init("a", gq.release(), locQuery)); - return ge; - } + return ge; +} - std::unique_ptr<GeoNearMatchExpression> makeGeoNearMatchExpression(const BSONObj& locQuery) { - std::unique_ptr<GeoNearExpression> nq(new GeoNearExpression); - ASSERT_OK(nq->parseFrom(locQuery)); +std::unique_ptr<GeoNearMatchExpression> makeGeoNearMatchExpression(const BSONObj& locQuery) { + std::unique_ptr<GeoNearExpression> nq(new GeoNearExpression); + ASSERT_OK(nq->parseFrom(locQuery)); - std::unique_ptr<GeoNearMatchExpression> gne = stdx::make_unique<GeoNearMatchExpression>(); - ASSERT_OK(gne->init("a", nq.release(), locQuery)); + std::unique_ptr<GeoNearMatchExpression> gne = stdx::make_unique<GeoNearMatchExpression>(); + ASSERT_OK(gne->init("a", nq.release(), locQuery)); - return gne; - } + return gne; +} - /** - * A bunch of cases in which a geo expression is equivalent() to both itself or to another - * expression. - */ - TEST(ExpressionGeoTest, GeoEquivalent) { - { - BSONObj query = fromjson("{$within: {$box: [{x: 4, y: 4}, [6, 6]]}}"); - std::unique_ptr<GeoMatchExpression> ge(makeGeoMatchExpression(query)); - ASSERT(ge->equivalent(ge.get())); - } - { - BSONObj query = fromjson("{$within: {$geometry: {type: 'Polygon'," - "coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]]}}}"); - std::unique_ptr<GeoMatchExpression> ge(makeGeoMatchExpression(query)); - ASSERT(ge->equivalent(ge.get())); - } - { - BSONObj query1 = fromjson("{$within: {$geometry: {type: 'Polygon'," - "coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]]}}}"), - query2 = fromjson("{$within: {$geometry: {type: 'Polygon'," - "coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]]}}}"); - std::unique_ptr<GeoMatchExpression> ge1(makeGeoMatchExpression(query1)), - ge2(makeGeoMatchExpression(query2)); - ASSERT(ge1->equivalent(ge2.get())); - } +/** + * A bunch of cases in which a geo expression is equivalent() to both itself or to another + * expression. + */ +TEST(ExpressionGeoTest, GeoEquivalent) { + { + BSONObj query = fromjson("{$within: {$box: [{x: 4, y: 4}, [6, 6]]}}"); + std::unique_ptr<GeoMatchExpression> ge(makeGeoMatchExpression(query)); + ASSERT(ge->equivalent(ge.get())); } - - /** - * A bunch of cases in which a *geoNear* expression is equivalent both to itself or to - * another expression. - */ - TEST(ExpressionGeoTest, GeoNearEquivalent) { - { - BSONObj query = fromjson("{$near: {$maxDistance: 100, " - "$geometry: {type: 'Point', coordinates: [0, 0]}}}"); - std::unique_ptr<GeoNearMatchExpression> gne(makeGeoNearMatchExpression(query)); - ASSERT(gne->equivalent(gne.get())); - } - { - BSONObj query = fromjson("{$near: {$minDistance: 10, $maxDistance: 100," - "$geometry: {type: 'Point', coordinates: [0, 0]}}}"); - std::unique_ptr<GeoNearMatchExpression> gne(makeGeoNearMatchExpression(query)); - ASSERT(gne->equivalent(gne.get())); - } - { - BSONObj query1 = fromjson("{$near: {$maxDistance: 100, " - "$geometry: {type: 'Point', coordinates: [1, 0]}}}"), - query2 = fromjson("{$near: {$maxDistance: 100, " - "$geometry: {type: 'Point', coordinates: [1, 0]}}}"); - std::unique_ptr<GeoNearMatchExpression> gne1(makeGeoNearMatchExpression(query1)), - gne2(makeGeoNearMatchExpression(query2)); - ASSERT(gne1->equivalent(gne2.get())); - } + { + BSONObj query = fromjson( + "{$within: {$geometry: {type: 'Polygon'," + "coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]]}}}"); + std::unique_ptr<GeoMatchExpression> ge(makeGeoMatchExpression(query)); + ASSERT(ge->equivalent(ge.get())); } - - /** - * A geo expression being not equivalent to another expression. - */ - TEST(ExpressionGeoTest, GeoNotEquivalent) { - BSONObj query1 = fromjson("{$within: {$geometry: {type: 'Polygon'," - "coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]]}}}"), - query2 = fromjson("{$within: {$geometry: {type: 'Polygon'," - "coordinates: [[[0, 0], [3, 6], [6, 2], [0, 0]]]}}}"); + { + BSONObj query1 = fromjson( + "{$within: {$geometry: {type: 'Polygon'," + "coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]]}}}"), + query2 = fromjson( + "{$within: {$geometry: {type: 'Polygon'," + "coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]]}}}"); std::unique_ptr<GeoMatchExpression> ge1(makeGeoMatchExpression(query1)), - ge2(makeGeoMatchExpression(query2)); - ASSERT(!ge1->equivalent(ge2.get())); + ge2(makeGeoMatchExpression(query2)); + ASSERT(ge1->equivalent(ge2.get())); } +} - /** - * A *geoNear* expression being not equivalent to another expression. - */ - TEST(ExpressionGeoTest, GeoNearNotEquivalent) { - BSONObj query1 = fromjson("{$near: {$maxDistance: 100, " - "$geometry: {type: 'Point', coordinates: [0, 0]}}}"), - query2 = fromjson("{$near: {$maxDistance: 100, " - "$geometry: {type: 'Point', coordinates: [1, 0]}}}"); +/** + * A bunch of cases in which a *geoNear* expression is equivalent both to itself or to + * another expression. + */ +TEST(ExpressionGeoTest, GeoNearEquivalent) { + { + BSONObj query = fromjson( + "{$near: {$maxDistance: 100, " + "$geometry: {type: 'Point', coordinates: [0, 0]}}}"); + std::unique_ptr<GeoNearMatchExpression> gne(makeGeoNearMatchExpression(query)); + ASSERT(gne->equivalent(gne.get())); + } + { + BSONObj query = fromjson( + "{$near: {$minDistance: 10, $maxDistance: 100," + "$geometry: {type: 'Point', coordinates: [0, 0]}}}"); + std::unique_ptr<GeoNearMatchExpression> gne(makeGeoNearMatchExpression(query)); + ASSERT(gne->equivalent(gne.get())); + } + { + BSONObj query1 = fromjson( + "{$near: {$maxDistance: 100, " + "$geometry: {type: 'Point', coordinates: [1, 0]}}}"), + query2 = fromjson( + "{$near: {$maxDistance: 100, " + "$geometry: {type: 'Point', coordinates: [1, 0]}}}"); std::unique_ptr<GeoNearMatchExpression> gne1(makeGeoNearMatchExpression(query1)), - gne2(makeGeoNearMatchExpression(query2)); - ASSERT(!gne1->equivalent(gne2.get())); + gne2(makeGeoNearMatchExpression(query2)); + ASSERT(gne1->equivalent(gne2.get())); } } + +/** + * A geo expression being not equivalent to another expression. + */ +TEST(ExpressionGeoTest, GeoNotEquivalent) { + BSONObj query1 = fromjson( + "{$within: {$geometry: {type: 'Polygon'," + "coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]]}}}"), + query2 = fromjson( + "{$within: {$geometry: {type: 'Polygon'," + "coordinates: [[[0, 0], [3, 6], [6, 2], [0, 0]]]}}}"); + std::unique_ptr<GeoMatchExpression> ge1(makeGeoMatchExpression(query1)), + ge2(makeGeoMatchExpression(query2)); + ASSERT(!ge1->equivalent(ge2.get())); +} + +/** + * A *geoNear* expression being not equivalent to another expression. + */ +TEST(ExpressionGeoTest, GeoNearNotEquivalent) { + BSONObj query1 = fromjson( + "{$near: {$maxDistance: 100, " + "$geometry: {type: 'Point', coordinates: [0, 0]}}}"), + query2 = fromjson( + "{$near: {$maxDistance: 100, " + "$geometry: {type: 'Point', coordinates: [1, 0]}}}"); + std::unique_ptr<GeoNearMatchExpression> gne1(makeGeoNearMatchExpression(query1)), + gne2(makeGeoNearMatchExpression(query2)); + ASSERT(!gne1->equivalent(gne2.get())); +} +} diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp index f0a91cffbbf..619fc64133d 100644 --- a/src/mongo/db/matcher/expression_leaf.cpp +++ b/src/mongo/db/matcher/expression_leaf.cpp @@ -41,52 +41,50 @@ namespace mongo { - Status LeafMatchExpression::initPath( StringData path ) { - _path = path; - return _elementPath.init( _path ); - } +Status LeafMatchExpression::initPath(StringData path) { + _path = path; + return _elementPath.init(_path); +} - bool LeafMatchExpression::matches( const MatchableDocument* doc, MatchDetails* details ) const { - MatchableDocument::IteratorHolder cursor( doc, &_elementPath ); - while ( cursor->more() ) { - ElementIterator::Context e = cursor->next(); - if ( !matchesSingleElement( e.element() ) ) - continue; - if ( details && details->needRecord() && !e.arrayOffset().eoo() ) { - details->setElemMatchKey( e.arrayOffset().fieldName() ); - } - return true; +bool LeafMatchExpression::matches(const MatchableDocument* doc, MatchDetails* details) const { + MatchableDocument::IteratorHolder cursor(doc, &_elementPath); + while (cursor->more()) { + ElementIterator::Context e = cursor->next(); + if (!matchesSingleElement(e.element())) + continue; + if (details && details->needRecord() && !e.arrayOffset().eoo()) { + details->setElemMatchKey(e.arrayOffset().fieldName()); } - return false; + return true; } + return false; +} - // ------------- +// ------------- - bool ComparisonMatchExpression::equivalent( const MatchExpression* other ) const { - if ( other->matchType() != matchType() ) - return false; - const ComparisonMatchExpression* realOther = - static_cast<const ComparisonMatchExpression*>( other ); +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 ); - } + return path() == realOther->path() && _rhs.valuesEqual(realOther->_rhs); +} - Status ComparisonMatchExpression::init( StringData path, const BSONElement& rhs ) { - _rhs = rhs; +Status ComparisonMatchExpression::init(StringData path, const BSONElement& rhs) { + _rhs = rhs; - if ( rhs.eoo() ) { - return Status( ErrorCodes::BadValue, "need a real operand" ); - } + if (rhs.eoo()) { + return Status(ErrorCodes::BadValue, "need a real operand"); + } - if ( rhs.type() == Undefined ) { - return Status( ErrorCodes::BadValue, "cannot compare to undefined" ); - } + if (rhs.type() == Undefined) { + return Status(ErrorCodes::BadValue, "cannot compare to undefined"); + } - switch ( matchType() ) { + switch (matchType()) { case LT: case LTE: case EQ: @@ -94,36 +92,36 @@ namespace mongo { case GTE: break; default: - return Status( ErrorCodes::BadValue, "bad match type for ComparisonMatchExpression" ); - } - - return initPath( path ); + return Status(ErrorCodes::BadValue, "bad match type for ComparisonMatchExpression"); } + return initPath(path); +} - bool ComparisonMatchExpression::matchesSingleElement( const BSONElement& e ) const { - //log() << "\t ComparisonMatchExpression e: " << e << " _rhs: " << _rhs << "\n" - //<< toString() << std::endl; - if ( e.canonicalType() != _rhs.canonicalType() ) { - // some special cases - // jstNULL and undefined are treated the same - if ( e.canonicalType() + _rhs.canonicalType() == 5 ) { - return matchType() == EQ || matchType() == LTE || matchType() == GTE; - } +bool ComparisonMatchExpression::matchesSingleElement(const BSONElement& e) const { + // log() << "\t ComparisonMatchExpression e: " << e << " _rhs: " << _rhs << "\n" + //<< toString() << std::endl; - if ( _rhs.type() == MaxKey || _rhs.type() == MinKey ) { - return matchType() != EQ; - } + if (e.canonicalType() != _rhs.canonicalType()) { + // some special cases + // jstNULL and undefined are treated the same + if (e.canonicalType() + _rhs.canonicalType() == 5) { + return matchType() == EQ || matchType() == LTE || matchType() == GTE; + } - return false; + if (_rhs.type() == MaxKey || _rhs.type() == MinKey) { + return matchType() != EQ; } - // Special case handling for NaN. NaN is equal to NaN but - // otherwise always compares to false. - if (std::isnan(e.numberDouble()) || std::isnan(_rhs.numberDouble())) { - bool bothNaN = std::isnan(e.numberDouble()) && std::isnan(_rhs.numberDouble()); - switch ( matchType() ) { + return false; + } + + // Special case handling for NaN. NaN is equal to NaN but + // otherwise always compares to false. + if (std::isnan(e.numberDouble()) || std::isnan(_rhs.numberDouble())) { + bool bothNaN = std::isnan(e.numberDouble()) && std::isnan(_rhs.numberDouble()); + switch (matchType()) { case LT: return false; case LTE: @@ -137,15 +135,15 @@ namespace mongo { default: // This is a comparison match expression, so it must be either // a $lt, $lte, $gt, $gte, or equality expression. - fassertFailed( 17448 ); - } + fassertFailed(17448); } + } - int x = compareElementValues( e, _rhs ); + int x = compareElementValues(e, _rhs); - //log() << "\t\t" << x << endl; + // log() << "\t\t" << x << endl; - switch ( matchType() ) { + switch (matchType()) { case LT: return x < 0; case LTE: @@ -159,450 +157,462 @@ namespace mongo { default: // This is a comparison match expression, so it must be either // a $lt, $lte, $gt, $gte, or equality expression. - fassertFailed( 16828 ); - } + fassertFailed(16828); } +} - void ComparisonMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << path() << " "; - 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; - default: debug << " UNKNOWN - should be impossible"; break; - } - debug << " " << _rhs.toString( false ); - - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); - } - - debug << "\n"; +void ComparisonMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << path() << " "; + 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; + default: + debug << " UNKNOWN - should be impossible"; + break; } + debug << " " << _rhs.toString(false); - void ComparisonMatchExpression::toBSON(BSONObjBuilder* out) const { - string opString = ""; - switch ( matchType() ) { - case LT: opString = "$lt"; break; - case LTE: opString = "$lte"; break; - case EQ: opString = "$eq"; break; - case GT: opString = "$gt"; break; - case GTE: opString = "$gte"; break; - default: opString = " UNKNOWN - should be impossible"; break; - } - - out->append(path(), BSON(opString << _rhs)); + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } - // --------------- - - // TODO: move - inline pcrecpp::RE_Options flags2options(const char* flags) { - pcrecpp::RE_Options options; - options.set_utf8(true); - while ( flags && *flags ) { - if ( *flags == 'i' ) - options.set_caseless(true); - else if ( *flags == 'm' ) - options.set_multiline(true); - else if ( *flags == 'x' ) - options.set_extended(true); - else if ( *flags == 's' ) - options.set_dotall(true); - flags++; - } - return options; + debug << "\n"; +} + +void ComparisonMatchExpression::toBSON(BSONObjBuilder* out) const { + string opString = ""; + switch (matchType()) { + case LT: + opString = "$lt"; + break; + case LTE: + opString = "$lte"; + break; + case EQ: + opString = "$eq"; + break; + case GT: + opString = "$gt"; + break; + case GTE: + opString = "$gte"; + break; + default: + opString = " UNKNOWN - should be impossible"; + break; } - RegexMatchExpression::RegexMatchExpression() - : LeafMatchExpression( REGEX ) {} + out->append(path(), BSON(opString << _rhs)); +} - RegexMatchExpression::~RegexMatchExpression() {} +// --------------- + +// TODO: move +inline pcrecpp::RE_Options flags2options(const char* flags) { + pcrecpp::RE_Options options; + options.set_utf8(true); + while (flags && *flags) { + if (*flags == 'i') + options.set_caseless(true); + else if (*flags == 'm') + options.set_multiline(true); + else if (*flags == 'x') + options.set_extended(true); + else if (*flags == 's') + options.set_dotall(true); + flags++; + } + return options; +} - bool RegexMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; +RegexMatchExpression::RegexMatchExpression() : LeafMatchExpression(REGEX) {} - const RegexMatchExpression* realOther = static_cast<const RegexMatchExpression*>( other ); - return - path() == realOther->path() && - _regex == realOther->_regex - && _flags == realOther->_flags; - } +RegexMatchExpression::~RegexMatchExpression() {} +bool RegexMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) + return false; - Status RegexMatchExpression::init( StringData path, const BSONElement& e ) { - if ( e.type() != RegEx ) - return Status( ErrorCodes::BadValue, "regex not a regex" ); - return init( path, e.regex(), e.regexFlags() ); - } + const RegexMatchExpression* realOther = static_cast<const RegexMatchExpression*>(other); + return path() == realOther->path() && _regex == realOther->_regex && + _flags == realOther->_flags; +} - Status RegexMatchExpression::init( StringData path, StringData regex, StringData options ) { - if ( regex.size() > MaxPatternSize ) { - return Status( ErrorCodes::BadValue, "Regular expression is too long" ); - } +Status RegexMatchExpression::init(StringData path, const BSONElement& e) { + if (e.type() != RegEx) + return Status(ErrorCodes::BadValue, "regex not a regex"); + return init(path, e.regex(), e.regexFlags()); +} - _regex = regex.toString(); - _flags = options.toString(); - _re.reset( new pcrecpp::RE( _regex.c_str(), flags2options( _flags.c_str() ) ) ); - return initPath( path ); +Status RegexMatchExpression::init(StringData path, StringData regex, StringData options) { + if (regex.size() > MaxPatternSize) { + return Status(ErrorCodes::BadValue, "Regular expression is too long"); } - bool RegexMatchExpression::matchesSingleElement( const BSONElement& e ) const { - //log() << "RegexMatchExpression::matchesSingleElement _regex: " << _regex << " e: " << e << std::endl; - switch (e.type()) { + _regex = regex.toString(); + _flags = options.toString(); + _re.reset(new pcrecpp::RE(_regex.c_str(), flags2options(_flags.c_str()))); + + return initPath(path); +} + +bool RegexMatchExpression::matchesSingleElement(const BSONElement& e) const { + // log() << "RegexMatchExpression::matchesSingleElement _regex: " << _regex << " e: " << e << std::endl; + switch (e.type()) { case String: case Symbol: // TODO - //if (rm._prefix.empty()) - return _re->PartialMatch(e.valuestr()); - //else - //return !strncmp(e.valuestr(), rm._prefix.c_str(), rm._prefix.size()); + // if (rm._prefix.empty()) + return _re->PartialMatch(e.valuestr()); + // else + // return !strncmp(e.valuestr(), rm._prefix.c_str(), rm._prefix.size()); case RegEx: return _regex == e.regex() && _flags == e.regexFlags(); default: return false; - } } +} - void RegexMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << path() << " regex /" << _regex << "/" << _flags; +void RegexMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << path() << " regex /" << _regex << "/" << _flags; - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); - } - debug << "\n"; + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } + debug << "\n"; +} - void RegexMatchExpression::toBSON(BSONObjBuilder* out) const { - out->appendRegex(path(), _regex, _flags); - } +void RegexMatchExpression::toBSON(BSONObjBuilder* out) const { + out->appendRegex(path(), _regex, _flags); +} - void RegexMatchExpression::shortDebugString( StringBuilder& debug ) const { - debug << "/" << _regex << "/" << _flags; - } +void RegexMatchExpression::shortDebugString(StringBuilder& debug) const { + debug << "/" << _regex << "/" << _flags; +} - // --------- +// --------- - Status ModMatchExpression::init( StringData path, int divisor, int remainder ) { - if ( divisor == 0 ) - return Status( ErrorCodes::BadValue, "divisor cannot be 0" ); - _divisor = divisor; - _remainder = remainder; - return initPath( path ); - } +Status ModMatchExpression::init(StringData path, int divisor, int remainder) { + if (divisor == 0) + return Status(ErrorCodes::BadValue, "divisor cannot be 0"); + _divisor = divisor; + _remainder = remainder; + return initPath(path); +} - bool ModMatchExpression::matchesSingleElement( const BSONElement& e ) const { - if ( !e.isNumber() ) - return false; - return e.numberLong() % _divisor == _remainder; - } +bool ModMatchExpression::matchesSingleElement(const BSONElement& e) const { + if (!e.isNumber()) + return false; + return e.numberLong() % _divisor == _remainder; +} - void ModMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << path() << " mod " << _divisor << " % x == " << _remainder; - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); - } - debug << "\n"; +void ModMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << path() << " mod " << _divisor << " % x == " << _remainder; + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } + debug << "\n"; +} - void ModMatchExpression::toBSON(BSONObjBuilder* out) const { - out->append(path(), BSON("$mod" << BSON_ARRAY(_divisor << _remainder))); - } +void ModMatchExpression::toBSON(BSONObjBuilder* out) const { + out->append(path(), BSON("$mod" << BSON_ARRAY(_divisor << _remainder))); +} - bool ModMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; +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; - } + const ModMatchExpression* realOther = static_cast<const ModMatchExpression*>(other); + return path() == realOther->path() && _divisor == realOther->_divisor && + _remainder == realOther->_remainder; +} - // ------------------ +// ------------------ - Status ExistsMatchExpression::init( StringData path ) { - return initPath( path ); - } +Status ExistsMatchExpression::init(StringData path) { + return initPath(path); +} - bool ExistsMatchExpression::matchesSingleElement( const BSONElement& e ) const { - return !e.eoo(); - } +bool ExistsMatchExpression::matchesSingleElement(const BSONElement& e) const { + return !e.eoo(); +} - void ExistsMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << path() << " exists"; - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); - } - debug << "\n"; +void ExistsMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << path() << " exists"; + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } + debug << "\n"; +} - void ExistsMatchExpression::toBSON(BSONObjBuilder* out) const { - out->append(path(), BSON("$exists" << true)); - } +void ExistsMatchExpression::toBSON(BSONObjBuilder* out) const { + out->append(path(), BSON("$exists" << true)); +} - bool ExistsMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; +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(); - } + const ExistsMatchExpression* realOther = static_cast<const ExistsMatchExpression*>(other); + return path() == realOther->path(); +} - // ---- +// ---- - Status TypeMatchExpression::init( StringData path, int type ) { - _path = path; - _type = type; - return _elementPath.init( _path ); - } +Status TypeMatchExpression::init(StringData path, int type) { + _path = path; + _type = type; + return _elementPath.init(_path); +} - bool TypeMatchExpression::matchesSingleElement( const BSONElement& e ) const { - return e.type() == _type; - } +bool TypeMatchExpression::matchesSingleElement(const BSONElement& e) const { + return e.type() == _type; +} - bool TypeMatchExpression::matches( const MatchableDocument* doc, MatchDetails* details ) const { - MatchableDocument::IteratorHolder cursor( doc, &_elementPath ); - while ( cursor->more() ) { - ElementIterator::Context e = cursor->next(); - - // In the case where _elementPath is referring to an array, - // $type should match elements of that array only. - // outerArray() helps to identify elements of the array - // and the containing array itself. - // This matters when we are looking for {$type: Array}. - // Example (_elementPath refers to field 'a' and _type is Array): - // a : [ // outer array. should not match - // 123, // inner array - // [ 456 ], // inner array. should match - // ... - // ] - if ( _type == mongo::Array && e.outerArray() ) { - continue; - } - - if ( !matchesSingleElement( e.element() ) ) { - continue; - } - - if ( details && details->needRecord() && !e.arrayOffset().eoo() ) { - details->setElemMatchKey( e.arrayOffset().fieldName() ); - } - return true; +bool TypeMatchExpression::matches(const MatchableDocument* doc, MatchDetails* details) const { + MatchableDocument::IteratorHolder cursor(doc, &_elementPath); + while (cursor->more()) { + ElementIterator::Context e = cursor->next(); + + // In the case where _elementPath is referring to an array, + // $type should match elements of that array only. + // outerArray() helps to identify elements of the array + // and the containing array itself. + // This matters when we are looking for {$type: Array}. + // Example (_elementPath refers to field 'a' and _type is Array): + // a : [ // outer array. should not match + // 123, // inner array + // [ 456 ], // inner array. should match + // ... + // ] + if (_type == mongo::Array && e.outerArray()) { + continue; } - return false; - } - void TypeMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << _path << " type: " << _type; - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); + if (!matchesSingleElement(e.element())) { + continue; } - debug << "\n"; - } - void TypeMatchExpression::toBSON(BSONObjBuilder* out) const { - out->append(path(), BSON("$type" << _type)); + if (details && details->needRecord() && !e.arrayOffset().eoo()) { + details->setElemMatchKey(e.arrayOffset().fieldName()); + } + return true; } + return false; +} - 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; +void TypeMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << _path << " type: " << _type; + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } + debug << "\n"; +} +void TypeMatchExpression::toBSON(BSONObjBuilder* out) const { + out->append(path(), BSON("$type" << _type)); +} - // -------- +bool TypeMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) + return false; - ArrayFilterEntries::ArrayFilterEntries(){ - _hasNull = false; - _hasEmptyArray = false; - } + const TypeMatchExpression* realOther = static_cast<const TypeMatchExpression*>(other); + return _path == realOther->_path && _type == realOther->_type; +} - ArrayFilterEntries::~ArrayFilterEntries() { - for ( unsigned i = 0; i < _regexes.size(); i++ ) - delete _regexes[i]; - _regexes.clear(); - } - Status ArrayFilterEntries::addEquality( const BSONElement& e ) { - if ( e.type() == RegEx ) - return Status( ErrorCodes::BadValue, "ArrayFilterEntries equality cannot be a regex" ); +// -------- - if ( e.type() == Undefined ) { - return Status( ErrorCodes::BadValue, - "ArrayFilterEntries equality cannot be undefined" ); - } +ArrayFilterEntries::ArrayFilterEntries() { + _hasNull = false; + _hasEmptyArray = false; +} - if ( e.type() == jstNULL ) { - _hasNull = true; - } +ArrayFilterEntries::~ArrayFilterEntries() { + for (unsigned i = 0; i < _regexes.size(); i++) + delete _regexes[i]; + _regexes.clear(); +} - if ( e.type() == Array && e.Obj().isEmpty() ) - _hasEmptyArray = true; +Status ArrayFilterEntries::addEquality(const BSONElement& e) { + if (e.type() == RegEx) + return Status(ErrorCodes::BadValue, "ArrayFilterEntries equality cannot be a regex"); - _equalities.insert( e ); - return Status::OK(); + if (e.type() == Undefined) { + return Status(ErrorCodes::BadValue, "ArrayFilterEntries equality cannot be undefined"); } - Status ArrayFilterEntries::addRegex( RegexMatchExpression* expr ) { - _regexes.push_back( expr ); - return Status::OK(); + if (e.type() == jstNULL) { + _hasNull = true; } - bool ArrayFilterEntries::equivalent( const ArrayFilterEntries& other ) const { - if ( _hasNull != other._hasNull ) - return false; + if (e.type() == Array && e.Obj().isEmpty()) + _hasEmptyArray = true; - if ( _regexes.size() != other._regexes.size() ) + _equalities.insert(e); + return Status::OK(); +} + +Status ArrayFilterEntries::addRegex(RegexMatchExpression* expr) { + _regexes.push_back(expr); + 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; - for ( unsigned i = 0; i < _regexes.size(); i++ ) - if ( !_regexes[i]->equivalent( other._regexes[i] ) ) - return false; - return _equalities == other._equalities; - } + return _equalities == other._equalities; +} - void ArrayFilterEntries::copyTo( ArrayFilterEntries& toFillIn ) const { - toFillIn._hasNull = _hasNull; - toFillIn._hasEmptyArray = _hasEmptyArray; - toFillIn._equalities = _equalities; - for ( unsigned i = 0; i < _regexes.size(); i++ ) - toFillIn._regexes.push_back( static_cast<RegexMatchExpression*>(_regexes[i]->shallowClone()) ); - } +void ArrayFilterEntries::copyTo(ArrayFilterEntries& toFillIn) const { + toFillIn._hasNull = _hasNull; + toFillIn._hasEmptyArray = _hasEmptyArray; + toFillIn._equalities = _equalities; + for (unsigned i = 0; i < _regexes.size(); i++) + toFillIn._regexes.push_back( + static_cast<RegexMatchExpression*>(_regexes[i]->shallowClone())); +} - void ArrayFilterEntries::debugString( StringBuilder& debug ) const { - debug << "[ "; - for (BSONElementSet::const_iterator it = _equalities.begin(); - it != _equalities.end(); ++it) { - debug << it->toString( false ) << " "; - } - for (size_t i = 0; i < _regexes.size(); ++i) { - _regexes[i]->shortDebugString( debug ); - debug << " "; - } - debug << "]"; +void ArrayFilterEntries::debugString(StringBuilder& debug) const { + debug << "[ "; + for (BSONElementSet::const_iterator it = _equalities.begin(); it != _equalities.end(); ++it) { + debug << it->toString(false) << " "; } - - void ArrayFilterEntries::toBSON(BSONArrayBuilder* out) const { - for (BSONElementSet::const_iterator it = _equalities.begin(); - it != _equalities.end(); ++it) { - out->append(*it); - } - for (size_t i = 0; i < _regexes.size(); ++i) { - BSONObjBuilder regexBob; - _regexes[i]->toBSON(®exBob); - out->append(regexBob.obj().firstElement()); - } - out->doneFast(); + for (size_t i = 0; i < _regexes.size(); ++i) { + _regexes[i]->shortDebugString(debug); + debug << " "; } + debug << "]"; +} - // ----------- - - Status InMatchExpression::init( StringData path ) { - return initPath( path ); +void ArrayFilterEntries::toBSON(BSONArrayBuilder* out) const { + for (BSONElementSet::const_iterator it = _equalities.begin(); it != _equalities.end(); ++it) { + out->append(*it); } + for (size_t i = 0; i < _regexes.size(); ++i) { + BSONObjBuilder regexBob; + _regexes[i]->toBSON(®exBob); + out->append(regexBob.obj().firstElement()); + } + out->doneFast(); +} - bool InMatchExpression::_matchesRealElement( const BSONElement& e ) const { - if ( _arrayEntries.contains( e ) ) - return true; +// ----------- - for ( unsigned i = 0; i < _arrayEntries.numRegexes(); i++ ) { - if ( _arrayEntries.regex(i)->matchesSingleElement( e ) ) - return true; - } +Status InMatchExpression::init(StringData path) { + return initPath(path); +} - return false; - } +bool InMatchExpression::_matchesRealElement(const BSONElement& e) const { + if (_arrayEntries.contains(e)) + return true; - bool InMatchExpression::matchesSingleElement( const BSONElement& e ) const { - if ( _arrayEntries.hasNull() && e.eoo() ) + for (unsigned i = 0; i < _arrayEntries.numRegexes(); i++) { + if (_arrayEntries.regex(i)->matchesSingleElement(e)) return true; + } - if ( _matchesRealElement( e ) ) - return true; + return false; +} - /* - if ( e.type() == Array ) { - BSONObjIterator i( e.Obj() ); - while ( i.more() ) { - BSONElement sub = i.next(); - if ( _matchesRealElement( sub ) ) - return true; - } - } - */ +bool InMatchExpression::matchesSingleElement(const BSONElement& e) const { + if (_arrayEntries.hasNull() && e.eoo()) + return true; - return false; - } + if (_matchesRealElement(e)) + return true; - void InMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << path() << " $in "; - _arrayEntries.debugString(debug); - MatchExpression::TagData* td = getTag(); - if (NULL != td) { - debug << " "; - td->debugString(&debug); + /* + if ( e.type() == Array ) { + BSONObjIterator i( e.Obj() ); + while ( i.more() ) { + BSONElement sub = i.next(); + if ( _matchesRealElement( sub ) ) + return true; } - debug << "\n"; - } - - void InMatchExpression::toBSON(BSONObjBuilder* out) const { - BSONObjBuilder inBob(out->subobjStart(path())); - BSONArrayBuilder arrBob(inBob.subarrayStart("$in")); - _arrayEntries.toBSON(&arrBob); - inBob.doneFast(); } + */ - bool InMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; - const InMatchExpression* realOther = static_cast<const InMatchExpression*>( other ); - return - path() == realOther->path() && - _arrayEntries.equivalent( realOther->_arrayEntries ); - } + return false; +} - LeafMatchExpression* InMatchExpression::shallowClone() const { - InMatchExpression* next = new InMatchExpression(); - copyTo( next ); - if ( getTag() ) { - next->setTag(getTag()->clone()); - } - return next; +void InMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << path() << " $in "; + _arrayEntries.debugString(debug); + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + debug << " "; + td->debugString(&debug); } + debug << "\n"; +} - void InMatchExpression::copyTo( InMatchExpression* toFillIn ) const { - toFillIn->init( path() ); - _arrayEntries.copyTo( toFillIn->_arrayEntries ); - } +void InMatchExpression::toBSON(BSONObjBuilder* out) const { + BSONObjBuilder inBob(out->subobjStart(path())); + BSONArrayBuilder arrBob(inBob.subarrayStart("$in")); + _arrayEntries.toBSON(&arrBob); + inBob.doneFast(); +} +bool InMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) + return false; + const InMatchExpression* realOther = static_cast<const InMatchExpression*>(other); + return path() == realOther->path() && _arrayEntries.equivalent(realOther->_arrayEntries); } +LeafMatchExpression* InMatchExpression::shallowClone() const { + InMatchExpression* next = new InMatchExpression(); + copyTo(next); + if (getTag()) { + next->setTag(getTag()->clone()); + } + return next; +} +void InMatchExpression::copyTo(InMatchExpression* toFillIn) const { + toFillIn->init(path()); + _arrayEntries.copyTo(toFillIn->_arrayEntries); +} +} diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h index 44f889d707b..e0e61ac11ea 100644 --- a/src/mongo/db/matcher/expression_leaf.h +++ b/src/mongo/db/matcher/expression_leaf.h @@ -36,366 +36,397 @@ #include "mongo/db/matcher/expression.h" namespace pcrecpp { - class RE; -} // namespace pcrecpp; +class RE; +} // namespace pcrecpp; namespace mongo { - /** - * This file contains leaves in the parse tree that are not array-based. - * - * LeafMatchExpression: REGEX MOD EXISTS MATCH_IN - * ComparisonMatchExpression: EQ LTE LT GT GTE - * MatchExpression: TYPE_OPERATOR - */ +/** + * This file contains leaves in the parse tree that are not array-based. + * + * LeafMatchExpression: REGEX MOD EXISTS MATCH_IN + * ComparisonMatchExpression: EQ LTE LT GT GTE + * MatchExpression: TYPE_OPERATOR + */ - /** - * Many operators subclass from this: - * REGEX, MOD, EXISTS, IN - * Everything that inherits from ComparisonMatchExpression. - */ - class LeafMatchExpression : public MatchExpression { - public: - LeafMatchExpression( MatchType matchType ) - : MatchExpression( matchType ) { - } +/** + * Many operators subclass from this: + * REGEX, MOD, EXISTS, IN + * Everything that inherits from ComparisonMatchExpression. + */ +class LeafMatchExpression : public MatchExpression { +public: + LeafMatchExpression(MatchType matchType) : MatchExpression(matchType) {} - virtual ~LeafMatchExpression(){} + virtual ~LeafMatchExpression() {} - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const; + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const; - virtual bool matchesSingleElement( const BSONElement& e ) const = 0; + virtual bool matchesSingleElement(const BSONElement& e) const = 0; - virtual const StringData path() const { return _path; } + virtual const StringData path() const { + return _path; + } - protected: - Status initPath( StringData path ); +protected: + Status initPath(StringData path); - private: - StringData _path; - ElementPath _elementPath; - }; +private: + StringData _path; + ElementPath _elementPath; +}; - /** - * EQ, LTE, LT, GT, GTE subclass from ComparisonMatchExpression. - */ - class ComparisonMatchExpression : public LeafMatchExpression { - public: - ComparisonMatchExpression( MatchType type ) : LeafMatchExpression( type ){} +/** + * EQ, LTE, LT, GT, GTE subclass from ComparisonMatchExpression. + */ +class ComparisonMatchExpression : public LeafMatchExpression { +public: + ComparisonMatchExpression(MatchType type) : LeafMatchExpression(type) {} - Status init( StringData path, const BSONElement& rhs ); + Status init(StringData path, const BSONElement& rhs); - virtual ~ComparisonMatchExpression(){} + virtual ~ComparisonMatchExpression() {} - virtual bool matchesSingleElement( const BSONElement& e ) const; + virtual bool matchesSingleElement(const BSONElement& e) const; - virtual const BSONElement& getRHS() const { return _rhs; } + virtual const BSONElement& getRHS() const { + return _rhs; + } - virtual void debugString( StringBuilder& debug, int level = 0 ) const; + virtual void debugString(StringBuilder& debug, int level = 0) const; - virtual void toBSON(BSONObjBuilder* out) const; + virtual void toBSON(BSONObjBuilder* out) const; - virtual bool equivalent( const MatchExpression* other ) const; + virtual bool equivalent(const MatchExpression* other) const; - const BSONElement& getData() const { return _rhs; } + const BSONElement& getData() const { + return _rhs; + } - protected: - BSONElement _rhs; - }; +protected: + BSONElement _rhs; +}; - // - // ComparisonMatchExpression inheritors - // +// +// ComparisonMatchExpression inheritors +// - class EqualityMatchExpression : public ComparisonMatchExpression { - public: - EqualityMatchExpression() : ComparisonMatchExpression( EQ ){} - virtual LeafMatchExpression* shallowClone() const { - ComparisonMatchExpression* e = new EqualityMatchExpression(); - e->init( path(), _rhs ); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; +class EqualityMatchExpression : public ComparisonMatchExpression { +public: + EqualityMatchExpression() : ComparisonMatchExpression(EQ) {} + virtual LeafMatchExpression* shallowClone() const { + ComparisonMatchExpression* e = new EqualityMatchExpression(); + e->init(path(), _rhs); + if (getTag()) { + e->setTag(getTag()->clone()); } - }; - - class LTEMatchExpression : public ComparisonMatchExpression { - public: - LTEMatchExpression() : ComparisonMatchExpression( LTE ){} - virtual LeafMatchExpression* shallowClone() const { - ComparisonMatchExpression* e = new LTEMatchExpression(); - e->init( path(), _rhs ); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; - } - - }; - - class LTMatchExpression : public ComparisonMatchExpression { - public: - LTMatchExpression() : ComparisonMatchExpression( LT ){} - virtual LeafMatchExpression* shallowClone() const { - ComparisonMatchExpression* e = new LTMatchExpression(); - e->init( path(), _rhs ); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; + return e; + } +}; + +class LTEMatchExpression : public ComparisonMatchExpression { +public: + LTEMatchExpression() : ComparisonMatchExpression(LTE) {} + virtual LeafMatchExpression* shallowClone() const { + ComparisonMatchExpression* e = new LTEMatchExpression(); + e->init(path(), _rhs); + if (getTag()) { + e->setTag(getTag()->clone()); } - - }; - - class GTMatchExpression : public ComparisonMatchExpression { - public: - GTMatchExpression() : ComparisonMatchExpression( GT ){} - virtual LeafMatchExpression* shallowClone() const { - ComparisonMatchExpression* e = new GTMatchExpression(); - e->init( path(), _rhs ); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; + return e; + } +}; + +class LTMatchExpression : public ComparisonMatchExpression { +public: + LTMatchExpression() : ComparisonMatchExpression(LT) {} + virtual LeafMatchExpression* shallowClone() const { + ComparisonMatchExpression* e = new LTMatchExpression(); + e->init(path(), _rhs); + if (getTag()) { + e->setTag(getTag()->clone()); } - - }; - - class GTEMatchExpression : public ComparisonMatchExpression { - public: - GTEMatchExpression() : ComparisonMatchExpression( GTE ){} - virtual LeafMatchExpression* shallowClone() const { - ComparisonMatchExpression* e = new GTEMatchExpression(); - e->init( path(), _rhs ); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; + return e; + } +}; + +class GTMatchExpression : public ComparisonMatchExpression { +public: + GTMatchExpression() : ComparisonMatchExpression(GT) {} + virtual LeafMatchExpression* shallowClone() const { + ComparisonMatchExpression* e = new GTMatchExpression(); + e->init(path(), _rhs); + if (getTag()) { + e->setTag(getTag()->clone()); } - - }; - - // - // LeafMatchExpression inheritors - // - - class RegexMatchExpression : public LeafMatchExpression { - public: - /** - * Maximum pattern size which pcre v8.3 can do matches correctly with - * LINK_SIZE define macro set to 2 @ pcre's config.h (based on - * experiments) - */ - static const size_t MaxPatternSize = 32764; - - RegexMatchExpression(); - ~RegexMatchExpression(); - - Status init( StringData path, StringData regex, StringData options ); - Status init( StringData path, const BSONElement& e ); - - virtual LeafMatchExpression* shallowClone() const { - RegexMatchExpression* e = new RegexMatchExpression(); - e->init( path(), _regex, _flags ); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; + return e; + } +}; + +class GTEMatchExpression : public ComparisonMatchExpression { +public: + GTEMatchExpression() : ComparisonMatchExpression(GTE) {} + virtual LeafMatchExpression* shallowClone() const { + ComparisonMatchExpression* e = new GTEMatchExpression(); + e->init(path(), _rhs); + if (getTag()) { + e->setTag(getTag()->clone()); } + return e; + } +}; - virtual bool matchesSingleElement( const BSONElement& e ) const; - - virtual void debugString( StringBuilder& debug, int level ) const; - - virtual void toBSON(BSONObjBuilder* out) const; +// +// LeafMatchExpression inheritors +// - void shortDebugString( StringBuilder& debug ) const; +class RegexMatchExpression : public LeafMatchExpression { +public: + /** + * Maximum pattern size which pcre v8.3 can do matches correctly with + * LINK_SIZE define macro set to 2 @ pcre's config.h (based on + * experiments) + */ + static const size_t MaxPatternSize = 32764; - virtual bool equivalent( const MatchExpression* other ) const; + RegexMatchExpression(); + ~RegexMatchExpression(); - const std::string& getString() const { return _regex; } - const std::string& getFlags() const { return _flags; } + Status init(StringData path, StringData regex, StringData options); + Status init(StringData path, const BSONElement& e); - private: - std::string _regex; - std::string _flags; - std::unique_ptr<pcrecpp::RE> _re; - }; + virtual LeafMatchExpression* shallowClone() const { + RegexMatchExpression* e = new RegexMatchExpression(); + e->init(path(), _regex, _flags); + if (getTag()) { + e->setTag(getTag()->clone()); + } + return e; + } - class ModMatchExpression : public LeafMatchExpression { - public: - ModMatchExpression() : LeafMatchExpression( MOD ){} + virtual bool matchesSingleElement(const BSONElement& e) const; - Status init( StringData path, int divisor, int remainder ); + virtual void debugString(StringBuilder& debug, int level) const; - virtual LeafMatchExpression* shallowClone() const { - ModMatchExpression* m = new ModMatchExpression(); - m->init( path(), _divisor, _remainder ); - if ( getTag() ) { - m->setTag(getTag()->clone()); - } - return m; - } + virtual void toBSON(BSONObjBuilder* out) const; - virtual bool matchesSingleElement( const BSONElement& e ) const; + void shortDebugString(StringBuilder& debug) const; - virtual void debugString( StringBuilder& debug, int level ) const; + virtual bool equivalent(const MatchExpression* other) const; - virtual void toBSON(BSONObjBuilder* out) const; + const std::string& getString() const { + return _regex; + } + const std::string& getFlags() const { + return _flags; + } - virtual bool equivalent( const MatchExpression* other ) const; +private: + std::string _regex; + std::string _flags; + std::unique_ptr<pcrecpp::RE> _re; +}; - int getDivisor() const { return _divisor; } - int getRemainder() const { return _remainder; } +class ModMatchExpression : public LeafMatchExpression { +public: + ModMatchExpression() : LeafMatchExpression(MOD) {} - private: - int _divisor; - int _remainder; - }; + Status init(StringData path, int divisor, int remainder); - class ExistsMatchExpression : public LeafMatchExpression { - public: - ExistsMatchExpression() : LeafMatchExpression( EXISTS ){} + virtual LeafMatchExpression* shallowClone() const { + ModMatchExpression* m = new ModMatchExpression(); + m->init(path(), _divisor, _remainder); + if (getTag()) { + m->setTag(getTag()->clone()); + } + return m; + } - Status init( StringData path ); + virtual bool matchesSingleElement(const BSONElement& e) const; - virtual LeafMatchExpression* shallowClone() const { - ExistsMatchExpression* e = new ExistsMatchExpression(); - e->init( path() ); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; - } + virtual void debugString(StringBuilder& debug, int level) const; - virtual bool matchesSingleElement( const BSONElement& e ) const; + virtual void toBSON(BSONObjBuilder* out) const; - virtual void debugString( StringBuilder& debug, int level ) const; + virtual bool equivalent(const MatchExpression* other) const; - virtual void toBSON(BSONObjBuilder* out) const; + int getDivisor() const { + return _divisor; + } + int getRemainder() const { + return _remainder; + } - virtual bool equivalent( const MatchExpression* other ) const; - }; +private: + int _divisor; + int _remainder; +}; - /** - * INTERNAL - * terrible name - * holds the entries of an $in or $all - * either scalars or regex - */ - class ArrayFilterEntries { - MONGO_DISALLOW_COPYING( ArrayFilterEntries ); - public: - ArrayFilterEntries(); - ~ArrayFilterEntries(); +class ExistsMatchExpression : public LeafMatchExpression { +public: + ExistsMatchExpression() : LeafMatchExpression(EXISTS) {} - Status addEquality( const BSONElement& e ); - Status addRegex( RegexMatchExpression* expr ); + Status init(StringData path); - const BSONElementSet& equalities() const { return _equalities; } - bool contains( const BSONElement& elem ) const { return _equalities.count(elem) > 0; } + virtual LeafMatchExpression* shallowClone() const { + ExistsMatchExpression* e = new ExistsMatchExpression(); + e->init(path()); + if (getTag()) { + e->setTag(getTag()->clone()); + } + return e; + } - size_t numRegexes() const { return _regexes.size(); } - RegexMatchExpression* regex( int idx ) const { return _regexes[idx]; } + virtual bool matchesSingleElement(const BSONElement& e) const; - bool hasNull() const { return _hasNull; } - bool singleNull() const { return size() == 1 && _hasNull; } - bool hasEmptyArray() const { return _hasEmptyArray; } - int size() const { return _equalities.size() + _regexes.size(); } + virtual void debugString(StringBuilder& debug, int level) const; - bool equivalent( const ArrayFilterEntries& other ) const; + virtual void toBSON(BSONObjBuilder* out) const; - void copyTo( ArrayFilterEntries& toFillIn ) const; + virtual bool equivalent(const MatchExpression* other) const; +}; - void debugString( StringBuilder& debug ) const; +/** + * INTERNAL + * terrible name + * holds the entries of an $in or $all + * either scalars or regex + */ +class ArrayFilterEntries { + MONGO_DISALLOW_COPYING(ArrayFilterEntries); + +public: + ArrayFilterEntries(); + ~ArrayFilterEntries(); + + Status addEquality(const BSONElement& e); + Status addRegex(RegexMatchExpression* expr); + + const BSONElementSet& equalities() const { + return _equalities; + } + bool contains(const BSONElement& elem) const { + return _equalities.count(elem) > 0; + } + + size_t numRegexes() const { + return _regexes.size(); + } + RegexMatchExpression* regex(int idx) const { + return _regexes[idx]; + } + + bool hasNull() const { + return _hasNull; + } + bool singleNull() const { + return size() == 1 && _hasNull; + } + bool hasEmptyArray() const { + return _hasEmptyArray; + } + int size() const { + return _equalities.size() + _regexes.size(); + } + + bool equivalent(const ArrayFilterEntries& other) const; + + void copyTo(ArrayFilterEntries& toFillIn) const; + + void debugString(StringBuilder& debug) const; + + void toBSON(BSONArrayBuilder* out) const; + +private: + bool _hasNull; // if _equalities has a jstNULL element in it + bool _hasEmptyArray; + BSONElementSet _equalities; + std::vector<RegexMatchExpression*> _regexes; +}; - void toBSON(BSONArrayBuilder* out) const; +/** + * query operator: $in + */ +class InMatchExpression : public LeafMatchExpression { +public: + InMatchExpression() : LeafMatchExpression(MATCH_IN) {} + Status init(StringData path); - private: - bool _hasNull; // if _equalities has a jstNULL element in it - bool _hasEmptyArray; - BSONElementSet _equalities; - std::vector<RegexMatchExpression*> _regexes; - }; + virtual LeafMatchExpression* shallowClone() const; - /** - * query operator: $in - */ - class InMatchExpression : public LeafMatchExpression { - public: - InMatchExpression() : LeafMatchExpression( MATCH_IN ){} - Status init( StringData path ); + ArrayFilterEntries* getArrayFilterEntries() { + return &_arrayEntries; + } - virtual LeafMatchExpression* shallowClone() const; + virtual bool matchesSingleElement(const BSONElement& e) const; - ArrayFilterEntries* getArrayFilterEntries() { return &_arrayEntries; } + virtual void debugString(StringBuilder& debug, int level) const; - virtual bool matchesSingleElement( const BSONElement& e ) const; + virtual void toBSON(BSONObjBuilder* out) const; - virtual void debugString( StringBuilder& debug, int level ) const; + virtual bool equivalent(const MatchExpression* other) const; - virtual void toBSON(BSONObjBuilder* out) const; + void copyTo(InMatchExpression* toFillIn) const; - virtual bool equivalent( const MatchExpression* other ) const; + const ArrayFilterEntries& getData() const { + return _arrayEntries; + } - void copyTo( InMatchExpression* toFillIn ) const; +private: + bool _matchesRealElement(const BSONElement& e) const; + ArrayFilterEntries _arrayEntries; +}; - const ArrayFilterEntries& getData() const { return _arrayEntries; } +// +// The odd duck out, TYPE_OPERATOR. +// - private: - bool _matchesRealElement( const BSONElement& e ) const; - ArrayFilterEntries _arrayEntries; - }; +/** + * Type has some odd semantics with arrays and as such it can't inherit from + * LeafMatchExpression. + */ +class TypeMatchExpression : public MatchExpression { +public: + TypeMatchExpression() : MatchExpression(TYPE_OPERATOR) {} - // - // The odd duck out, TYPE_OPERATOR. - // + Status init(StringData path, int type); - /** - * Type has some odd semantics with arrays and as such it can't inherit from - * LeafMatchExpression. - */ - class TypeMatchExpression : public MatchExpression { - public: - TypeMatchExpression() : MatchExpression( TYPE_OPERATOR ){} - - Status init( StringData path, int type ); - - virtual MatchExpression* shallowClone() const { - TypeMatchExpression* e = new TypeMatchExpression(); - e->init(_path, _type); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; + virtual MatchExpression* shallowClone() const { + TypeMatchExpression* e = new TypeMatchExpression(); + e->init(_path, _type); + if (getTag()) { + e->setTag(getTag()->clone()); } + return e; + } - virtual bool matchesSingleElement( const BSONElement& e ) const; + virtual bool matchesSingleElement(const BSONElement& e) const; - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const; + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const; - virtual void debugString( StringBuilder& debug, int level ) const; + virtual void debugString(StringBuilder& debug, int level) const; - virtual void toBSON(BSONObjBuilder* out) const; + virtual void toBSON(BSONObjBuilder* out) const; - virtual bool equivalent( const MatchExpression* other ) const; + virtual bool equivalent(const MatchExpression* other) const; - /** - * What is the type we're matching against? - */ - int getData() const { return _type; } + /** + * What is the type we're matching against? + */ + int getData() const { + return _type; + } - virtual const StringData path() const { return _path; } + virtual const StringData path() const { + return _path; + } - private: - bool _matches( StringData path, - const MatchableDocument* doc, - MatchDetails* details = 0 ) const; +private: + bool _matches(StringData path, const MatchableDocument* doc, MatchDetails* details = 0) const; - StringData _path; - ElementPath _elementPath; - int _type; - }; + StringData _path; + ElementPath _elementPath; + int _type; +}; } // namespace mongo diff --git a/src/mongo/db/matcher/expression_leaf_test.cpp b/src/mongo/db/matcher/expression_leaf_test.cpp index ed2f7b259d9..10ae1e8bc10 100644 --- a/src/mongo/db/matcher/expression_leaf_test.cpp +++ b/src/mongo/db/matcher/expression_leaf_test.cpp @@ -38,1618 +38,1663 @@ namespace mongo { - using std::string; - - TEST( EqOp, MatchesElement ) { - BSONObj operand = BSON( "a" << 5 ); - BSONObj match = BSON( "a" << 5.0 ); - BSONObj notMatch = BSON( "a" << 6 ); - - 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; - EqualityMatchExpression eq; - ASSERT( !eq.init( "", operand.firstElement() ).isOK() ); - } - - TEST( EqOp, MatchesScalar ) { - BSONObj operand = BSON( "a" << 5 ); - EqualityMatchExpression eq; - eq.init( "a", operand[ "a" ] ); - ASSERT( eq.matchesBSON( BSON( "a" << 5.0 ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( EqOp, MatchesArrayValue ) { - BSONObj operand = BSON( "a" << 5 ); - EqualityMatchExpression eq; - eq.init( "a", operand[ "a" ] ); - ASSERT( eq.matchesBSON( BSON( "a" << BSON_ARRAY( 5.0 << 6 ) ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << BSON_ARRAY( 6 << 7 ) ), NULL ) ); - } - - TEST( EqOp, MatchesReferencedObjectValue ) { - BSONObj operand = BSON( "a.b" << 5 ); - EqualityMatchExpression eq; - eq.init( "a.b", operand[ "a.b" ] ); - ASSERT( eq.matchesBSON( BSON( "a" << BSON( "b" << 5 ) ), NULL ) ); - ASSERT( eq.matchesBSON( BSON( "a" << BSON( "b" << BSON_ARRAY( 5 ) ) ), NULL ) ); - ASSERT( eq.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << 5 ) ) ), NULL ) ); - } - - TEST( EqOp, MatchesReferencedArrayValue ) { - BSONObj operand = BSON( "a.0" << 5 ); - EqualityMatchExpression eq; - eq.init( "a.0", operand[ "a.0" ] ); - ASSERT( eq.matchesBSON( BSON( "a" << BSON_ARRAY( 5 ) ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 5 ) ) ), NULL ) ); - } - - TEST( EqOp, MatchesNull ) { - BSONObj operand = BSON( "a" << BSONNULL ); - EqualityMatchExpression eq; - eq.init( "a", operand[ "a" ] ); - ASSERT( eq.matchesBSON( BSONObj(), NULL ) ); - ASSERT( eq.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << 4 ), NULL ) ); - // A non-existent field is treated same way as an empty bson object - ASSERT( eq.matchesBSON( BSON( "b" << 4 ), NULL ) ); - } - - // This test documents how the matcher currently works, - // not necessarily how it should work ideally. - TEST( EqOp, MatchesNestedNull ) { - BSONObj operand = BSON( "a.b" << BSONNULL ); - EqualityMatchExpression eq; - eq.init( "a.b", operand[ "a.b" ] ); - // null matches any empty object that is on a subpath of a.b - ASSERT( eq.matchesBSON( BSONObj(), NULL ) ); - ASSERT( eq.matchesBSON( BSON( "a" << BSONObj() ), NULL ) ); - ASSERT( eq.matchesBSON( BSON( "a" << BSON_ARRAY( BSONObj() ) ), NULL ) ); - ASSERT( eq.matchesBSON( BSON( "a" << BSON( "b" << BSONNULL ) ), NULL ) ); - // b does not exist as an element in array under a. - ASSERT( !eq.matchesBSON( BSON( "a" << BSONArray() ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << BSON_ARRAY( BSONNULL ) ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << BSON_ARRAY( 1 << 2 ) ), NULL ) ); - // a.b exists but is not null. - ASSERT( !eq.matchesBSON( BSON( "a" << BSON( "b" << 4 ) ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << BSON( "b" << BSONObj() ) ), NULL ) ); - // A non-existent field is treated same way as an empty bson object - ASSERT( eq.matchesBSON( BSON( "b" << 4 ), NULL ) ); - } - - TEST( EqOp, MatchesMinKey ) { - BSONObj operand = BSON( "a" << MinKey ); - EqualityMatchExpression eq; - eq.init( "a", operand[ "a" ] ); - ASSERT( eq.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - - - TEST( EqOp, MatchesMaxKey ) { - BSONObj operand = BSON( "a" << MaxKey ); - EqualityMatchExpression eq; - ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); - ASSERT( eq.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( EqOp, MatchesFullArray ) { - BSONObj operand = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); - EqualityMatchExpression eq; - ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); - ASSERT( eq.matchesBSON( BSON( "a" << BSON_ARRAY( 1 << 2 ) ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << BSON_ARRAY( 1 << 2 << 3 ) ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << BSON_ARRAY( 1 ) ), NULL ) ); - ASSERT( !eq.matchesBSON( BSON( "a" << 1 ), NULL ) ); - } - - TEST( EqOp, MatchesThroughNestedArray ) { - BSONObj operand = BSON( "a.b.c.d" << 3 ); - EqualityMatchExpression eq; - eq.init( "a.b.c.d", operand["a.b.c.d"] ); - BSONObj obj = fromjson("{a:{b:[{c:[{d:1},{d:2}]},{c:[{d:3}]}]}}"); - ASSERT( eq.matchesBSON( obj, NULL ) ); - } - - TEST( EqOp, ElemMatchKey ) { - BSONObj operand = BSON( "a" << 5 ); - EqualityMatchExpression eq; - ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !eq.matchesBSON( BSON( "a" << 4 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( eq.matchesBSON( BSON( "a" << 5 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( eq.matchesBSON( BSON( "a" << BSON_ARRAY( 1 << 2 << 5 ) ), &details ) ); - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( "2", details.elemMatchKey() ); - } - - // SERVER-14886: when an array is being traversed explictly at the same time that a nested array - // is being traversed implicitly, the elemMatch key should refer to the offset of the array - // being implicitly traversed. - TEST( EqOp, ElemMatchKeyWithImplicitAndExplicitTraversal ) { - BSONObj operand = BSON( "a.0.b" << 3 ); - BSONElement operandFirstElt = operand.firstElement(); - EqualityMatchExpression eq; - ASSERT( eq.init( operandFirstElt.fieldName(), operandFirstElt ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - BSONObj obj = fromjson("{a: [{b: [2, 3]}, {b: [4, 5]}]}"); - ASSERT( eq.matchesBSON( obj, &details ) ); - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( "1", 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 ); - EqualityMatchExpression eq; - ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - eq.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - eq.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - eq.matchesIndexKey( BSON( "" << BSON_ARRAY( 6 ) ), indexSpec ) ); - } - - TEST( EqOp, MatchesIndexKeyMissing ) { - BSONObj operand = BSON( "a" << 6 ); - EqualityMatchExpression eq; - ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); - IndexSpec indexSpec( BSON( "b" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - eq.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - eq.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - eq.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); - } - - TEST( EqOp, MatchesIndexKeyArray ) { - BSONObj operand = BSON( "a" << BSON_ARRAY( 4 << 5 ) ); - ComparisonMatchExpression eq - ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - eq.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); - } - - TEST( EqOp, MatchesIndexKeyArrayValue ) { - BSONObj operand = BSON( "a" << 6 ); - ComparisonMatchExpression eq - ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); - IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - eq.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - eq.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - eq.matchesIndexKey( BSON( "" << "dummygeohash" << - "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); - } - */ - TEST( LtOp, MatchesElement ) { - BSONObj operand = BSON( "$lt" << 5 ); - BSONObj match = BSON( "a" << 4.5 ); - BSONObj notMatch = BSON( "a" << 6 ); - BSONObj notMatchEqual = BSON( "a" << 5 ); - BSONObj notMatchWrongType = BSON( "a" << "foo" ); - LTMatchExpression lt; - ASSERT( lt.init( "", operand[ "$lt" ] ).isOK() ); - ASSERT( lt.matchesSingleElement( match.firstElement() ) ); - ASSERT( !lt.matchesSingleElement( notMatch.firstElement() ) ); - ASSERT( !lt.matchesSingleElement( notMatchEqual.firstElement() ) ); - ASSERT( !lt.matchesSingleElement( notMatchWrongType.firstElement() ) ); - } - - TEST( LtOp, InvalidEooOperand ) { - BSONObj operand; - LTMatchExpression lt; - ASSERT( !lt.init( "", operand.firstElement() ).isOK() ); - } - - TEST( LtOp, MatchesScalar ) { - BSONObj operand = BSON( "$lt" << 5 ); - LTMatchExpression lt; - ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); - ASSERT( lt.matchesBSON( BSON( "a" << 4.5 ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << 6 ), NULL ) ); - } - - TEST( LtOp, MatchesScalarEmptyKey ) { - BSONObj operand = BSON( "$lt" << 5 ); - LTMatchExpression lt; - ASSERT( lt.init( "", operand[ "$lt" ] ).isOK() ); - ASSERT( lt.matchesBSON( BSON( "" << 4.5 ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "" << 6 ), NULL ) ); - } - - TEST( LtOp, MatchesArrayValue ) { - BSONObj operand = BSON( "$lt" << 5 ); - LTMatchExpression lt; - ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); - ASSERT( lt.matchesBSON( BSON( "a" << BSON_ARRAY( 6 << 4.5 ) ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSON_ARRAY( 6 << 7 ) ), NULL ) ); - } - - TEST( LtOp, MatchesWholeArray ) { - BSONObj operand = BSON( "$lt" << BSON_ARRAY( 5 ) ); - LTMatchExpression lt; - ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); - ASSERT( lt.matchesBSON( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSON_ARRAY( 5 ) ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSON_ARRAY( 6 ) ), NULL ) ); - // Nested array. - ASSERT( lt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 4 ) ) ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 5 ) ) ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 6 ) ) ), NULL ) ); - } - - TEST( LtOp, MatchesNull ) { - BSONObj operand = BSON( "$lt" << BSONNULL ); - LTMatchExpression lt; - ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); - ASSERT( !lt.matchesBSON( BSONObj(), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << 4 ), NULL ) ); - // A non-existent field is treated same way as an empty bson object - ASSERT( !lt.matchesBSON( BSON( "b" << 4 ), NULL ) ); - } - - TEST( LtOp, MatchesDotNotationNull) { - BSONObj operand = BSON( "$lt" << BSONNULL ); - LTMatchExpression lt; - ASSERT( lt.init( "a.b", operand[ "$lt" ] ).isOK() ); - ASSERT( !lt.matchesBSON( BSONObj(), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << 4 ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSONObj() ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << BSONNULL ) ) ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "a" << 4 ) << BSON( "b" << 4 ) ) ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << 4 ) ) ), NULL ) ); - } - - TEST( LtOp, MatchesMinKey ) { - BSONObj operand = BSON( "a" << MinKey ); - LTMatchExpression lt; - ASSERT( lt.init( "a", operand[ "a" ] ).isOK() ); - ASSERT( !lt.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( !lt.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( LtOp, MatchesMaxKey ) { - BSONObj operand = BSON( "a" << MaxKey ); - LTMatchExpression lt; - ASSERT( lt.init( "a", operand[ "a" ] ).isOK() ); - ASSERT( !lt.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( lt.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( lt.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( LtOp, ElemMatchKey ) { - BSONObj operand = BSON( "$lt" << 5 ); - LTMatchExpression lt; - ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !lt.matchesBSON( BSON( "a" << 6 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( lt.matchesBSON( BSON( "a" << 4 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( lt.matchesBSON( BSON( "a" << BSON_ARRAY( 6 << 2 << 5 ) ), &details ) ); - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( "1", details.elemMatchKey() ); - } - - /** - TEST( LtOp, MatchesIndexKeyScalar ) { - BSONObj operand = BSON( "$lt" << 6 ); - LtOp lt; - ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - lt.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - lt.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - lt.matchesIndexKey( BSON( "" << BSON_ARRAY( 5 ) ), indexSpec ) ); - } - - TEST( LtOp, MatchesIndexKeyMissing ) { - BSONObj operand = BSON( "$lt" << 6 ); - LtOp lt; - ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); - IndexSpec indexSpec( BSON( "b" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - lt.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - lt.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - lt.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); - } - - TEST( LtOp, MatchesIndexKeyArray ) { - BSONObj operand = BSON( "$lt" << BSON_ARRAY( 4 << 5 ) ); - LtOp lt; - ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - lt.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); - } - - TEST( LtOp, MatchesIndexKeyArrayValue ) { - BSONObj operand = BSON( "$lt" << 6 ); - LtOp lt; - ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); - IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - lt.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 3 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - lt.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - lt.matchesIndexKey( BSON( "" << "dummygeohash" << - "" << BSON_ARRAY( 8 << 6 << 4 ) ), indexSpec ) ); - } - */ - TEST( LteOp, MatchesElement ) { - BSONObj operand = BSON( "$lte" << 5 ); - BSONObj match = BSON( "a" << 4.5 ); - BSONObj equalMatch = BSON( "a" << 5 ); - BSONObj notMatch = BSON( "a" << 6 ); - BSONObj notMatchWrongType = BSON( "a" << "foo" ); - LTEMatchExpression lte; - ASSERT( lte.init( "", operand[ "$lte" ] ).isOK() ); - ASSERT( lte.matchesSingleElement( match.firstElement() ) ); - ASSERT( lte.matchesSingleElement( equalMatch.firstElement() ) ); - ASSERT( !lte.matchesSingleElement( notMatch.firstElement() ) ); - ASSERT( !lte.matchesSingleElement( notMatchWrongType.firstElement() ) ); - } - - TEST( LteOp, InvalidEooOperand ) { - BSONObj operand; - LTEMatchExpression lte; - ASSERT( !lte.init( "", operand.firstElement() ).isOK() ); - } - - TEST( LteOp, MatchesScalar ) { - BSONObj operand = BSON( "$lte" << 5 ); - LTEMatchExpression lte; - ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); - ASSERT( lte.matchesBSON( BSON( "a" << 4.5 ), NULL ) ); - ASSERT( !lte.matchesBSON( BSON( "a" << 6 ), NULL ) ); - } - - TEST( LteOp, MatchesArrayValue ) { - BSONObj operand = BSON( "$lte" << 5 ); - LTEMatchExpression lte; - ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); - ASSERT( lte.matchesBSON( BSON( "a" << BSON_ARRAY( 6 << 4.5 ) ), NULL ) ); - ASSERT( !lte.matchesBSON( BSON( "a" << BSON_ARRAY( 6 << 7 ) ), NULL ) ); - } - - TEST( LteOp, MatchesWholeArray ) { - BSONObj operand = BSON( "$lte" << BSON_ARRAY( 5 ) ); - LTEMatchExpression lte; - ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); - ASSERT( lte.matchesBSON( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) ); - ASSERT( lte.matchesBSON( BSON( "a" << BSON_ARRAY( 5 ) ), NULL ) ); - ASSERT( !lte.matchesBSON( BSON( "a" << BSON_ARRAY( 6 ) ), NULL ) ); - // Nested array. - ASSERT( lte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 4 ) ) ), NULL ) ); - ASSERT( lte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 5 ) ) ), NULL ) ); - ASSERT( !lte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 6 ) ) ), NULL ) ); - } - - TEST( LteOp, MatchesNull ) { - BSONObj operand = BSON( "$lte" << BSONNULL ); - LTEMatchExpression lte; - ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); - ASSERT( lte.matchesBSON( BSONObj(), NULL ) ); - ASSERT( lte.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( !lte.matchesBSON( BSON( "a" << 4 ), NULL ) ); - // A non-existent field is treated same way as an empty bson object - ASSERT( lte.matchesBSON( BSON( "b" << 4 ), NULL ) ); - } - - TEST( LteOp, MatchesDotNotationNull) { - BSONObj operand = BSON( "$lte" << BSONNULL ); - LTEMatchExpression lte; - ASSERT( lte.init( "a.b", operand[ "$lte" ] ).isOK() ); - ASSERT( lte.matchesBSON( BSONObj(), NULL ) ); - ASSERT( lte.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( lte.matchesBSON( BSON( "a" << 4 ), NULL ) ); - ASSERT( lte.matchesBSON( BSON( "a" << BSONObj() ), NULL ) ); - ASSERT( lte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << BSONNULL ) ) ), NULL ) ); - ASSERT( lte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "a" << 4 ) << BSON( "b" << 4 ) ) ), NULL ) ); - ASSERT( !lte.matchesBSON( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) ); - ASSERT( !lte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << 4 ) ) ), NULL ) ); - } - - TEST( LteOp, MatchesMinKey ) { - BSONObj operand = BSON( "a" << MinKey ); - LTEMatchExpression lte; - ASSERT( lte.init( "a", operand[ "a" ] ).isOK() ); - ASSERT( lte.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( !lte.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( !lte.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( LteOp, MatchesMaxKey ) { - BSONObj operand = BSON( "a" << MaxKey ); - LTEMatchExpression lte; - ASSERT( lte.init( "a", operand[ "a" ] ).isOK() ); - ASSERT( lte.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( lte.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( lte.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - - TEST( LteOp, ElemMatchKey ) { - BSONObj operand = BSON( "$lte" << 5 ); - LTEMatchExpression lte; - ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !lte.matchesBSON( BSON( "a" << 6 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( lte.matchesBSON( BSON( "a" << 4 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( lte.matchesBSON( BSON( "a" << BSON_ARRAY( 6 << 2 << 5 ) ), &details ) ); - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( "1", details.elemMatchKey() ); - } - - /** - TEST( LteOp, MatchesIndexKeyScalar ) { - BSONObj operand = BSON( "$lte" << 6 ); - LteOp lte; - ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - lte.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - lte.matchesIndexKey( BSON( "" << 7 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - lte.matchesIndexKey( BSON( "" << BSON_ARRAY( 5 ) ), indexSpec ) ); - } - - TEST( LteOp, MatchesIndexKeyMissing ) { - BSONObj operand = BSON( "$lte" << 6 ); - LteOp lte; - ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); - IndexSpec indexSpec( BSON( "b" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - lte.matchesIndexKey( BSON( "" << 7 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - lte.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - lte.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); - } - - TEST( LteOp, MatchesIndexKeyArray ) { - BSONObj operand = BSON( "$lte" << BSON_ARRAY( 4 << 5 ) ); - LteOp lte; - ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - lte.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); - } - - TEST( LteOp, MatchesIndexKeyArrayValue ) { - BSONObj operand = BSON( "$lte" << 6 ); - LteOp lte; - ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); - IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - lte.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 3 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - lte.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 7 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - lte.matchesIndexKey( BSON( "" << "dummygeohash" << - "" << BSON_ARRAY( 8 << 6 << 4 ) ), indexSpec ) ); - } - - TEST( GtOp, MatchesElement ) { - BSONObj operand = BSON( "$gt" << 5 ); - BSONObj match = BSON( "a" << 5.5 ); - BSONObj notMatch = BSON( "a" << 4 ); - BSONObj notMatchEqual = BSON( "a" << 5 ); - BSONObj notMatchWrongType = BSON( "a" << "foo" ); - GtOp gt; - ASSERT( gt.init( "", operand[ "$gt" ] ).isOK() ); - ASSERT( gt.matchesSingleElement( match.firstElement() ) ); - ASSERT( !gt.matchesSingleElement( notMatch.firstElement() ) ); - ASSERT( !gt.matchesSingleElement( notMatchEqual.firstElement() ) ); - ASSERT( !gt.matchesSingleElement( notMatchWrongType.firstElement() ) ); - } - */ - - TEST( GtOp, InvalidEooOperand ) { - BSONObj operand; - GTMatchExpression gt; - ASSERT( !gt.init( "", operand.firstElement() ).isOK() ); - } - - TEST( GtOp, MatchesScalar ) { - BSONObj operand = BSON( "$gt" << 5 ); - GTMatchExpression gt; - ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); - ASSERT( gt.matchesBSON( BSON( "a" << 5.5 ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( GtOp, MatchesArrayValue ) { - BSONObj operand = BSON( "$gt" << 5 ); - GTMatchExpression gt; - ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); - ASSERT( gt.matchesBSON( BSON( "a" << BSON_ARRAY( 3 << 5.5 ) ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << BSON_ARRAY( 2 << 4 ) ), NULL ) ); - } - - TEST( GtOp, MatchesWholeArray ) { - BSONObj operand = BSON( "$gt" << BSON_ARRAY( 5 ) ); - GTMatchExpression gt; - ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); - ASSERT( !gt.matchesBSON( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << BSON_ARRAY( 5 ) ), NULL ) ); - ASSERT( gt.matchesBSON( BSON( "a" << BSON_ARRAY( 6 ) ), NULL ) ); - // Nested array. - // XXX: The following assertion documents current behavior. - ASSERT( gt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 4 ) ) ), NULL ) ); - // XXX: The following assertion documents current behavior. - ASSERT( gt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 5 ) ) ), NULL ) ); - ASSERT( gt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 6 ) ) ), NULL ) ); - } - - TEST( GtOp, MatchesNull ) { - BSONObj operand = BSON( "$gt" << BSONNULL ); - GTMatchExpression gt; - ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); - ASSERT( !gt.matchesBSON( BSONObj(), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << 4 ), NULL ) ); - // A non-existent field is treated same way as an empty bson object - ASSERT( !gt.matchesBSON( BSON( "b" << 4 ), NULL ) ); - } - - TEST( GtOp, MatchesDotNotationNull) { - BSONObj operand = BSON( "$gt" << BSONNULL ); - GTMatchExpression gt; - ASSERT( gt.init( "a.b", operand[ "$gt" ] ).isOK() ); - ASSERT( !gt.matchesBSON( BSONObj(), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << 4 ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << BSONObj() ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << BSONNULL ) ) ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "a" << 4 ) << BSON( "b" << 4 ) ) ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << 4 ) ) ), NULL ) ); - } - - TEST( GtOp, MatchesMinKey ) { - BSONObj operand = BSON( "a" << MinKey ); - GTMatchExpression gt; - ASSERT( gt.init( "a", operand[ "a" ] ).isOK() ); - ASSERT( !gt.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( gt.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( gt.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( GtOp, MatchesMaxKey ) { - BSONObj operand = BSON( "a" << MaxKey ); - GTMatchExpression gt; - ASSERT( gt.init( "a", operand[ "a" ] ).isOK() ); - ASSERT( !gt.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( !gt.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( GtOp, ElemMatchKey ) { - BSONObj operand = BSON( "$gt" << 5 ); - GTMatchExpression gt; - ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !gt.matchesBSON( BSON( "a" << 4 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( gt.matchesBSON( BSON( "a" << 6 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( gt.matchesBSON( BSON( "a" << BSON_ARRAY( 2 << 6 << 5 ) ), &details ) ); - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( "1", details.elemMatchKey() ); - } - - /** - TEST( GtOp, MatchesIndexKeyScalar ) { - BSONObj operand = BSON( "$gt" << 6 ); - GtOp gt; - ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - gt.matchesIndexKey( BSON( "" << 7 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - gt.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - gt.matchesIndexKey( BSON( "" << BSON_ARRAY( 9 ) ), indexSpec ) ); - } - - TEST( GtOp, MatchesIndexKeyMissing ) { - BSONObj operand = BSON( "$gt" << 6 ); - GtOp gt; - ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); - IndexSpec indexSpec( BSON( "b" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - gt.matchesIndexKey( BSON( "" << 7 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - gt.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - gt.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); - } - - TEST( GtOp, MatchesIndexKeyArray ) { - BSONObj operand = BSON( "$gt" << BSON_ARRAY( 4 << 5 ) ); - GtOp gt; - ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - gt.matchesIndexKey( BSON( "" << 8 ), indexSpec ) ); - } - - TEST( GtOp, MatchesIndexKeyArrayValue ) { - BSONObj operand = BSON( "$gt" << 6 ); - GtOp gt; - ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); - IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - gt.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 7 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - gt.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 3 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - gt.matchesIndexKey( BSON( "" << "dummygeohash" << - "" << BSON_ARRAY( 8 << 6 << 4 ) ), indexSpec ) ); - } - */ - - TEST( ComparisonMatchExpression, MatchesElement ) { - BSONObj operand = BSON( "$gte" << 5 ); - BSONObj match = BSON( "a" << 5.5 ); - BSONObj equalMatch = BSON( "a" << 5 ); - BSONObj notMatch = BSON( "a" << 4 ); - BSONObj notMatchWrongType = BSON( "a" << "foo" ); - GTEMatchExpression gte; - ASSERT( gte.init( "", operand[ "$gte" ] ).isOK() ); - ASSERT( gte.matchesSingleElement( match.firstElement() ) ); - ASSERT( gte.matchesSingleElement( equalMatch.firstElement() ) ); - ASSERT( !gte.matchesSingleElement( notMatch.firstElement() ) ); - ASSERT( !gte.matchesSingleElement( notMatchWrongType.firstElement() ) ); - } - - TEST( ComparisonMatchExpression, InvalidEooOperand ) { - BSONObj operand; - GTEMatchExpression gte; - ASSERT( !gte.init( "", operand.firstElement() ).isOK() ); - } - - TEST( ComparisonMatchExpression, MatchesScalar ) { - BSONObj operand = BSON( "$gte" << 5 ); - GTEMatchExpression gte; - ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); - ASSERT( gte.matchesBSON( BSON( "a" << 5.5 ), NULL ) ); - ASSERT( !gte.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( ComparisonMatchExpression, MatchesArrayValue ) { - BSONObj operand = BSON( "$gte" << 5 ); - GTEMatchExpression gte; - ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); - ASSERT( gte.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 5.5 ) ), NULL ) ); - ASSERT( !gte.matchesBSON( BSON( "a" << BSON_ARRAY( 1 << 2 ) ), NULL ) ); - } - - TEST( ComparisonMatchExpression, MatchesWholeArray ) { - BSONObj operand = BSON( "$gte" << BSON_ARRAY( 5 ) ); - GTEMatchExpression gte; - ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); - ASSERT( !gte.matchesBSON( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << BSON_ARRAY( 5 ) ), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << BSON_ARRAY( 6 ) ), NULL ) ); - // Nested array. - // XXX: The following assertion documents current behavior. - ASSERT( gte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 4 ) ) ), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 5 ) ) ), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 6 ) ) ), NULL ) ); - } - - TEST( ComparisonMatchExpression, MatchesNull ) { - BSONObj operand = BSON( "$gte" << BSONNULL ); - GTEMatchExpression gte; - ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); - ASSERT( gte.matchesBSON( BSONObj(), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( !gte.matchesBSON( BSON( "a" << 4 ), NULL ) ); - // A non-existent field is treated same way as an empty bson object - ASSERT( gte.matchesBSON( BSON( "b" << 4 ), NULL ) ); - } - - TEST( ComparisonMatchExpression, MatchesDotNotationNull) { - BSONObj operand = BSON( "$gte" << BSONNULL ); - GTEMatchExpression gte; - ASSERT( gte.init( "a.b", operand[ "$gte" ] ).isOK() ); - ASSERT( gte.matchesBSON( BSONObj(), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << 4 ), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << BSONObj() ), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << BSONNULL ) ) ), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "a" << 4 ) << BSON( "b" << 4 ) ) ), NULL ) ); - ASSERT( !gte.matchesBSON( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) ); - ASSERT( !gte.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << 4 ) ) ), NULL ) ); - } - - TEST( ComparisonMatchExpression, MatchesMinKey ) { - BSONObj operand = BSON( "a" << MinKey ); - GTEMatchExpression gte; - ASSERT( gte.init( "a", operand[ "a" ] ).isOK() ); - ASSERT( gte.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( gte.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( ComparisonMatchExpression, MatchesMaxKey ) { - BSONObj operand = BSON( "a" << MaxKey ); - GTEMatchExpression gte; - ASSERT( gte.init( "a", operand[ "a" ] ).isOK() ); - ASSERT( gte.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( !gte.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( !gte.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( ComparisonMatchExpression, ElemMatchKey ) { - BSONObj operand = BSON( "$gte" << 5 ); - GTEMatchExpression gte; - ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !gte.matchesBSON( BSON( "a" << 4 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( gte.matchesBSON( BSON( "a" << 6 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( gte.matchesBSON( BSON( "a" << BSON_ARRAY( 2 << 6 << 5 ) ), &details ) ); - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( "1", details.elemMatchKey() ); - } - - /** - TEST( GteOp, MatchesIndexKeyScalar ) { - BSONObj operand = BSON( "$gte" << 6 ); - GteOp gte; - ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - gte.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - gte.matchesIndexKey( BSON( "" << 5 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - gte.matchesIndexKey( BSON( "" << BSON_ARRAY( 7 ) ), indexSpec ) ); - } - - TEST( GteOp, MatchesIndexKeyMissing ) { - BSONObj operand = BSON( "$gte" << 6 ); - GteOp gte; - ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); - IndexSpec indexSpec( BSON( "b" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - gte.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - gte.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - gte.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); - } - - TEST( GteOp, MatchesIndexKeyArray ) { - BSONObj operand = BSON( "$gte" << BSON_ARRAY( 4 << 5 ) ); - GteOp gte; - ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - gte.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - } - - TEST( GteOp, MatchesIndexKeyArrayValue ) { - BSONObj operand = BSON( "$gte" << 6 ); - GteOp gte; - ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); - IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - gte.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - gte.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 3 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - gte.matchesIndexKey( BSON( "" << "dummygeohash" << - "" << BSON_ARRAY( 8 << 6 << 4 ) ), indexSpec ) ); - } - */ - - TEST( RegexMatchExpression, MatchesElementExact ) { - BSONObj match = BSON( "a" << "b" ); - BSONObj notMatch = BSON( "a" << "c" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "b", "" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, TooLargePattern ) { - string tooLargePattern( 50 * 1000, 'z' ); - RegexMatchExpression regex; - ASSERT( !regex.init( "a", tooLargePattern, "" ).isOK() ); - } - - TEST( RegexMatchExpression, MatchesElementSimplePrefix ) { - BSONObj match = BSON( "x" << "abc" ); - BSONObj notMatch = BSON( "x" << "adz" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "^ab", "" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementCaseSensitive ) { - BSONObj match = BSON( "x" << "abc" ); - BSONObj notMatch = BSON( "x" << "ABC" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "abc", "" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementCaseInsensitive ) { - BSONObj match = BSON( "x" << "abc" ); - BSONObj matchUppercase = BSON( "x" << "ABC" ); - BSONObj notMatch = BSON( "x" << "abz" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "abc", "i" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( regex.matchesSingleElement( matchUppercase.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementMultilineOff ) { - BSONObj match = BSON( "x" << "az" ); - BSONObj notMatch = BSON( "x" << "\naz" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "^a", "" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementMultilineOn ) { - BSONObj match = BSON( "x" << "az" ); - BSONObj matchMultiline = BSON( "x" << "\naz" ); - BSONObj notMatch = BSON( "x" << "\n\n" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "^a", "m" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( regex.matchesSingleElement( matchMultiline.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementExtendedOff ) { - BSONObj match = BSON( "x" << "a b" ); - BSONObj notMatch = BSON( "x" << "ab" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "a b", "" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementExtendedOn ) { - BSONObj match = BSON( "x" << "ab" ); - BSONObj notMatch = BSON( "x" << "a b" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "a b", "x" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementDotAllOff ) { - BSONObj match = BSON( "x" << "a b" ); - BSONObj notMatch = BSON( "x" << "a\nb" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "a.b", "" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementDotAllOn ) { - BSONObj match = BSON( "x" << "a b" ); - BSONObj matchDotAll = BSON( "x" << "a\nb" ); - BSONObj notMatch = BSON( "x" << "ab" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "a.b", "s" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( regex.matchesSingleElement( matchDotAll.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementMultipleFlags ) { - BSONObj matchMultilineDotAll = BSON( "x" << "\na\nb" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "^a.b", "ms" ).isOK() ); - ASSERT( regex.matchesSingleElement( matchMultilineDotAll.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementRegexType ) { - BSONObj match = BSONObjBuilder().appendRegex( "x", "yz", "i" ).obj(); - BSONObj notMatchPattern = BSONObjBuilder().appendRegex( "x", "r", "i" ).obj(); - BSONObj notMatchFlags = BSONObjBuilder().appendRegex( "x", "yz", "s" ).obj(); - RegexMatchExpression regex; - ASSERT( regex.init( "", "yz", "i" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatchPattern.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatchFlags.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementSymbolType ) { - BSONObj match = BSONObjBuilder().appendSymbol( "x", "yz" ).obj(); - BSONObj notMatch = BSONObjBuilder().appendSymbol( "x", "gg" ).obj(); - RegexMatchExpression regex; - ASSERT( regex.init( "", "yz", "" ).isOK() ); - ASSERT( regex.matchesSingleElement( match.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatch.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementWrongType ) { - BSONObj notMatchInt = BSON( "x" << 1 ); - BSONObj notMatchBool = BSON( "x" << true ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "1", "" ).isOK() ); - ASSERT( !regex.matchesSingleElement( notMatchInt.firstElement() ) ); - ASSERT( !regex.matchesSingleElement( notMatchBool.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesElementUtf8 ) { - BSONObj multiByteCharacter = BSON( "x" << "\xc2\xa5" ); - RegexMatchExpression regex; - ASSERT( regex.init( "", "^.$", "" ).isOK() ); - ASSERT( regex.matchesSingleElement( multiByteCharacter.firstElement() ) ); - } - - TEST( RegexMatchExpression, MatchesScalar ) { - RegexMatchExpression regex; - ASSERT( regex.init( "a", "b", "" ).isOK() ); - ASSERT( regex.matchesBSON( BSON( "a" << "b" ), NULL ) ); - ASSERT( !regex.matchesBSON( BSON( "a" << "c" ), NULL ) ); - } - - TEST( RegexMatchExpression, MatchesArrayValue ) { - RegexMatchExpression regex; - ASSERT( regex.init( "a", "b", "" ).isOK() ); - ASSERT( regex.matchesBSON( BSON( "a" << BSON_ARRAY( "c" << "b" ) ), NULL ) ); - ASSERT( !regex.matchesBSON( BSON( "a" << BSON_ARRAY( "d" << "c" ) ), NULL ) ); - } - - TEST( RegexMatchExpression, MatchesNull ) { - RegexMatchExpression regex; - ASSERT( regex.init( "a", "b", "" ).isOK() ); - ASSERT( !regex.matchesBSON( BSONObj(), NULL ) ); - ASSERT( !regex.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - } - - TEST( RegexMatchExpression, ElemMatchKey ) { - RegexMatchExpression regex; - ASSERT( regex.init( "a", "b", "" ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !regex.matchesBSON( BSON( "a" << "c" ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( regex.matchesBSON( BSON( "a" << "b" ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( regex.matchesBSON( BSON( "a" << BSON_ARRAY( "c" << "b" ) ), &details ) ); - ASSERT( details.hasElemMatchKey() ); - 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; - ASSERT( regex.init( "a", "xyz", "" ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - regex.matchesIndexKey( BSON( "" << "z xyz" ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - regex.matchesIndexKey( BSON( "" << "xy" ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - regex.matchesIndexKey( BSON( "" << BSON_ARRAY( "xyz" ) ), indexSpec ) ); - } - - TEST( RegexMatchExpression, MatchesIndexKeyMissing ) { - RegexMatchExpression regex; - ASSERT( regex.init( "a", "xyz", "" ).isOK() ); - IndexSpec indexSpec( BSON( "b" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - regex.matchesIndexKey( BSON( "" << "z xyz" ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - regex.matchesIndexKey( BSON( "" << "xy" ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - regex.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << "xyz" ) ), indexSpec ) ); - } - - TEST( RegexMatchExpression, MatchesIndexKeyArrayValue ) { - RegexMatchExpression regex; - ASSERT( regex.init( "a", "xyz", "" ).isOK() ); - IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - regex.matchesIndexKey( BSON( "" << "dummygeohash" << "" << "xyz" ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - regex.matchesIndexKey( BSON( "" << "dummygeohash" << "" << "z" ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - regex.matchesIndexKey( BSON( "" << "dummygeohash" << - "" << BSON_ARRAY( "r" << 6 << "xyz" ) ), indexSpec ) ); - } - */ - - TEST( ModMatchExpression, MatchesElement ) { - BSONObj match = BSON( "a" << 1 ); - BSONObj largerMatch = BSON( "a" << 4.0 ); - BSONObj longLongMatch = BSON( "a" << 68719476736LL ); - BSONObj notMatch = BSON( "a" << 6 ); - BSONObj negativeNotMatch = BSON( "a" << -2 ); - ModMatchExpression mod; - ASSERT( mod.init( "", 3, 1 ).isOK() ); - ASSERT( mod.matchesSingleElement( match.firstElement() ) ); - ASSERT( mod.matchesSingleElement( largerMatch.firstElement() ) ); - ASSERT( mod.matchesSingleElement( longLongMatch.firstElement() ) ); - ASSERT( !mod.matchesSingleElement( notMatch.firstElement() ) ); - ASSERT( !mod.matchesSingleElement( negativeNotMatch.firstElement() ) ); - } - - TEST( ModMatchExpression, ZeroDivisor ) { - ModMatchExpression mod; - ASSERT( !mod.init( "", 0, 1 ).isOK() ); - } - - TEST( ModMatchExpression, MatchesScalar ) { - ModMatchExpression mod; - ASSERT( mod.init( "a", 5, 2 ).isOK() ); - ASSERT( mod.matchesBSON( BSON( "a" << 7.0 ), NULL ) ); - ASSERT( !mod.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( ModMatchExpression, MatchesArrayValue ) { - ModMatchExpression mod; - ASSERT( mod.init( "a", 5, 2 ).isOK() ); - ASSERT( mod.matchesBSON( BSON( "a" << BSON_ARRAY( 5 << 12LL ) ), NULL ) ); - ASSERT( !mod.matchesBSON( BSON( "a" << BSON_ARRAY( 6 << 8 ) ), NULL ) ); - } - - TEST( ModMatchExpression, MatchesNull ) { - ModMatchExpression mod; - ASSERT( mod.init( "a", 5, 2 ).isOK() ); - ASSERT( !mod.matchesBSON( BSONObj(), NULL ) ); - ASSERT( !mod.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - } - - TEST( ModMatchExpression, ElemMatchKey ) { - ModMatchExpression mod; - ASSERT( mod.init( "a", 5, 2 ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !mod.matchesBSON( BSON( "a" << 4 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( mod.matchesBSON( BSON( "a" << 2 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( mod.matchesBSON( BSON( "a" << BSON_ARRAY( 1 << 2 << 5 ) ), &details ) ); - 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 ) ); - ModMatchExpression mod; - ASSERT( mod.init( "a", operand[ "$mod" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - BSONObj indexKey = BSON( "" << 1 ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - mod.matchesIndexKey( indexKey, indexSpec ) ); - } - */ - - TEST( ExistsMatchExpression, MatchesElement ) { - BSONObj existsInt = BSON( "a" << 5 ); - BSONObj existsNull = BSON( "a" << BSONNULL ); - BSONObj doesntExist = BSONObj(); - ExistsMatchExpression exists; - ASSERT( exists.init( "" ).isOK() ); - ASSERT( exists.matchesSingleElement( existsInt.firstElement() ) ); - ASSERT( exists.matchesSingleElement( existsNull.firstElement() ) ); - ASSERT( !exists.matchesSingleElement( doesntExist.firstElement() ) ); - } - - TEST( ExistsMatchExpression, MatchesElementExistsTrueValue ) { - BSONObj exists = BSON( "a" << 5 ); - BSONObj missing = BSONObj(); - ExistsMatchExpression existsTrueValue; - ASSERT( existsTrueValue.init( "" ).isOK() ); - ASSERT( existsTrueValue.matchesSingleElement( exists.firstElement() ) ); - ASSERT( !existsTrueValue.matchesSingleElement( missing.firstElement() ) ); - } - - TEST( ExistsMatchExpression, MatchesScalar ) { - ExistsMatchExpression exists; - ASSERT( exists.init( "a" ).isOK() ); - ASSERT( exists.matchesBSON( BSON( "a" << 1 ), NULL ) ); - ASSERT( exists.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( !exists.matchesBSON( BSON( "b" << 1 ), NULL ) ); - } - - TEST( ExistsMatchExpression, MatchesArray ) { - ExistsMatchExpression exists; - ASSERT( exists.init( "a" ).isOK() ); - ASSERT( exists.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 5.5 ) ), NULL ) ); - } - - TEST( ExistsMatchExpression, ElemMatchKey ) { - ExistsMatchExpression exists; - ASSERT( exists.init( "a.b" ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !exists.matchesBSON( BSON( "a" << 1 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( exists.matchesBSON( BSON( "a" << BSON( "b" << 6 ) ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( exists.matchesBSON( BSON( "a" << BSON_ARRAY( 2 << BSON( "b" << 7 ) ) ), &details ) ); - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( "1", details.elemMatchKey() ); - } - - TEST( ExistsMatchExpression, Equivalent ) { - ExistsMatchExpression e1; - ExistsMatchExpression e2; - e1.init( "a" ); - e2.init( "b" ); - - ASSERT( e1.equivalent( &e1 ) ); - ASSERT( !e1.equivalent( &e2 ) ); - } - - /** - TEST( ExistsMatchExpression, MatchesIndexKey ) { - BSONObj operand = BSON( "$exists" << true ); - ExistsMatchExpression exists; - ASSERT( exists.init( "a", operand[ "$exists" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - BSONObj indexKey = BSON( "" << 1 ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - exists.matchesIndexKey( indexKey, indexSpec ) ); - } - */ - - - - TEST( TypeMatchExpression, MatchesElementStringType ) { - BSONObj match = BSON( "a" << "abc" ); - BSONObj notMatch = BSON( "a" << 5 ); - TypeMatchExpression type; - ASSERT( type.init( "", String ).isOK() ); - ASSERT( type.matchesSingleElement( match[ "a" ] ) ); - ASSERT( !type.matchesSingleElement( notMatch[ "a" ] ) ); - } - - TEST( TypeMatchExpression, MatchesElementNullType ) { - BSONObj match = BSON( "a" << BSONNULL ); - BSONObj notMatch = BSON( "a" << "abc" ); - TypeMatchExpression type; - ASSERT( type.init( "", jstNULL ).isOK() ); - ASSERT( type.matchesSingleElement( match[ "a" ] ) ); - ASSERT( !type.matchesSingleElement( notMatch[ "a" ] ) ); - } - - TEST( TypeMatchExpression, InvalidTypeMatchExpressionerand ) { - // If the provided type number is not a valid BSONType, it is not a parse error. The - // operator will simply not match anything. - BSONObj notMatch1 = BSON( "a" << BSONNULL ); - BSONObj notMatch2 = BSON( "a" << "abc" ); - TypeMatchExpression type; - ASSERT( type.init( "", JSTypeMax + 1 ).isOK() ); - ASSERT( !type.matchesSingleElement( notMatch1[ "a" ] ) ); - ASSERT( !type.matchesSingleElement( notMatch2[ "a" ] ) ); - } - - TEST( TypeMatchExpression, MatchesScalar ) { - TypeMatchExpression type; - ASSERT( type.init( "a", Bool ).isOK() ); - ASSERT( type.matchesBSON( BSON( "a" << true ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << 1 ), NULL ) ); - } - - TEST( TypeMatchExpression, MatchesArray ) { - TypeMatchExpression type; - ASSERT( type.init( "a", NumberInt ).isOK() ); - ASSERT( type.matchesBSON( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) ); - ASSERT( type.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << "a" ) ), NULL ) ); - ASSERT( type.matchesBSON( BSON( "a" << BSON_ARRAY( "a" << 4 ) ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << BSON_ARRAY( "a" ) ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 4 ) ) ), NULL ) ); - } - - TEST( TypeMatchExpression, MatchesOuterArray ) { - TypeMatchExpression type; - ASSERT( type.init( "a", Array ).isOK() ); - // The outer array is not matched. - ASSERT( !type.matchesBSON( BSON( "a" << BSONArray() ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << "a" ) ), NULL ) ); - ASSERT( type.matchesBSON( BSON( "a" << BSON_ARRAY( BSONArray() << 2 ) ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << "bar" ), NULL ) ); - } - - TEST( TypeMatchExpression, MatchesObject ) { - TypeMatchExpression type; - ASSERT( type.init( "a", Object ).isOK() ); - ASSERT( type.matchesBSON( BSON( "a" << BSON( "b" << 1 ) ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << 1 ), NULL ) ); - } - - TEST( TypeMatchExpression, MatchesDotNotationFieldObject ) { - TypeMatchExpression type; - ASSERT( type.init( "a.b", Object ).isOK() ); - ASSERT( type.matchesBSON( BSON( "a" << BSON( "b" << BSON( "c" << 1 ) ) ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << BSON( "b" << 1 ) ), NULL ) ); - } - - TEST( TypeMatchExpression, MatchesDotNotationArrayElementArray ) { - TypeMatchExpression type; - ASSERT( type.init( "a.0", Array ).isOK() ); - ASSERT( type.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 1 ) ) ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << BSON_ARRAY( "b" ) ), NULL ) ); - } - - TEST( TypeMatchExpression, MatchesDotNotationArrayElementScalar ) { - TypeMatchExpression type; - ASSERT( type.init( "a.0", String ).isOK() ); - ASSERT( type.matchesBSON( BSON( "a" << BSON_ARRAY( "b" ) ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << BSON_ARRAY( 1 ) ), NULL ) ); - } - - TEST( TypeMatchExpression, MatchesDotNotationArrayElementObject ) { - TypeMatchExpression type; - ASSERT( type.init( "a.0", Object ).isOK() ); - ASSERT( type.matchesBSON( BSON( "a" << BSON_ARRAY( BSON( "b" << 1 ) ) ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << BSON_ARRAY( 1 ) ), NULL ) ); - } - - TEST( TypeMatchExpression, MatchesNull ) { - TypeMatchExpression type; - ASSERT( type.init( "a", jstNULL ).isOK() ); - ASSERT( type.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( !type.matchesBSON( BSON( "a" << 4 ), NULL ) ); - ASSERT( !type.matchesBSON( BSONObj(), NULL ) ); - } - - TEST( TypeMatchExpression, ElemMatchKey ) { - TypeMatchExpression type; - ASSERT( type.init( "a.b", String ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !type.matchesBSON( BSON( "a" << 1 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( type.matchesBSON( BSON( "a" << BSON( "b" << "string" ) ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( type.matchesBSON( BSON( "a" << BSON( "b" << BSON_ARRAY( "string" ) ) ), &details ) ); - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( "0", details.elemMatchKey() ); - ASSERT( type.matchesBSON( BSON( "a" << - BSON_ARRAY( 2 << - BSON( "b" << BSON_ARRAY( "string" ) ) ) ), - &details ) ); - 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 ); - TypeMatchExpression type; - ASSERT( type.init( "a", operand[ "$type" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - BSONObj indexKey = BSON( "" << "q" ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - type.matchesIndexKey( indexKey, indexSpec ) ); - } - */ - - - TEST( InMatchExpression, MatchesElementSingle ) { - BSONArray operand = BSON_ARRAY( 1 ); - BSONObj match = BSON( "a" << 1 ); - BSONObj notMatch = BSON( "a" << 2 ); - InMatchExpression in; - in.getArrayFilterEntries()->addEquality( operand.firstElement() ); - ASSERT( in.matchesSingleElement( match[ "a" ] ) ); - ASSERT( !in.matchesSingleElement( notMatch[ "a" ] ) ); - } - - TEST( InMatchExpression, MatchesEmpty ) { - InMatchExpression in; - in.init( "a" ); - - BSONObj notMatch = BSON( "a" << 2 ); - ASSERT( !in.matchesSingleElement( notMatch[ "a" ] ) ); - ASSERT( !in.matchesBSON( BSON( "a" << 1 ), NULL ) ); - ASSERT( !in.matchesBSON( BSONObj(), NULL ) ); - } - - TEST( InMatchExpression, MatchesElementMultiple ) { - BSONObj operand = BSON_ARRAY( 1 << "r" << true << 1 ); - InMatchExpression in; - in.getArrayFilterEntries()->addEquality( operand[0] ); - in.getArrayFilterEntries()->addEquality( operand[1] ); - in.getArrayFilterEntries()->addEquality( operand[2] ); - in.getArrayFilterEntries()->addEquality( operand[3] ); - - BSONObj matchFirst = BSON( "a" << 1 ); - BSONObj matchSecond = BSON( "a" << "r" ); - BSONObj matchThird = BSON( "a" << true ); - BSONObj notMatch = BSON( "a" << false ); - ASSERT( in.matchesSingleElement( matchFirst[ "a" ] ) ); - ASSERT( in.matchesSingleElement( matchSecond[ "a" ] ) ); - ASSERT( in.matchesSingleElement( matchThird[ "a" ] ) ); - ASSERT( !in.matchesSingleElement( notMatch[ "a" ] ) ); - } - - - TEST( InMatchExpression, MatchesScalar ) { - BSONObj operand = BSON_ARRAY( 5 ); - InMatchExpression in; - in.init( "a" ); - in.getArrayFilterEntries()->addEquality( operand.firstElement() ); - - ASSERT( in.matchesBSON( BSON( "a" << 5.0 ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( InMatchExpression, MatchesArrayValue ) { - BSONObj operand = BSON_ARRAY( 5 ); - InMatchExpression in; - in.init( "a" ); - in.getArrayFilterEntries()->addEquality( operand.firstElement() ); - - ASSERT( in.matchesBSON( BSON( "a" << BSON_ARRAY( 5.0 << 6 ) ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << BSON_ARRAY( 6 << 7 ) ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << BSON_ARRAY( BSON_ARRAY( 5 ) ) ), NULL ) ); - } - - TEST( InMatchExpression, MatchesNull ) { - BSONObj operand = BSON_ARRAY( BSONNULL ); - - InMatchExpression in; - in.init( "a" ); - in.getArrayFilterEntries()->addEquality( operand.firstElement() ); - - ASSERT( in.matchesBSON( BSONObj(), NULL ) ); - ASSERT( in.matchesBSON( BSON( "a" << BSONNULL ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << 4 ), NULL ) ); - // A non-existent field is treated same way as an empty bson object - ASSERT( in.matchesBSON( BSON( "b" << 4 ), NULL ) ); - } - - TEST( InMatchExpression, MatchesUndefined ) { - BSONObj operand = BSON_ARRAY( BSONUndefined ); - - InMatchExpression in; - in.init( "a" ); - Status s = in.getArrayFilterEntries()->addEquality( operand.firstElement() ); - ASSERT_NOT_OK(s); - } - - TEST( InMatchExpression, MatchesMinKey ) { - BSONObj operand = BSON_ARRAY( MinKey ); - InMatchExpression in; - in.init( "a" ); - in.getArrayFilterEntries()->addEquality( operand.firstElement() ); - - ASSERT( in.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( InMatchExpression, MatchesMaxKey ) { - BSONObj operand = BSON_ARRAY( MaxKey ); - InMatchExpression in; - in.init( "a" ); - in.getArrayFilterEntries()->addEquality( operand.firstElement() ); - - ASSERT( in.matchesBSON( BSON( "a" << MaxKey ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << MinKey ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( InMatchExpression, MatchesFullArray ) { - BSONObj operand = BSON_ARRAY( BSON_ARRAY( 1 << 2 ) << 4 << 5 ); - InMatchExpression in; - in.init( "a" ); - in.getArrayFilterEntries()->addEquality( operand[0] ); - in.getArrayFilterEntries()->addEquality( operand[1] ); - in.getArrayFilterEntries()->addEquality( operand[2] ); - - ASSERT( in.matchesBSON( BSON( "a" << BSON_ARRAY( 1 << 2 ) ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << BSON_ARRAY( 1 << 2 << 3 ) ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << BSON_ARRAY( 1 ) ), NULL ) ); - ASSERT( !in.matchesBSON( BSON( "a" << 1 ), NULL ) ); - } - - TEST( InMatchExpression, ElemMatchKey ) { - BSONObj operand = BSON_ARRAY( 5 << 2 ); - InMatchExpression in; - in.init( "a" ); - in.getArrayFilterEntries()->addEquality( operand[0] ); - in.getArrayFilterEntries()->addEquality( operand[1] ); - - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !in.matchesBSON( BSON( "a" << 4 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( in.matchesBSON( BSON( "a" << 5 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( in.matchesBSON( BSON( "a" << BSON_ARRAY( 1 << 2 << 5 ) ), &details ) ); - ASSERT( details.hasElemMatchKey() ); - ASSERT_EQUALS( "1", details.elemMatchKey() ); - } - - /** - TEST( InMatchExpression, MatchesIndexKeyScalar ) { - BSONObj operand = BSON( "$in" << BSON_ARRAY( 6 << 5 ) ); - InMatchExpression in; - ASSERT( in.init( "a", operand[ "$in" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - in.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - in.matchesIndexKey( BSON( "" << 5 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - in.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - in.matchesIndexKey( BSON( "" << BSON_ARRAY( 6 ) ), indexSpec ) ); - } - - TEST( InMatchExpression, MatchesIndexKeyMissing ) { - BSONObj operand = BSON( "$in" << BSON_ARRAY( 6 ) ); - ComparisonMatchExpression eq - ASSERT( eq.init( "a", operand[ "$in" ] ).isOK() ); - IndexSpec indexSpec( BSON( "b" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - eq.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - eq.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - eq.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); - } - - TEST( InMatchExpression, MatchesIndexKeyArray ) { - BSONObj operand = BSON( "$in" << BSON_ARRAY( 4 << BSON_ARRAY( 5 ) ) ); - InMatchExpression in; - ASSERT( in.init( "a", operand[ "$in" ] ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - in.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - in.matchesIndexKey( BSON( "" << 5 ), indexSpec ) ); - } - - TEST( InMatchExpression, MatchesIndexKeyArrayValue ) { - BSONObjBuilder inArray; - inArray.append( "0", 4 ).append( "1", 5 ).appendRegex( "2", "abc", "" ); - BSONObj operand = BSONObjBuilder().appendArray( "$in", inArray.obj() ).obj(); - InMatchExpression in; - ASSERT( in.init( "a", operand[ "$in" ] ).isOK() ); - IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - in.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 4 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - in.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 6 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - in.matchesIndexKey( BSON( "" << "dummygeohash" << "" << "abcd" ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - in.matchesIndexKey( BSONObjBuilder() - .append( "", "dummygeohash" ) - .appendRegex( "", "abc", "" ).obj(), - indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - in.matchesIndexKey( BSON( "" << "dummygeohash" << "" << "ab" ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - in.matchesIndexKey( BSON( "" << "dummygeohash" << - "" << BSON_ARRAY( 8 << 5 ) ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - in.matchesIndexKey( BSON( "" << "dummygeohash" << - "" << BSON_ARRAY( 8 << 9 ) ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - in.matchesIndexKey( BSON( "" << "dummygeohash" << - "" << BSON_ARRAY( 8 << "abc" ) ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - in.matchesIndexKey( BSON( "" << "dummygeohash" << - "" << BSON_ARRAY( 8 << "ac" ) ), indexSpec ) ); - } - */ +using std::string; +TEST(EqOp, MatchesElement) { + BSONObj operand = BSON("a" << 5); + BSONObj match = BSON("a" << 5.0); + BSONObj notMatch = BSON("a" << 6); + + 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; + EqualityMatchExpression eq; + ASSERT(!eq.init("", operand.firstElement()).isOK()); +} + +TEST(EqOp, MatchesScalar) { + BSONObj operand = BSON("a" << 5); + EqualityMatchExpression eq; + eq.init("a", operand["a"]); + ASSERT(eq.matchesBSON(BSON("a" << 5.0), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(EqOp, MatchesArrayValue) { + BSONObj operand = BSON("a" << 5); + EqualityMatchExpression eq; + eq.init("a", operand["a"]); + ASSERT(eq.matchesBSON(BSON("a" << BSON_ARRAY(5.0 << 6)), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << BSON_ARRAY(6 << 7)), NULL)); +} + +TEST(EqOp, MatchesReferencedObjectValue) { + BSONObj operand = BSON("a.b" << 5); + EqualityMatchExpression eq; + eq.init("a.b", operand["a.b"]); + ASSERT(eq.matchesBSON(BSON("a" << BSON("b" << 5)), NULL)); + ASSERT(eq.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY(5))), NULL)); + ASSERT(eq.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 5))), NULL)); +} + +TEST(EqOp, MatchesReferencedArrayValue) { + BSONObj operand = BSON("a.0" << 5); + EqualityMatchExpression eq; + eq.init("a.0", operand["a.0"]); + ASSERT(eq.matchesBSON(BSON("a" << BSON_ARRAY(5)), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(5))), NULL)); +} + +TEST(EqOp, MatchesNull) { + BSONObj operand = BSON("a" << BSONNULL); + EqualityMatchExpression eq; + eq.init("a", operand["a"]); + ASSERT(eq.matchesBSON(BSONObj(), NULL)); + ASSERT(eq.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << 4), NULL)); + // A non-existent field is treated same way as an empty bson object + ASSERT(eq.matchesBSON(BSON("b" << 4), NULL)); +} + +// This test documents how the matcher currently works, +// not necessarily how it should work ideally. +TEST(EqOp, MatchesNestedNull) { + BSONObj operand = BSON("a.b" << BSONNULL); + EqualityMatchExpression eq; + eq.init("a.b", operand["a.b"]); + // null matches any empty object that is on a subpath of a.b + ASSERT(eq.matchesBSON(BSONObj(), NULL)); + ASSERT(eq.matchesBSON(BSON("a" << BSONObj()), NULL)); + ASSERT(eq.matchesBSON(BSON("a" << BSON_ARRAY(BSONObj())), NULL)); + ASSERT(eq.matchesBSON(BSON("a" << BSON("b" << BSONNULL)), NULL)); + // b does not exist as an element in array under a. + ASSERT(!eq.matchesBSON(BSON("a" << BSONArray()), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << BSON_ARRAY(BSONNULL)), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2)), NULL)); + // a.b exists but is not null. + ASSERT(!eq.matchesBSON(BSON("a" << BSON("b" << 4)), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << BSON("b" << BSONObj())), NULL)); + // A non-existent field is treated same way as an empty bson object + ASSERT(eq.matchesBSON(BSON("b" << 4), NULL)); +} + +TEST(EqOp, MatchesMinKey) { + BSONObj operand = BSON("a" << MinKey); + EqualityMatchExpression eq; + eq.init("a", operand["a"]); + ASSERT(eq.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << 4), NULL)); +} + + +TEST(EqOp, MatchesMaxKey) { + BSONObj operand = BSON("a" << MaxKey); + EqualityMatchExpression eq; + ASSERT(eq.init("a", operand["a"]).isOK()); + ASSERT(eq.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(EqOp, MatchesFullArray) { + BSONObj operand = BSON("a" << BSON_ARRAY(1 << 2)); + EqualityMatchExpression eq; + ASSERT(eq.init("a", operand["a"]).isOK()); + ASSERT(eq.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2)), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 3)), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << BSON_ARRAY(1)), NULL)); + ASSERT(!eq.matchesBSON(BSON("a" << 1), NULL)); +} + +TEST(EqOp, MatchesThroughNestedArray) { + BSONObj operand = BSON("a.b.c.d" << 3); + EqualityMatchExpression eq; + eq.init("a.b.c.d", operand["a.b.c.d"]); + BSONObj obj = fromjson("{a:{b:[{c:[{d:1},{d:2}]},{c:[{d:3}]}]}}"); + ASSERT(eq.matchesBSON(obj, NULL)); +} + +TEST(EqOp, ElemMatchKey) { + BSONObj operand = BSON("a" << 5); + EqualityMatchExpression eq; + ASSERT(eq.init("a", operand["a"]).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!eq.matchesBSON(BSON("a" << 4), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(eq.matchesBSON(BSON("a" << 5), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(eq.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 5)), &details)); + ASSERT(details.hasElemMatchKey()); + ASSERT_EQUALS("2", details.elemMatchKey()); +} + +// SERVER-14886: when an array is being traversed explictly at the same time that a nested array +// is being traversed implicitly, the elemMatch key should refer to the offset of the array +// being implicitly traversed. +TEST(EqOp, ElemMatchKeyWithImplicitAndExplicitTraversal) { + BSONObj operand = BSON("a.0.b" << 3); + BSONElement operandFirstElt = operand.firstElement(); + EqualityMatchExpression eq; + ASSERT(eq.init(operandFirstElt.fieldName(), operandFirstElt).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + BSONObj obj = fromjson("{a: [{b: [2, 3]}, {b: [4, 5]}]}"); + ASSERT(eq.matchesBSON(obj, &details)); + ASSERT(details.hasElemMatchKey()); + ASSERT_EQUALS("1", 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 ); + EqualityMatchExpression eq; + ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + eq.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + eq.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + eq.matchesIndexKey( BSON( "" << BSON_ARRAY( 6 ) ), indexSpec ) ); + } + + TEST( EqOp, MatchesIndexKeyMissing ) { + BSONObj operand = BSON( "a" << 6 ); + EqualityMatchExpression eq; + ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); + IndexSpec indexSpec( BSON( "b" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + eq.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + eq.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + eq.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); + } + + TEST( EqOp, MatchesIndexKeyArray ) { + BSONObj operand = BSON( "a" << BSON_ARRAY( 4 << 5 ) ); + ComparisonMatchExpression eq + ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + eq.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); + } + + TEST( EqOp, MatchesIndexKeyArrayValue ) { + BSONObj operand = BSON( "a" << 6 ); + ComparisonMatchExpression eq + ASSERT( eq.init( "a", operand[ "a" ] ).isOK() ); + IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + eq.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + eq.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + eq.matchesIndexKey( BSON( "" << "dummygeohash" << + "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); + } +*/ +TEST(LtOp, MatchesElement) { + BSONObj operand = BSON("$lt" << 5); + BSONObj match = BSON("a" << 4.5); + BSONObj notMatch = BSON("a" << 6); + BSONObj notMatchEqual = BSON("a" << 5); + BSONObj notMatchWrongType = BSON("a" + << "foo"); + LTMatchExpression lt; + ASSERT(lt.init("", operand["$lt"]).isOK()); + ASSERT(lt.matchesSingleElement(match.firstElement())); + ASSERT(!lt.matchesSingleElement(notMatch.firstElement())); + ASSERT(!lt.matchesSingleElement(notMatchEqual.firstElement())); + ASSERT(!lt.matchesSingleElement(notMatchWrongType.firstElement())); +} + +TEST(LtOp, InvalidEooOperand) { + BSONObj operand; + LTMatchExpression lt; + ASSERT(!lt.init("", operand.firstElement()).isOK()); +} + +TEST(LtOp, MatchesScalar) { + BSONObj operand = BSON("$lt" << 5); + LTMatchExpression lt; + ASSERT(lt.init("a", operand["$lt"]).isOK()); + ASSERT(lt.matchesBSON(BSON("a" << 4.5), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << 6), NULL)); +} + +TEST(LtOp, MatchesScalarEmptyKey) { + BSONObj operand = BSON("$lt" << 5); + LTMatchExpression lt; + ASSERT(lt.init("", operand["$lt"]).isOK()); + ASSERT(lt.matchesBSON(BSON("" << 4.5), NULL)); + ASSERT(!lt.matchesBSON(BSON("" << 6), NULL)); +} + +TEST(LtOp, MatchesArrayValue) { + BSONObj operand = BSON("$lt" << 5); + LTMatchExpression lt; + ASSERT(lt.init("a", operand["$lt"]).isOK()); + ASSERT(lt.matchesBSON(BSON("a" << BSON_ARRAY(6 << 4.5)), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSON_ARRAY(6 << 7)), NULL)); +} + +TEST(LtOp, MatchesWholeArray) { + BSONObj operand = BSON("$lt" << BSON_ARRAY(5)); + LTMatchExpression lt; + ASSERT(lt.init("a", operand["$lt"]).isOK()); + ASSERT(lt.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSON_ARRAY(5)), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSON_ARRAY(6)), NULL)); + // Nested array. + ASSERT(lt.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(4))), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(5))), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(6))), NULL)); +} + +TEST(LtOp, MatchesNull) { + BSONObj operand = BSON("$lt" << BSONNULL); + LTMatchExpression lt; + ASSERT(lt.init("a", operand["$lt"]).isOK()); + ASSERT(!lt.matchesBSON(BSONObj(), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << 4), NULL)); + // A non-existent field is treated same way as an empty bson object + ASSERT(!lt.matchesBSON(BSON("b" << 4), NULL)); +} + +TEST(LtOp, MatchesDotNotationNull) { + BSONObj operand = BSON("$lt" << BSONNULL); + LTMatchExpression lt; + ASSERT(lt.init("a.b", operand["$lt"]).isOK()); + ASSERT(!lt.matchesBSON(BSONObj(), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << 4), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSONObj()), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << BSONNULL))), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSON_ARRAY(BSON("a" << 4) << BSON("b" << 4))), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 4))), NULL)); +} + +TEST(LtOp, MatchesMinKey) { + BSONObj operand = BSON("a" << MinKey); + LTMatchExpression lt; + ASSERT(lt.init("a", operand["a"]).isOK()); + ASSERT(!lt.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(!lt.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(LtOp, MatchesMaxKey) { + BSONObj operand = BSON("a" << MaxKey); + LTMatchExpression lt; + ASSERT(lt.init("a", operand["a"]).isOK()); + ASSERT(!lt.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(lt.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(lt.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(LtOp, ElemMatchKey) { + BSONObj operand = BSON("$lt" << 5); + LTMatchExpression lt; + ASSERT(lt.init("a", operand["$lt"]).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!lt.matchesBSON(BSON("a" << 6), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(lt.matchesBSON(BSON("a" << 4), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(lt.matchesBSON(BSON("a" << BSON_ARRAY(6 << 2 << 5)), &details)); + ASSERT(details.hasElemMatchKey()); + ASSERT_EQUALS("1", details.elemMatchKey()); +} + +/** + TEST( LtOp, MatchesIndexKeyScalar ) { + BSONObj operand = BSON( "$lt" << 6 ); + LtOp lt; + ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + lt.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + lt.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + lt.matchesIndexKey( BSON( "" << BSON_ARRAY( 5 ) ), indexSpec ) ); + } + + TEST( LtOp, MatchesIndexKeyMissing ) { + BSONObj operand = BSON( "$lt" << 6 ); + LtOp lt; + ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); + IndexSpec indexSpec( BSON( "b" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + lt.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + lt.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + lt.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); + } + + TEST( LtOp, MatchesIndexKeyArray ) { + BSONObj operand = BSON( "$lt" << BSON_ARRAY( 4 << 5 ) ); + LtOp lt; + ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + lt.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); + } + + TEST( LtOp, MatchesIndexKeyArrayValue ) { + BSONObj operand = BSON( "$lt" << 6 ); + LtOp lt; + ASSERT( lt.init( "a", operand[ "$lt" ] ).isOK() ); + IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + lt.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 3 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + lt.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + lt.matchesIndexKey( BSON( "" << "dummygeohash" << + "" << BSON_ARRAY( 8 << 6 << 4 ) ), indexSpec ) ); + } +*/ +TEST(LteOp, MatchesElement) { + BSONObj operand = BSON("$lte" << 5); + BSONObj match = BSON("a" << 4.5); + BSONObj equalMatch = BSON("a" << 5); + BSONObj notMatch = BSON("a" << 6); + BSONObj notMatchWrongType = BSON("a" + << "foo"); + LTEMatchExpression lte; + ASSERT(lte.init("", operand["$lte"]).isOK()); + ASSERT(lte.matchesSingleElement(match.firstElement())); + ASSERT(lte.matchesSingleElement(equalMatch.firstElement())); + ASSERT(!lte.matchesSingleElement(notMatch.firstElement())); + ASSERT(!lte.matchesSingleElement(notMatchWrongType.firstElement())); +} + +TEST(LteOp, InvalidEooOperand) { + BSONObj operand; + LTEMatchExpression lte; + ASSERT(!lte.init("", operand.firstElement()).isOK()); +} + +TEST(LteOp, MatchesScalar) { + BSONObj operand = BSON("$lte" << 5); + LTEMatchExpression lte; + ASSERT(lte.init("a", operand["$lte"]).isOK()); + ASSERT(lte.matchesBSON(BSON("a" << 4.5), NULL)); + ASSERT(!lte.matchesBSON(BSON("a" << 6), NULL)); +} + +TEST(LteOp, MatchesArrayValue) { + BSONObj operand = BSON("$lte" << 5); + LTEMatchExpression lte; + ASSERT(lte.init("a", operand["$lte"]).isOK()); + ASSERT(lte.matchesBSON(BSON("a" << BSON_ARRAY(6 << 4.5)), NULL)); + ASSERT(!lte.matchesBSON(BSON("a" << BSON_ARRAY(6 << 7)), NULL)); +} + +TEST(LteOp, MatchesWholeArray) { + BSONObj operand = BSON("$lte" << BSON_ARRAY(5)); + LTEMatchExpression lte; + ASSERT(lte.init("a", operand["$lte"]).isOK()); + ASSERT(lte.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL)); + ASSERT(lte.matchesBSON(BSON("a" << BSON_ARRAY(5)), NULL)); + ASSERT(!lte.matchesBSON(BSON("a" << BSON_ARRAY(6)), NULL)); + // Nested array. + ASSERT(lte.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(4))), NULL)); + ASSERT(lte.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(5))), NULL)); + ASSERT(!lte.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(6))), NULL)); +} + +TEST(LteOp, MatchesNull) { + BSONObj operand = BSON("$lte" << BSONNULL); + LTEMatchExpression lte; + ASSERT(lte.init("a", operand["$lte"]).isOK()); + ASSERT(lte.matchesBSON(BSONObj(), NULL)); + ASSERT(lte.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(!lte.matchesBSON(BSON("a" << 4), NULL)); + // A non-existent field is treated same way as an empty bson object + ASSERT(lte.matchesBSON(BSON("b" << 4), NULL)); +} + +TEST(LteOp, MatchesDotNotationNull) { + BSONObj operand = BSON("$lte" << BSONNULL); + LTEMatchExpression lte; + ASSERT(lte.init("a.b", operand["$lte"]).isOK()); + ASSERT(lte.matchesBSON(BSONObj(), NULL)); + ASSERT(lte.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(lte.matchesBSON(BSON("a" << 4), NULL)); + ASSERT(lte.matchesBSON(BSON("a" << BSONObj()), NULL)); + ASSERT(lte.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << BSONNULL))), NULL)); + ASSERT(lte.matchesBSON(BSON("a" << BSON_ARRAY(BSON("a" << 4) << BSON("b" << 4))), NULL)); + ASSERT(!lte.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL)); + ASSERT(!lte.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 4))), NULL)); +} + +TEST(LteOp, MatchesMinKey) { + BSONObj operand = BSON("a" << MinKey); + LTEMatchExpression lte; + ASSERT(lte.init("a", operand["a"]).isOK()); + ASSERT(lte.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(!lte.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(!lte.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(LteOp, MatchesMaxKey) { + BSONObj operand = BSON("a" << MaxKey); + LTEMatchExpression lte; + ASSERT(lte.init("a", operand["a"]).isOK()); + ASSERT(lte.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(lte.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(lte.matchesBSON(BSON("a" << 4), NULL)); +} + + +TEST(LteOp, ElemMatchKey) { + BSONObj operand = BSON("$lte" << 5); + LTEMatchExpression lte; + ASSERT(lte.init("a", operand["$lte"]).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!lte.matchesBSON(BSON("a" << 6), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(lte.matchesBSON(BSON("a" << 4), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(lte.matchesBSON(BSON("a" << BSON_ARRAY(6 << 2 << 5)), &details)); + ASSERT(details.hasElemMatchKey()); + ASSERT_EQUALS("1", details.elemMatchKey()); +} + +/** + TEST( LteOp, MatchesIndexKeyScalar ) { + BSONObj operand = BSON( "$lte" << 6 ); + LteOp lte; + ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + lte.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + lte.matchesIndexKey( BSON( "" << 7 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + lte.matchesIndexKey( BSON( "" << BSON_ARRAY( 5 ) ), indexSpec ) ); + } + + TEST( LteOp, MatchesIndexKeyMissing ) { + BSONObj operand = BSON( "$lte" << 6 ); + LteOp lte; + ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); + IndexSpec indexSpec( BSON( "b" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + lte.matchesIndexKey( BSON( "" << 7 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + lte.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + lte.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); + } + + TEST( LteOp, MatchesIndexKeyArray ) { + BSONObj operand = BSON( "$lte" << BSON_ARRAY( 4 << 5 ) ); + LteOp lte; + ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + lte.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); + } + + TEST( LteOp, MatchesIndexKeyArrayValue ) { + BSONObj operand = BSON( "$lte" << 6 ); + LteOp lte; + ASSERT( lte.init( "a", operand[ "$lte" ] ).isOK() ); + IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + lte.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 3 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + lte.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 7 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + lte.matchesIndexKey( BSON( "" << "dummygeohash" << + "" << BSON_ARRAY( 8 << 6 << 4 ) ), indexSpec ) ); + } + + TEST( GtOp, MatchesElement ) { + BSONObj operand = BSON( "$gt" << 5 ); + BSONObj match = BSON( "a" << 5.5 ); + BSONObj notMatch = BSON( "a" << 4 ); + BSONObj notMatchEqual = BSON( "a" << 5 ); + BSONObj notMatchWrongType = BSON( "a" << "foo" ); + GtOp gt; + ASSERT( gt.init( "", operand[ "$gt" ] ).isOK() ); + ASSERT( gt.matchesSingleElement( match.firstElement() ) ); + ASSERT( !gt.matchesSingleElement( notMatch.firstElement() ) ); + ASSERT( !gt.matchesSingleElement( notMatchEqual.firstElement() ) ); + ASSERT( !gt.matchesSingleElement( notMatchWrongType.firstElement() ) ); + } +*/ + +TEST(GtOp, InvalidEooOperand) { + BSONObj operand; + GTMatchExpression gt; + ASSERT(!gt.init("", operand.firstElement()).isOK()); +} + +TEST(GtOp, MatchesScalar) { + BSONObj operand = BSON("$gt" << 5); + GTMatchExpression gt; + ASSERT(gt.init("a", operand["$gt"]).isOK()); + ASSERT(gt.matchesBSON(BSON("a" << 5.5), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(GtOp, MatchesArrayValue) { + BSONObj operand = BSON("$gt" << 5); + GTMatchExpression gt; + ASSERT(gt.init("a", operand["$gt"]).isOK()); + ASSERT(gt.matchesBSON(BSON("a" << BSON_ARRAY(3 << 5.5)), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << BSON_ARRAY(2 << 4)), NULL)); +} + +TEST(GtOp, MatchesWholeArray) { + BSONObj operand = BSON("$gt" << BSON_ARRAY(5)); + GTMatchExpression gt; + ASSERT(gt.init("a", operand["$gt"]).isOK()); + ASSERT(!gt.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << BSON_ARRAY(5)), NULL)); + ASSERT(gt.matchesBSON(BSON("a" << BSON_ARRAY(6)), NULL)); + // Nested array. + // XXX: The following assertion documents current behavior. + ASSERT(gt.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(4))), NULL)); + // XXX: The following assertion documents current behavior. + ASSERT(gt.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(5))), NULL)); + ASSERT(gt.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(6))), NULL)); +} + +TEST(GtOp, MatchesNull) { + BSONObj operand = BSON("$gt" << BSONNULL); + GTMatchExpression gt; + ASSERT(gt.init("a", operand["$gt"]).isOK()); + ASSERT(!gt.matchesBSON(BSONObj(), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << 4), NULL)); + // A non-existent field is treated same way as an empty bson object + ASSERT(!gt.matchesBSON(BSON("b" << 4), NULL)); +} + +TEST(GtOp, MatchesDotNotationNull) { + BSONObj operand = BSON("$gt" << BSONNULL); + GTMatchExpression gt; + ASSERT(gt.init("a.b", operand["$gt"]).isOK()); + ASSERT(!gt.matchesBSON(BSONObj(), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << 4), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << BSONObj()), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << BSONNULL))), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << BSON_ARRAY(BSON("a" << 4) << BSON("b" << 4))), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 4))), NULL)); +} + +TEST(GtOp, MatchesMinKey) { + BSONObj operand = BSON("a" << MinKey); + GTMatchExpression gt; + ASSERT(gt.init("a", operand["a"]).isOK()); + ASSERT(!gt.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(gt.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(gt.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(GtOp, MatchesMaxKey) { + BSONObj operand = BSON("a" << MaxKey); + GTMatchExpression gt; + ASSERT(gt.init("a", operand["a"]).isOK()); + ASSERT(!gt.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(!gt.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(GtOp, ElemMatchKey) { + BSONObj operand = BSON("$gt" << 5); + GTMatchExpression gt; + ASSERT(gt.init("a", operand["$gt"]).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!gt.matchesBSON(BSON("a" << 4), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(gt.matchesBSON(BSON("a" << 6), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(gt.matchesBSON(BSON("a" << BSON_ARRAY(2 << 6 << 5)), &details)); + ASSERT(details.hasElemMatchKey()); + ASSERT_EQUALS("1", details.elemMatchKey()); +} + +/** + TEST( GtOp, MatchesIndexKeyScalar ) { + BSONObj operand = BSON( "$gt" << 6 ); + GtOp gt; + ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + gt.matchesIndexKey( BSON( "" << 7 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + gt.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + gt.matchesIndexKey( BSON( "" << BSON_ARRAY( 9 ) ), indexSpec ) ); + } + + TEST( GtOp, MatchesIndexKeyMissing ) { + BSONObj operand = BSON( "$gt" << 6 ); + GtOp gt; + ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); + IndexSpec indexSpec( BSON( "b" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + gt.matchesIndexKey( BSON( "" << 7 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + gt.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + gt.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); + } + + TEST( GtOp, MatchesIndexKeyArray ) { + BSONObj operand = BSON( "$gt" << BSON_ARRAY( 4 << 5 ) ); + GtOp gt; + ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + gt.matchesIndexKey( BSON( "" << 8 ), indexSpec ) ); + } + + TEST( GtOp, MatchesIndexKeyArrayValue ) { + BSONObj operand = BSON( "$gt" << 6 ); + GtOp gt; + ASSERT( gt.init( "a", operand[ "$gt" ] ).isOK() ); + IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + gt.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 7 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + gt.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 3 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + gt.matchesIndexKey( BSON( "" << "dummygeohash" << + "" << BSON_ARRAY( 8 << 6 << 4 ) ), indexSpec ) ); + } +*/ + +TEST(ComparisonMatchExpression, MatchesElement) { + BSONObj operand = BSON("$gte" << 5); + BSONObj match = BSON("a" << 5.5); + BSONObj equalMatch = BSON("a" << 5); + BSONObj notMatch = BSON("a" << 4); + BSONObj notMatchWrongType = BSON("a" + << "foo"); + GTEMatchExpression gte; + ASSERT(gte.init("", operand["$gte"]).isOK()); + ASSERT(gte.matchesSingleElement(match.firstElement())); + ASSERT(gte.matchesSingleElement(equalMatch.firstElement())); + ASSERT(!gte.matchesSingleElement(notMatch.firstElement())); + ASSERT(!gte.matchesSingleElement(notMatchWrongType.firstElement())); +} + +TEST(ComparisonMatchExpression, InvalidEooOperand) { + BSONObj operand; + GTEMatchExpression gte; + ASSERT(!gte.init("", operand.firstElement()).isOK()); +} + +TEST(ComparisonMatchExpression, MatchesScalar) { + BSONObj operand = BSON("$gte" << 5); + GTEMatchExpression gte; + ASSERT(gte.init("a", operand["$gte"]).isOK()); + ASSERT(gte.matchesBSON(BSON("a" << 5.5), NULL)); + ASSERT(!gte.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(ComparisonMatchExpression, MatchesArrayValue) { + BSONObj operand = BSON("$gte" << 5); + GTEMatchExpression gte; + ASSERT(gte.init("a", operand["$gte"]).isOK()); + ASSERT(gte.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5.5)), NULL)); + ASSERT(!gte.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2)), NULL)); +} + +TEST(ComparisonMatchExpression, MatchesWholeArray) { + BSONObj operand = BSON("$gte" << BSON_ARRAY(5)); + GTEMatchExpression gte; + ASSERT(gte.init("a", operand["$gte"]).isOK()); + ASSERT(!gte.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << BSON_ARRAY(5)), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << BSON_ARRAY(6)), NULL)); + // Nested array. + // XXX: The following assertion documents current behavior. + ASSERT(gte.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(4))), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(5))), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(6))), NULL)); +} + +TEST(ComparisonMatchExpression, MatchesNull) { + BSONObj operand = BSON("$gte" << BSONNULL); + GTEMatchExpression gte; + ASSERT(gte.init("a", operand["$gte"]).isOK()); + ASSERT(gte.matchesBSON(BSONObj(), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(!gte.matchesBSON(BSON("a" << 4), NULL)); + // A non-existent field is treated same way as an empty bson object + ASSERT(gte.matchesBSON(BSON("b" << 4), NULL)); +} + +TEST(ComparisonMatchExpression, MatchesDotNotationNull) { + BSONObj operand = BSON("$gte" << BSONNULL); + GTEMatchExpression gte; + ASSERT(gte.init("a.b", operand["$gte"]).isOK()); + ASSERT(gte.matchesBSON(BSONObj(), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << 4), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << BSONObj()), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << BSONNULL))), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << BSON_ARRAY(BSON("a" << 4) << BSON("b" << 4))), NULL)); + ASSERT(!gte.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL)); + ASSERT(!gte.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 4))), NULL)); +} + +TEST(ComparisonMatchExpression, MatchesMinKey) { + BSONObj operand = BSON("a" << MinKey); + GTEMatchExpression gte; + ASSERT(gte.init("a", operand["a"]).isOK()); + ASSERT(gte.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(gte.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(ComparisonMatchExpression, MatchesMaxKey) { + BSONObj operand = BSON("a" << MaxKey); + GTEMatchExpression gte; + ASSERT(gte.init("a", operand["a"]).isOK()); + ASSERT(gte.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(!gte.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(!gte.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(ComparisonMatchExpression, ElemMatchKey) { + BSONObj operand = BSON("$gte" << 5); + GTEMatchExpression gte; + ASSERT(gte.init("a", operand["$gte"]).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!gte.matchesBSON(BSON("a" << 4), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(gte.matchesBSON(BSON("a" << 6), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(gte.matchesBSON(BSON("a" << BSON_ARRAY(2 << 6 << 5)), &details)); + ASSERT(details.hasElemMatchKey()); + ASSERT_EQUALS("1", details.elemMatchKey()); +} + +/** + TEST( GteOp, MatchesIndexKeyScalar ) { + BSONObj operand = BSON( "$gte" << 6 ); + GteOp gte; + ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + gte.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + gte.matchesIndexKey( BSON( "" << 5 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + gte.matchesIndexKey( BSON( "" << BSON_ARRAY( 7 ) ), indexSpec ) ); + } + + TEST( GteOp, MatchesIndexKeyMissing ) { + BSONObj operand = BSON( "$gte" << 6 ); + GteOp gte; + ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); + IndexSpec indexSpec( BSON( "b" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + gte.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + gte.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + gte.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); + } + + TEST( GteOp, MatchesIndexKeyArray ) { + BSONObj operand = BSON( "$gte" << BSON_ARRAY( 4 << 5 ) ); + GteOp gte; + ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + gte.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + } + + TEST( GteOp, MatchesIndexKeyArrayValue ) { + BSONObj operand = BSON( "$gte" << 6 ); + GteOp gte; + ASSERT( gte.init( "a", operand[ "$gte" ] ).isOK() ); + IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + gte.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + gte.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 3 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + gte.matchesIndexKey( BSON( "" << "dummygeohash" << + "" << BSON_ARRAY( 8 << 6 << 4 ) ), indexSpec ) ); + } +*/ + +TEST(RegexMatchExpression, MatchesElementExact) { + BSONObj match = BSON("a" + << "b"); + BSONObj notMatch = BSON("a" + << "c"); + RegexMatchExpression regex; + ASSERT(regex.init("", "b", "").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, TooLargePattern) { + string tooLargePattern(50 * 1000, 'z'); + RegexMatchExpression regex; + ASSERT(!regex.init("a", tooLargePattern, "").isOK()); +} + +TEST(RegexMatchExpression, MatchesElementSimplePrefix) { + BSONObj match = BSON("x" + << "abc"); + BSONObj notMatch = BSON("x" + << "adz"); + RegexMatchExpression regex; + ASSERT(regex.init("", "^ab", "").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementCaseSensitive) { + BSONObj match = BSON("x" + << "abc"); + BSONObj notMatch = BSON("x" + << "ABC"); + RegexMatchExpression regex; + ASSERT(regex.init("", "abc", "").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementCaseInsensitive) { + BSONObj match = BSON("x" + << "abc"); + BSONObj matchUppercase = BSON("x" + << "ABC"); + BSONObj notMatch = BSON("x" + << "abz"); + RegexMatchExpression regex; + ASSERT(regex.init("", "abc", "i").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(regex.matchesSingleElement(matchUppercase.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementMultilineOff) { + BSONObj match = BSON("x" + << "az"); + BSONObj notMatch = BSON("x" + << "\naz"); + RegexMatchExpression regex; + ASSERT(regex.init("", "^a", "").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementMultilineOn) { + BSONObj match = BSON("x" + << "az"); + BSONObj matchMultiline = BSON("x" + << "\naz"); + BSONObj notMatch = BSON("x" + << "\n\n"); + RegexMatchExpression regex; + ASSERT(regex.init("", "^a", "m").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(regex.matchesSingleElement(matchMultiline.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementExtendedOff) { + BSONObj match = BSON("x" + << "a b"); + BSONObj notMatch = BSON("x" + << "ab"); + RegexMatchExpression regex; + ASSERT(regex.init("", "a b", "").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementExtendedOn) { + BSONObj match = BSON("x" + << "ab"); + BSONObj notMatch = BSON("x" + << "a b"); + RegexMatchExpression regex; + ASSERT(regex.init("", "a b", "x").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementDotAllOff) { + BSONObj match = BSON("x" + << "a b"); + BSONObj notMatch = BSON("x" + << "a\nb"); + RegexMatchExpression regex; + ASSERT(regex.init("", "a.b", "").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementDotAllOn) { + BSONObj match = BSON("x" + << "a b"); + BSONObj matchDotAll = BSON("x" + << "a\nb"); + BSONObj notMatch = BSON("x" + << "ab"); + RegexMatchExpression regex; + ASSERT(regex.init("", "a.b", "s").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(regex.matchesSingleElement(matchDotAll.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementMultipleFlags) { + BSONObj matchMultilineDotAll = BSON("x" + << "\na\nb"); + RegexMatchExpression regex; + ASSERT(regex.init("", "^a.b", "ms").isOK()); + ASSERT(regex.matchesSingleElement(matchMultilineDotAll.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementRegexType) { + BSONObj match = BSONObjBuilder().appendRegex("x", "yz", "i").obj(); + BSONObj notMatchPattern = BSONObjBuilder().appendRegex("x", "r", "i").obj(); + BSONObj notMatchFlags = BSONObjBuilder().appendRegex("x", "yz", "s").obj(); + RegexMatchExpression regex; + ASSERT(regex.init("", "yz", "i").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatchPattern.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatchFlags.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementSymbolType) { + BSONObj match = BSONObjBuilder().appendSymbol("x", "yz").obj(); + BSONObj notMatch = BSONObjBuilder().appendSymbol("x", "gg").obj(); + RegexMatchExpression regex; + ASSERT(regex.init("", "yz", "").isOK()); + ASSERT(regex.matchesSingleElement(match.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatch.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementWrongType) { + BSONObj notMatchInt = BSON("x" << 1); + BSONObj notMatchBool = BSON("x" << true); + RegexMatchExpression regex; + ASSERT(regex.init("", "1", "").isOK()); + ASSERT(!regex.matchesSingleElement(notMatchInt.firstElement())); + ASSERT(!regex.matchesSingleElement(notMatchBool.firstElement())); +} + +TEST(RegexMatchExpression, MatchesElementUtf8) { + BSONObj multiByteCharacter = BSON("x" + << "\xc2\xa5"); + RegexMatchExpression regex; + ASSERT(regex.init("", "^.$", "").isOK()); + ASSERT(regex.matchesSingleElement(multiByteCharacter.firstElement())); +} + +TEST(RegexMatchExpression, MatchesScalar) { + RegexMatchExpression regex; + ASSERT(regex.init("a", "b", "").isOK()); + ASSERT(regex.matchesBSON(BSON("a" + << "b"), + NULL)); + ASSERT(!regex.matchesBSON(BSON("a" + << "c"), + NULL)); +} + +TEST(RegexMatchExpression, MatchesArrayValue) { + RegexMatchExpression regex; + ASSERT(regex.init("a", "b", "").isOK()); + ASSERT(regex.matchesBSON(BSON("a" << BSON_ARRAY("c" + << "b")), + NULL)); + ASSERT(!regex.matchesBSON(BSON("a" << BSON_ARRAY("d" + << "c")), + NULL)); +} + +TEST(RegexMatchExpression, MatchesNull) { + RegexMatchExpression regex; + ASSERT(regex.init("a", "b", "").isOK()); + ASSERT(!regex.matchesBSON(BSONObj(), NULL)); + ASSERT(!regex.matchesBSON(BSON("a" << BSONNULL), NULL)); +} + +TEST(RegexMatchExpression, ElemMatchKey) { + RegexMatchExpression regex; + ASSERT(regex.init("a", "b", "").isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!regex.matchesBSON(BSON("a" + << "c"), + &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(regex.matchesBSON(BSON("a" + << "b"), + &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(regex.matchesBSON(BSON("a" << BSON_ARRAY("c" + << "b")), + &details)); + ASSERT(details.hasElemMatchKey()); + 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; + ASSERT( regex.init( "a", "xyz", "" ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + regex.matchesIndexKey( BSON( "" << "z xyz" ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + regex.matchesIndexKey( BSON( "" << "xy" ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + regex.matchesIndexKey( BSON( "" << BSON_ARRAY( "xyz" ) ), indexSpec ) ); + } + + TEST( RegexMatchExpression, MatchesIndexKeyMissing ) { + RegexMatchExpression regex; + ASSERT( regex.init( "a", "xyz", "" ).isOK() ); + IndexSpec indexSpec( BSON( "b" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + regex.matchesIndexKey( BSON( "" << "z xyz" ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + regex.matchesIndexKey( BSON( "" << "xy" ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + regex.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << "xyz" ) ), indexSpec ) ); + } + + TEST( RegexMatchExpression, MatchesIndexKeyArrayValue ) { + RegexMatchExpression regex; + ASSERT( regex.init( "a", "xyz", "" ).isOK() ); + IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + regex.matchesIndexKey( BSON( "" << "dummygeohash" << "" << "xyz" ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + regex.matchesIndexKey( BSON( "" << "dummygeohash" << "" << "z" ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + regex.matchesIndexKey( BSON( "" << "dummygeohash" << + "" << BSON_ARRAY( "r" << 6 << "xyz" ) ), indexSpec ) ); + } +*/ + +TEST(ModMatchExpression, MatchesElement) { + BSONObj match = BSON("a" << 1); + BSONObj largerMatch = BSON("a" << 4.0); + BSONObj longLongMatch = BSON("a" << 68719476736LL); + BSONObj notMatch = BSON("a" << 6); + BSONObj negativeNotMatch = BSON("a" << -2); + ModMatchExpression mod; + ASSERT(mod.init("", 3, 1).isOK()); + ASSERT(mod.matchesSingleElement(match.firstElement())); + ASSERT(mod.matchesSingleElement(largerMatch.firstElement())); + ASSERT(mod.matchesSingleElement(longLongMatch.firstElement())); + ASSERT(!mod.matchesSingleElement(notMatch.firstElement())); + ASSERT(!mod.matchesSingleElement(negativeNotMatch.firstElement())); +} + +TEST(ModMatchExpression, ZeroDivisor) { + ModMatchExpression mod; + ASSERT(!mod.init("", 0, 1).isOK()); +} + +TEST(ModMatchExpression, MatchesScalar) { + ModMatchExpression mod; + ASSERT(mod.init("a", 5, 2).isOK()); + ASSERT(mod.matchesBSON(BSON("a" << 7.0), NULL)); + ASSERT(!mod.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(ModMatchExpression, MatchesArrayValue) { + ModMatchExpression mod; + ASSERT(mod.init("a", 5, 2).isOK()); + ASSERT(mod.matchesBSON(BSON("a" << BSON_ARRAY(5 << 12LL)), NULL)); + ASSERT(!mod.matchesBSON(BSON("a" << BSON_ARRAY(6 << 8)), NULL)); +} + +TEST(ModMatchExpression, MatchesNull) { + ModMatchExpression mod; + ASSERT(mod.init("a", 5, 2).isOK()); + ASSERT(!mod.matchesBSON(BSONObj(), NULL)); + ASSERT(!mod.matchesBSON(BSON("a" << BSONNULL), NULL)); +} + +TEST(ModMatchExpression, ElemMatchKey) { + ModMatchExpression mod; + ASSERT(mod.init("a", 5, 2).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!mod.matchesBSON(BSON("a" << 4), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(mod.matchesBSON(BSON("a" << 2), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(mod.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 5)), &details)); + 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 ) ); + ModMatchExpression mod; + ASSERT( mod.init( "a", operand[ "$mod" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + BSONObj indexKey = BSON( "" << 1 ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + mod.matchesIndexKey( indexKey, indexSpec ) ); + } +*/ + +TEST(ExistsMatchExpression, MatchesElement) { + BSONObj existsInt = BSON("a" << 5); + BSONObj existsNull = BSON("a" << BSONNULL); + BSONObj doesntExist = BSONObj(); + ExistsMatchExpression exists; + ASSERT(exists.init("").isOK()); + ASSERT(exists.matchesSingleElement(existsInt.firstElement())); + ASSERT(exists.matchesSingleElement(existsNull.firstElement())); + ASSERT(!exists.matchesSingleElement(doesntExist.firstElement())); +} + +TEST(ExistsMatchExpression, MatchesElementExistsTrueValue) { + BSONObj exists = BSON("a" << 5); + BSONObj missing = BSONObj(); + ExistsMatchExpression existsTrueValue; + ASSERT(existsTrueValue.init("").isOK()); + ASSERT(existsTrueValue.matchesSingleElement(exists.firstElement())); + ASSERT(!existsTrueValue.matchesSingleElement(missing.firstElement())); +} + +TEST(ExistsMatchExpression, MatchesScalar) { + ExistsMatchExpression exists; + ASSERT(exists.init("a").isOK()); + ASSERT(exists.matchesBSON(BSON("a" << 1), NULL)); + ASSERT(exists.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(!exists.matchesBSON(BSON("b" << 1), NULL)); +} + +TEST(ExistsMatchExpression, MatchesArray) { + ExistsMatchExpression exists; + ASSERT(exists.init("a").isOK()); + ASSERT(exists.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5.5)), NULL)); +} + +TEST(ExistsMatchExpression, ElemMatchKey) { + ExistsMatchExpression exists; + ASSERT(exists.init("a.b").isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!exists.matchesBSON(BSON("a" << 1), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(exists.matchesBSON(BSON("a" << BSON("b" << 6)), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(exists.matchesBSON(BSON("a" << BSON_ARRAY(2 << BSON("b" << 7))), &details)); + ASSERT(details.hasElemMatchKey()); + ASSERT_EQUALS("1", details.elemMatchKey()); +} + +TEST(ExistsMatchExpression, Equivalent) { + ExistsMatchExpression e1; + ExistsMatchExpression e2; + e1.init("a"); + e2.init("b"); + + ASSERT(e1.equivalent(&e1)); + ASSERT(!e1.equivalent(&e2)); +} + +/** + TEST( ExistsMatchExpression, MatchesIndexKey ) { + BSONObj operand = BSON( "$exists" << true ); + ExistsMatchExpression exists; + ASSERT( exists.init( "a", operand[ "$exists" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + BSONObj indexKey = BSON( "" << 1 ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + exists.matchesIndexKey( indexKey, indexSpec ) ); + } +*/ + + +TEST(TypeMatchExpression, MatchesElementStringType) { + BSONObj match = BSON("a" + << "abc"); + BSONObj notMatch = BSON("a" << 5); + TypeMatchExpression type; + ASSERT(type.init("", String).isOK()); + ASSERT(type.matchesSingleElement(match["a"])); + ASSERT(!type.matchesSingleElement(notMatch["a"])); +} + +TEST(TypeMatchExpression, MatchesElementNullType) { + BSONObj match = BSON("a" << BSONNULL); + BSONObj notMatch = BSON("a" + << "abc"); + TypeMatchExpression type; + ASSERT(type.init("", jstNULL).isOK()); + ASSERT(type.matchesSingleElement(match["a"])); + ASSERT(!type.matchesSingleElement(notMatch["a"])); +} + +TEST(TypeMatchExpression, InvalidTypeMatchExpressionerand) { + // If the provided type number is not a valid BSONType, it is not a parse error. The + // operator will simply not match anything. + BSONObj notMatch1 = BSON("a" << BSONNULL); + BSONObj notMatch2 = BSON("a" + << "abc"); + TypeMatchExpression type; + ASSERT(type.init("", JSTypeMax + 1).isOK()); + ASSERT(!type.matchesSingleElement(notMatch1["a"])); + ASSERT(!type.matchesSingleElement(notMatch2["a"])); +} + +TEST(TypeMatchExpression, MatchesScalar) { + TypeMatchExpression type; + ASSERT(type.init("a", Bool).isOK()); + ASSERT(type.matchesBSON(BSON("a" << true), NULL)); + ASSERT(!type.matchesBSON(BSON("a" << 1), NULL)); +} + +TEST(TypeMatchExpression, MatchesArray) { + TypeMatchExpression type; + ASSERT(type.init("a", NumberInt).isOK()); + ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL)); + ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY(4 << "a")), NULL)); + ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY("a" << 4)), NULL)); + ASSERT(!type.matchesBSON(BSON("a" << BSON_ARRAY("a")), NULL)); + ASSERT(!type.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(4))), NULL)); +} + +TEST(TypeMatchExpression, MatchesOuterArray) { + TypeMatchExpression type; + ASSERT(type.init("a", Array).isOK()); + // The outer array is not matched. + ASSERT(!type.matchesBSON(BSON("a" << BSONArray()), NULL)); + ASSERT(!type.matchesBSON(BSON("a" << BSON_ARRAY(4 << "a")), NULL)); + ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY(BSONArray() << 2)), NULL)); + ASSERT(!type.matchesBSON(BSON("a" + << "bar"), + NULL)); +} + +TEST(TypeMatchExpression, MatchesObject) { + TypeMatchExpression type; + ASSERT(type.init("a", Object).isOK()); + ASSERT(type.matchesBSON(BSON("a" << BSON("b" << 1)), NULL)); + ASSERT(!type.matchesBSON(BSON("a" << 1), NULL)); +} + +TEST(TypeMatchExpression, MatchesDotNotationFieldObject) { + TypeMatchExpression type; + ASSERT(type.init("a.b", Object).isOK()); + ASSERT(type.matchesBSON(BSON("a" << BSON("b" << BSON("c" << 1))), NULL)); + ASSERT(!type.matchesBSON(BSON("a" << BSON("b" << 1)), NULL)); +} + +TEST(TypeMatchExpression, MatchesDotNotationArrayElementArray) { + TypeMatchExpression type; + ASSERT(type.init("a.0", Array).isOK()); + ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(1))), NULL)); + ASSERT(!type.matchesBSON(BSON("a" << BSON_ARRAY("b")), NULL)); +} + +TEST(TypeMatchExpression, MatchesDotNotationArrayElementScalar) { + TypeMatchExpression type; + ASSERT(type.init("a.0", String).isOK()); + ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY("b")), NULL)); + ASSERT(!type.matchesBSON(BSON("a" << BSON_ARRAY(1)), NULL)); +} + +TEST(TypeMatchExpression, MatchesDotNotationArrayElementObject) { + TypeMatchExpression type; + ASSERT(type.init("a.0", Object).isOK()); + ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY(BSON("b" << 1))), NULL)); + ASSERT(!type.matchesBSON(BSON("a" << BSON_ARRAY(1)), NULL)); +} + +TEST(TypeMatchExpression, MatchesNull) { + TypeMatchExpression type; + ASSERT(type.init("a", jstNULL).isOK()); + ASSERT(type.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(!type.matchesBSON(BSON("a" << 4), NULL)); + ASSERT(!type.matchesBSON(BSONObj(), NULL)); +} + +TEST(TypeMatchExpression, ElemMatchKey) { + TypeMatchExpression type; + ASSERT(type.init("a.b", String).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!type.matchesBSON(BSON("a" << 1), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(type.matchesBSON(BSON("a" << BSON("b" + << "string")), + &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(type.matchesBSON(BSON("a" << BSON("b" << BSON_ARRAY("string"))), &details)); + ASSERT(details.hasElemMatchKey()); + ASSERT_EQUALS("0", details.elemMatchKey()); + ASSERT(type.matchesBSON(BSON("a" << BSON_ARRAY(2 << BSON("b" << BSON_ARRAY("string")))), + &details)); + 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 ); + TypeMatchExpression type; + ASSERT( type.init( "a", operand[ "$type" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + BSONObj indexKey = BSON( "" << "q" ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + type.matchesIndexKey( indexKey, indexSpec ) ); + } +*/ + + +TEST(InMatchExpression, MatchesElementSingle) { + BSONArray operand = BSON_ARRAY(1); + BSONObj match = BSON("a" << 1); + BSONObj notMatch = BSON("a" << 2); + InMatchExpression in; + in.getArrayFilterEntries()->addEquality(operand.firstElement()); + ASSERT(in.matchesSingleElement(match["a"])); + ASSERT(!in.matchesSingleElement(notMatch["a"])); +} + +TEST(InMatchExpression, MatchesEmpty) { + InMatchExpression in; + in.init("a"); + + BSONObj notMatch = BSON("a" << 2); + ASSERT(!in.matchesSingleElement(notMatch["a"])); + ASSERT(!in.matchesBSON(BSON("a" << 1), NULL)); + ASSERT(!in.matchesBSON(BSONObj(), NULL)); +} + +TEST(InMatchExpression, MatchesElementMultiple) { + BSONObj operand = BSON_ARRAY(1 << "r" << true << 1); + InMatchExpression in; + in.getArrayFilterEntries()->addEquality(operand[0]); + in.getArrayFilterEntries()->addEquality(operand[1]); + in.getArrayFilterEntries()->addEquality(operand[2]); + in.getArrayFilterEntries()->addEquality(operand[3]); + + BSONObj matchFirst = BSON("a" << 1); + BSONObj matchSecond = BSON("a" + << "r"); + BSONObj matchThird = BSON("a" << true); + BSONObj notMatch = BSON("a" << false); + ASSERT(in.matchesSingleElement(matchFirst["a"])); + ASSERT(in.matchesSingleElement(matchSecond["a"])); + ASSERT(in.matchesSingleElement(matchThird["a"])); + ASSERT(!in.matchesSingleElement(notMatch["a"])); +} + + +TEST(InMatchExpression, MatchesScalar) { + BSONObj operand = BSON_ARRAY(5); + InMatchExpression in; + in.init("a"); + in.getArrayFilterEntries()->addEquality(operand.firstElement()); + + ASSERT(in.matchesBSON(BSON("a" << 5.0), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(InMatchExpression, MatchesArrayValue) { + BSONObj operand = BSON_ARRAY(5); + InMatchExpression in; + in.init("a"); + in.getArrayFilterEntries()->addEquality(operand.firstElement()); + + ASSERT(in.matchesBSON(BSON("a" << BSON_ARRAY(5.0 << 6)), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << BSON_ARRAY(6 << 7)), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << BSON_ARRAY(BSON_ARRAY(5))), NULL)); +} + +TEST(InMatchExpression, MatchesNull) { + BSONObj operand = BSON_ARRAY(BSONNULL); + + InMatchExpression in; + in.init("a"); + in.getArrayFilterEntries()->addEquality(operand.firstElement()); + + ASSERT(in.matchesBSON(BSONObj(), NULL)); + ASSERT(in.matchesBSON(BSON("a" << BSONNULL), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << 4), NULL)); + // A non-existent field is treated same way as an empty bson object + ASSERT(in.matchesBSON(BSON("b" << 4), NULL)); +} + +TEST(InMatchExpression, MatchesUndefined) { + BSONObj operand = BSON_ARRAY(BSONUndefined); + + InMatchExpression in; + in.init("a"); + Status s = in.getArrayFilterEntries()->addEquality(operand.firstElement()); + ASSERT_NOT_OK(s); +} + +TEST(InMatchExpression, MatchesMinKey) { + BSONObj operand = BSON_ARRAY(MinKey); + InMatchExpression in; + in.init("a"); + in.getArrayFilterEntries()->addEquality(operand.firstElement()); + + ASSERT(in.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(InMatchExpression, MatchesMaxKey) { + BSONObj operand = BSON_ARRAY(MaxKey); + InMatchExpression in; + in.init("a"); + in.getArrayFilterEntries()->addEquality(operand.firstElement()); + + ASSERT(in.matchesBSON(BSON("a" << MaxKey), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << MinKey), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(InMatchExpression, MatchesFullArray) { + BSONObj operand = BSON_ARRAY(BSON_ARRAY(1 << 2) << 4 << 5); + InMatchExpression in; + in.init("a"); + in.getArrayFilterEntries()->addEquality(operand[0]); + in.getArrayFilterEntries()->addEquality(operand[1]); + in.getArrayFilterEntries()->addEquality(operand[2]); + + ASSERT(in.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2)), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 3)), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << BSON_ARRAY(1)), NULL)); + ASSERT(!in.matchesBSON(BSON("a" << 1), NULL)); +} + +TEST(InMatchExpression, ElemMatchKey) { + BSONObj operand = BSON_ARRAY(5 << 2); + InMatchExpression in; + in.init("a"); + in.getArrayFilterEntries()->addEquality(operand[0]); + in.getArrayFilterEntries()->addEquality(operand[1]); + + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!in.matchesBSON(BSON("a" << 4), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(in.matchesBSON(BSON("a" << 5), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(in.matchesBSON(BSON("a" << BSON_ARRAY(1 << 2 << 5)), &details)); + ASSERT(details.hasElemMatchKey()); + ASSERT_EQUALS("1", details.elemMatchKey()); +} + +/** +TEST( InMatchExpression, MatchesIndexKeyScalar ) { + BSONObj operand = BSON( "$in" << BSON_ARRAY( 6 << 5 ) ); + InMatchExpression in; + ASSERT( in.init( "a", operand[ "$in" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + in.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + in.matchesIndexKey( BSON( "" << 5 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + in.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + in.matchesIndexKey( BSON( "" << BSON_ARRAY( 6 ) ), indexSpec ) ); +} + +TEST( InMatchExpression, MatchesIndexKeyMissing ) { + BSONObj operand = BSON( "$in" << BSON_ARRAY( 6 ) ); + ComparisonMatchExpression eq + ASSERT( eq.init( "a", operand[ "$in" ] ).isOK() ); + IndexSpec indexSpec( BSON( "b" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + eq.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + eq.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + eq.matchesIndexKey( BSON( "" << BSON_ARRAY( 8 << 6 ) ), indexSpec ) ); +} + +TEST( InMatchExpression, MatchesIndexKeyArray ) { + BSONObj operand = BSON( "$in" << BSON_ARRAY( 4 << BSON_ARRAY( 5 ) ) ); + InMatchExpression in; + ASSERT( in.init( "a", operand[ "$in" ] ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + in.matchesIndexKey( BSON( "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + in.matchesIndexKey( BSON( "" << 5 ), indexSpec ) ); +} + +TEST( InMatchExpression, MatchesIndexKeyArrayValue ) { + BSONObjBuilder inArray; + inArray.append( "0", 4 ).append( "1", 5 ).appendRegex( "2", "abc", "" ); + BSONObj operand = BSONObjBuilder().appendArray( "$in", inArray.obj() ).obj(); + InMatchExpression in; + ASSERT( in.init( "a", operand[ "$in" ] ).isOK() ); + IndexSpec indexSpec( BSON( "loc" << "mockarrayvalue" << "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + in.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 4 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + in.matchesIndexKey( BSON( "" << "dummygeohash" << "" << 6 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + in.matchesIndexKey( BSON( "" << "dummygeohash" << "" << "abcd" ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + in.matchesIndexKey( BSONObjBuilder() + .append( "", "dummygeohash" ) + .appendRegex( "", "abc", "" ).obj(), + indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + in.matchesIndexKey( BSON( "" << "dummygeohash" << "" << "ab" ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + in.matchesIndexKey( BSON( "" << "dummygeohash" << + "" << BSON_ARRAY( 8 << 5 ) ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + in.matchesIndexKey( BSON( "" << "dummygeohash" << + "" << BSON_ARRAY( 8 << 9 ) ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + in.matchesIndexKey( BSON( "" << "dummygeohash" << + "" << BSON_ARRAY( 8 << "abc" ) ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + in.matchesIndexKey( BSON( "" << "dummygeohash" << + "" << BSON_ARRAY( 8 << "ac" ) ), indexSpec ) ); +} +*/ } diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp index b2977e34117..4146051bfb5 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -41,774 +41,743 @@ namespace { - using namespace mongo; +using namespace mongo; - /** - * Returns true if subtree contains MatchExpression 'type'. - */ - bool hasNode(const MatchExpression* root, MatchExpression::MatchType type) { - if (type == root->matchType()) { +/** + * Returns true if subtree contains MatchExpression 'type'. + */ +bool hasNode(const MatchExpression* root, MatchExpression::MatchType type) { + if (type == root->matchType()) { + return true; + } + for (size_t i = 0; i < root->numChildren(); ++i) { + if (hasNode(root->getChild(i), type)) { return true; } - for (size_t i = 0; i < root->numChildren(); ++i) { - if (hasNode(root->getChild(i), type)) { - return true; - } - } - return false; } + return false; +} -} // namespace +} // namespace namespace mongo { - using std::string; +using std::string; - StatusWithMatchExpression MatchExpressionParser::_parseComparison( const char* name, - ComparisonMatchExpression* cmp, - const BSONElement& e ) { - std::unique_ptr<ComparisonMatchExpression> temp(cmp); +StatusWithMatchExpression MatchExpressionParser::_parseComparison(const char* name, + ComparisonMatchExpression* cmp, + const BSONElement& e) { + std::unique_ptr<ComparisonMatchExpression> temp(cmp); - // Non-equality comparison match expressions cannot have - // a regular expression as the argument (e.g. {a: {$gt: /b/}} is illegal). - if (MatchExpression::EQ != cmp->matchType() && RegEx == e.type()) { - std::stringstream ss; - ss << "Can't have RegEx as arg to predicate over field '" << name << "'."; - return StatusWithMatchExpression(Status(ErrorCodes::BadValue, ss.str())); - } - - Status s = temp->init( name, e ); - if ( !s.isOK() ) - return StatusWithMatchExpression(s); - - return StatusWithMatchExpression( temp.release() ); + // Non-equality comparison match expressions cannot have + // a regular expression as the argument (e.g. {a: {$gt: /b/}} is illegal). + if (MatchExpression::EQ != cmp->matchType() && RegEx == e.type()) { + std::stringstream ss; + ss << "Can't have RegEx as arg to predicate over field '" << name << "'."; + return StatusWithMatchExpression(Status(ErrorCodes::BadValue, ss.str())); } - StatusWithMatchExpression MatchExpressionParser::_parseSubField( const BSONObj& context, - const AndMatchExpression* andSoFar, - const char* name, - const BSONElement& e, - int level ) { + Status s = temp->init(name, e); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + return StatusWithMatchExpression(temp.release()); +} - // TODO: these should move to getGtLtOp, or its replacement +StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& context, + const AndMatchExpression* andSoFar, + const char* name, + const BSONElement& e, + int level) { + // TODO: these should move to getGtLtOp, or its replacement - if ( mongoutils::str::equals( "$eq", e.fieldName() ) ) - return _parseComparison( name, new EqualityMatchExpression(), e ); + if (mongoutils::str::equals("$eq", e.fieldName())) + return _parseComparison(name, new EqualityMatchExpression(), e); - if ( mongoutils::str::equals( "$not", e.fieldName() ) ) { - return _parseNot( name, e, level ); - } + if (mongoutils::str::equals("$not", e.fieldName())) { + return _parseNot(name, e, level); + } - int x = e.getGtLtOp(-1); - switch ( x ) { + int x = e.getGtLtOp(-1); + switch (x) { case -1: // $where cannot be a sub-expression because it works on top-level documents only. - if ( mongoutils::str::equals( "$where", e.fieldName() ) ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$where cannot be applied to a field" ); + if (mongoutils::str::equals("$where", e.fieldName())) { + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$where cannot be applied to a field"); } - return StatusWithMatchExpression( ErrorCodes::BadValue, - mongoutils::str::stream() << "unknown operator: " - << e.fieldName() ); + return StatusWithMatchExpression(ErrorCodes::BadValue, + mongoutils::str::stream() + << "unknown operator: " << e.fieldName()); case BSONObj::LT: - return _parseComparison( name, new LTMatchExpression(), e ); + return _parseComparison(name, new LTMatchExpression(), e); case BSONObj::LTE: - return _parseComparison( name, new LTEMatchExpression(), e ); + return _parseComparison(name, new LTEMatchExpression(), e); case BSONObj::GT: - return _parseComparison( name, new GTMatchExpression(), e ); + return _parseComparison(name, new GTMatchExpression(), e); case BSONObj::GTE: - return _parseComparison( name, new GTEMatchExpression(), e ); + return _parseComparison(name, new GTEMatchExpression(), e); case BSONObj::NE: { if (RegEx == e.type()) { // Just because $ne can be rewritten as the negation of an // equality does not mean that $ne of a regex is allowed. See SERVER-1705. - return StatusWithMatchExpression(Status(ErrorCodes::BadValue, - "Can't have regex as arg to $ne.")); + return StatusWithMatchExpression( + Status(ErrorCodes::BadValue, "Can't have regex as arg to $ne.")); } - StatusWithMatchExpression s = _parseComparison( name, new EqualityMatchExpression(), e ); - if ( !s.isOK() ) + StatusWithMatchExpression s = _parseComparison(name, new EqualityMatchExpression(), e); + if (!s.isOK()) return s; - std::unique_ptr<NotMatchExpression> n( new NotMatchExpression() ); - Status s2 = n->init( s.getValue() ); - if ( !s2.isOK() ) - return StatusWithMatchExpression( s2 ); - return StatusWithMatchExpression( n.release() ); + std::unique_ptr<NotMatchExpression> n(new NotMatchExpression()); + Status s2 = n->init(s.getValue()); + if (!s2.isOK()) + return StatusWithMatchExpression(s2); + return StatusWithMatchExpression(n.release()); } case BSONObj::Equality: - return _parseComparison( name, new EqualityMatchExpression(), e ); + return _parseComparison(name, new EqualityMatchExpression(), e); case BSONObj::opIN: { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$in needs an array" ); - std::unique_ptr<InMatchExpression> temp( new InMatchExpression() ); - Status s = temp->init( name ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$in needs an array"); + std::unique_ptr<InMatchExpression> temp(new InMatchExpression()); + Status s = temp->init(name); + if (!s.isOK()) + return StatusWithMatchExpression(s); + s = _parseArrayFilterEntries(temp->getArrayFilterEntries(), e.Obj()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); } case BSONObj::NIN: { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$nin needs an array" ); - std::unique_ptr<InMatchExpression> temp( new InMatchExpression() ); - Status s = temp->init( name ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - - std::unique_ptr<NotMatchExpression> temp2( new NotMatchExpression() ); - s = temp2->init( temp.release() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - - return StatusWithMatchExpression( temp2.release() ); + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$nin needs an array"); + std::unique_ptr<InMatchExpression> temp(new InMatchExpression()); + Status s = temp->init(name); + if (!s.isOK()) + return StatusWithMatchExpression(s); + s = _parseArrayFilterEntries(temp->getArrayFilterEntries(), e.Obj()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + std::unique_ptr<NotMatchExpression> temp2(new NotMatchExpression()); + s = temp2->init(temp.release()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + return StatusWithMatchExpression(temp2.release()); } case BSONObj::opSIZE: { int size = 0; - if ( e.type() == String ) { + if (e.type() == String) { // matching old odd semantics size = 0; - } - else if ( e.type() == NumberInt || e.type() == NumberLong ) { + } else if (e.type() == NumberInt || e.type() == NumberLong) { if (e.numberLong() < 0) { // SERVER-11952. Setting 'size' to -1 means that no documents // should match this $size expression. size = -1; - } - else { + } else { size = e.numberInt(); } - } - else if ( e.type() == NumberDouble ) { - if ( e.numberInt() == e.numberDouble() ) { + } else if (e.type() == NumberDouble) { + if (e.numberInt() == e.numberDouble()) { size = e.numberInt(); - } - else { + } else { // old semantcs require exact numeric match // so [1,2] != 1 or 2 size = -1; } - } - else { - return StatusWithMatchExpression( ErrorCodes::BadValue, "$size needs a number" ); + } else { + return StatusWithMatchExpression(ErrorCodes::BadValue, "$size needs a number"); } - std::unique_ptr<SizeMatchExpression> temp( new SizeMatchExpression() ); - Status s = temp->init( name, size ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); + std::unique_ptr<SizeMatchExpression> temp(new SizeMatchExpression()); + Status s = temp->init(name, size); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); } case BSONObj::opEXISTS: { - if ( e.eoo() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$exists can't be eoo" ); - std::unique_ptr<ExistsMatchExpression> temp( new ExistsMatchExpression() ); - Status s = temp->init( name ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - if ( e.trueValue() ) - return StatusWithMatchExpression( temp.release() ); - std::unique_ptr<NotMatchExpression> temp2( new NotMatchExpression() ); - s = temp2->init( temp.release() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp2.release() ); + if (e.eoo()) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$exists can't be eoo"); + std::unique_ptr<ExistsMatchExpression> temp(new ExistsMatchExpression()); + Status s = temp->init(name); + if (!s.isOK()) + return StatusWithMatchExpression(s); + if (e.trueValue()) + return StatusWithMatchExpression(temp.release()); + std::unique_ptr<NotMatchExpression> temp2(new NotMatchExpression()); + s = temp2->init(temp.release()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp2.release()); } case BSONObj::opTYPE: { - if ( !e.isNumber() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$type has to be a number" ); + if (!e.isNumber()) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$type has to be a number"); int type = e.numberInt(); - if ( e.type() != NumberInt && type != e.number() ) + if (e.type() != NumberInt && type != e.number()) type = -1; - std::unique_ptr<TypeMatchExpression> temp( new TypeMatchExpression() ); - Status s = temp->init( name, type ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); + std::unique_ptr<TypeMatchExpression> temp(new TypeMatchExpression()); + Status s = temp->init(name, type); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); } case BSONObj::opMOD: - return _parseMOD( name, e ); + return _parseMOD(name, e); case BSONObj::opOPTIONS: { // TODO: try to optimize this // we have to do this since $options can be before or after a $regex // but we validate here - BSONObjIterator i( context ); - while ( i.more() ) { + BSONObjIterator i(context); + while (i.more()) { BSONElement temp = i.next(); - if ( temp.getGtLtOp( -1 ) == BSONObj::opREGEX ) - return StatusWithMatchExpression( NULL ); + if (temp.getGtLtOp(-1) == BSONObj::opREGEX) + return StatusWithMatchExpression(NULL); } - return StatusWithMatchExpression( ErrorCodes::BadValue, "$options needs a $regex" ); + return StatusWithMatchExpression(ErrorCodes::BadValue, "$options needs a $regex"); } case BSONObj::opREGEX: { - return _parseRegexDocument( name, context ); + return _parseRegexDocument(name, context); } case BSONObj::opELEM_MATCH: - return _parseElemMatch( name, e, level ); + return _parseElemMatch(name, e, level); case BSONObj::opALL: - return _parseAll( name, e, level ); + return _parseAll(name, e, level); case BSONObj::opWITHIN: case BSONObj::opGEO_INTERSECTS: - return expressionParserGeoCallback( name, x, context ); - } - - return StatusWithMatchExpression( ErrorCodes::BadValue, - mongoutils::str::stream() << "not handled: " << e.fieldName() ); + return expressionParserGeoCallback(name, x, context); } - StatusWithMatchExpression MatchExpressionParser::_parse( const BSONObj& obj, int level ) { - if (level > kMaximumTreeDepth) { - mongoutils::str::stream ss; - ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth - << " at " << obj.toString(); - return StatusWithMatchExpression( ErrorCodes::BadValue, ss ); - } - - std::unique_ptr<AndMatchExpression> root( new AndMatchExpression() ); - - bool topLevel = (level == 0); - level++; - - BSONObjIterator i( obj ); - while ( i.more() ){ + return StatusWithMatchExpression(ErrorCodes::BadValue, + mongoutils::str::stream() << "not handled: " << e.fieldName()); +} - BSONElement e = i.next(); - if ( e.fieldName()[0] == '$' ) { - const char * rest = e.fieldName() + 1; +StatusWithMatchExpression MatchExpressionParser::_parse(const BSONObj& obj, int level) { + if (level > kMaximumTreeDepth) { + mongoutils::str::stream ss; + ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth << " at " + << obj.toString(); + return StatusWithMatchExpression(ErrorCodes::BadValue, ss); + } - // TODO: optimize if block? - if ( mongoutils::str::equals( "or", rest ) ) { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$or needs an array" ); - std::unique_ptr<OrMatchExpression> temp( new OrMatchExpression() ); - Status s = _parseTreeList( e.Obj(), temp.get(), level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - root->add( temp.release() ); - } - else if ( mongoutils::str::equals( "and", rest ) ) { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, - "and needs an array" ); - std::unique_ptr<AndMatchExpression> temp( new AndMatchExpression() ); - Status s = _parseTreeList( e.Obj(), temp.get(), level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - root->add( temp.release() ); - } - else if ( mongoutils::str::equals( "nor", rest ) ) { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, - "and needs an array" ); - std::unique_ptr<NorMatchExpression> temp( new NorMatchExpression() ); - Status s = _parseTreeList( e.Obj(), temp.get(), level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - root->add( temp.release() ); - } - else if ( mongoutils::str::equals( "atomic", rest ) || - mongoutils::str::equals( "isolated", rest ) ) { - if ( !topLevel ) - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$atomic/$isolated has to be at the top level" ); - if ( e.trueValue() ) - root->add( new AtomicMatchExpression() ); - } - else if ( mongoutils::str::equals( "where", rest ) ) { - StatusWithMatchExpression s = _whereCallback->parseWhere(e); - if ( !s.isOK() ) - return s; - root->add( s.getValue() ); - } - else if ( mongoutils::str::equals( "text", rest ) ) { - if ( e.type() != Object ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$text expects an object" ); - } - StatusWithMatchExpression s = expressionParserTextCallback( e.Obj() ); - if ( !s.isOK() ) { - return s; - } - root->add( s.getValue() ); - } - else if ( mongoutils::str::equals( "comment", rest ) ) { - } - else if ( mongoutils::str::equals( "ref", rest ) || - mongoutils::str::equals( "id", rest ) || - mongoutils::str::equals( "db", rest ) ) { - // DBRef fields. - std::unique_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); - Status s = eq->init( e.fieldName(), e ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - - root->add( eq.release() ); + std::unique_ptr<AndMatchExpression> root(new AndMatchExpression()); + + bool topLevel = (level == 0); + level++; + + BSONObjIterator i(obj); + while (i.more()) { + BSONElement e = i.next(); + if (e.fieldName()[0] == '$') { + const char* rest = e.fieldName() + 1; + + // TODO: optimize if block? + if (mongoutils::str::equals("or", rest)) { + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$or needs an array"); + std::unique_ptr<OrMatchExpression> temp(new OrMatchExpression()); + Status s = _parseTreeList(e.Obj(), temp.get(), level); + if (!s.isOK()) + return StatusWithMatchExpression(s); + root->add(temp.release()); + } else if (mongoutils::str::equals("and", rest)) { + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "and needs an array"); + std::unique_ptr<AndMatchExpression> temp(new AndMatchExpression()); + Status s = _parseTreeList(e.Obj(), temp.get(), level); + if (!s.isOK()) + return StatusWithMatchExpression(s); + root->add(temp.release()); + } else if (mongoutils::str::equals("nor", rest)) { + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "and needs an array"); + std::unique_ptr<NorMatchExpression> temp(new NorMatchExpression()); + Status s = _parseTreeList(e.Obj(), temp.get(), level); + if (!s.isOK()) + return StatusWithMatchExpression(s); + root->add(temp.release()); + } else if (mongoutils::str::equals("atomic", rest) || + mongoutils::str::equals("isolated", rest)) { + if (!topLevel) + return StatusWithMatchExpression( + ErrorCodes::BadValue, "$atomic/$isolated has to be at the top level"); + if (e.trueValue()) + root->add(new AtomicMatchExpression()); + } else if (mongoutils::str::equals("where", rest)) { + StatusWithMatchExpression s = _whereCallback->parseWhere(e); + if (!s.isOK()) + return s; + root->add(s.getValue()); + } else if (mongoutils::str::equals("text", rest)) { + if (e.type() != Object) { + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$text expects an object"); } - else { - return StatusWithMatchExpression( ErrorCodes::BadValue, - mongoutils::str::stream() - << "unknown top level operator: " - << e.fieldName() ); + StatusWithMatchExpression s = expressionParserTextCallback(e.Obj()); + if (!s.isOK()) { + return s; } - - continue; - } - - if ( _isExpressionDocument( e, false ) ) { - Status s = _parseSub( e.fieldName(), e.Obj(), root.get(), level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - continue; + root->add(s.getValue()); + } else if (mongoutils::str::equals("comment", rest)) { + } else if (mongoutils::str::equals("ref", rest) || + mongoutils::str::equals("id", rest) || mongoutils::str::equals("db", rest)) { + // DBRef fields. + std::unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); + Status s = eq->init(e.fieldName(), e); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + root->add(eq.release()); + } else { + return StatusWithMatchExpression( + ErrorCodes::BadValue, + mongoutils::str::stream() << "unknown top level operator: " << e.fieldName()); } - if ( e.type() == RegEx ) { - StatusWithMatchExpression result = _parseRegexElement( e.fieldName(), e ); - if ( !result.isOK() ) - return result; - root->add( result.getValue() ); - continue; - } - - std::unique_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); - Status s = eq->init( e.fieldName(), e ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); + continue; + } - root->add( eq.release() ); + if (_isExpressionDocument(e, false)) { + Status s = _parseSub(e.fieldName(), e.Obj(), root.get(), level); + if (!s.isOK()) + return StatusWithMatchExpression(s); + continue; } - if ( root->numChildren() == 1 ) { - const MatchExpression* real = root->getChild(0); - root->clearAndRelease(); - return StatusWithMatchExpression( const_cast<MatchExpression*>(real) ); + if (e.type() == RegEx) { + StatusWithMatchExpression result = _parseRegexElement(e.fieldName(), e); + if (!result.isOK()) + return result; + root->add(result.getValue()); + continue; } - return StatusWithMatchExpression( root.release() ); + std::unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); + Status s = eq->init(e.fieldName(), e); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + root->add(eq.release()); } - Status MatchExpressionParser::_parseSub( const char* name, - const BSONObj& sub, - AndMatchExpression* root, - int level ) { - // The one exception to {field : {fully contained argument} } is, of course, geo. Example: - // sub == { field : {$near[Sphere]: [0,0], $maxDistance: 1000, $minDistance: 10 } } - // We peek inside of 'sub' to see if it's possibly a $near. If so, we can't iterate over - // its subfields and parse them one at a time (there is no $maxDistance without $near), so - // we hand the entire object over to the geo parsing routines. - - if (level > kMaximumTreeDepth) { - mongoutils::str::stream ss; - ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth - << " at " << sub.toString(); - return Status( ErrorCodes::BadValue, ss ); - } + if (root->numChildren() == 1) { + const MatchExpression* real = root->getChild(0); + root->clearAndRelease(); + return StatusWithMatchExpression(const_cast<MatchExpression*>(real)); + } + + return StatusWithMatchExpression(root.release()); +} - level++; - - BSONObjIterator geoIt(sub); - if (geoIt.more()) { - BSONElement firstElt = geoIt.next(); - if (firstElt.isABSONObj()) { - const char* fieldName = firstElt.fieldName(); - // TODO: Having these $fields here isn't ideal but we don't want to pull in anything - // from db/geo at this point, since it may not actually be linked in... - if (mongoutils::str::equals(fieldName, "$near") - || mongoutils::str::equals(fieldName, "$nearSphere") - || mongoutils::str::equals(fieldName, "$geoNear") - || mongoutils::str::equals(fieldName, "$maxDistance") - || mongoutils::str::equals(fieldName, "$minDistance")) { - - StatusWithMatchExpression s = expressionParserGeoCallback(name, - firstElt.getGtLtOp(), - sub); - if (s.isOK()) { - root->add(s.getValue()); - } - - // Propagate geo parsing result to caller. - return s.getStatus(); +Status MatchExpressionParser::_parseSub(const char* name, + const BSONObj& sub, + AndMatchExpression* root, + int level) { + // The one exception to {field : {fully contained argument} } is, of course, geo. Example: + // sub == { field : {$near[Sphere]: [0,0], $maxDistance: 1000, $minDistance: 10 } } + // We peek inside of 'sub' to see if it's possibly a $near. If so, we can't iterate over + // its subfields and parse them one at a time (there is no $maxDistance without $near), so + // we hand the entire object over to the geo parsing routines. + + if (level > kMaximumTreeDepth) { + mongoutils::str::stream ss; + ss << "exceeded maximum query tree depth of " << kMaximumTreeDepth << " at " + << sub.toString(); + return Status(ErrorCodes::BadValue, ss); + } + + level++; + + BSONObjIterator geoIt(sub); + if (geoIt.more()) { + BSONElement firstElt = geoIt.next(); + if (firstElt.isABSONObj()) { + const char* fieldName = firstElt.fieldName(); + // TODO: Having these $fields here isn't ideal but we don't want to pull in anything + // from db/geo at this point, since it may not actually be linked in... + if (mongoutils::str::equals(fieldName, "$near") || + mongoutils::str::equals(fieldName, "$nearSphere") || + mongoutils::str::equals(fieldName, "$geoNear") || + mongoutils::str::equals(fieldName, "$maxDistance") || + mongoutils::str::equals(fieldName, "$minDistance")) { + StatusWithMatchExpression s = + expressionParserGeoCallback(name, firstElt.getGtLtOp(), sub); + if (s.isOK()) { + root->add(s.getValue()); } + + // Propagate geo parsing result to caller. + return s.getStatus(); } } + } - BSONObjIterator j( sub ); - while ( j.more() ) { - BSONElement deep = j.next(); - - StatusWithMatchExpression s = _parseSubField( sub, root, name, deep, level ); - if ( !s.isOK() ) - return s.getStatus(); + BSONObjIterator j(sub); + while (j.more()) { + BSONElement deep = j.next(); - if ( s.getValue() ) - root->add( s.getValue() ); - } + StatusWithMatchExpression s = _parseSubField(sub, root, name, deep, level); + if (!s.isOK()) + return s.getStatus(); - return Status::OK(); + if (s.getValue()) + root->add(s.getValue()); } - bool MatchExpressionParser::_isExpressionDocument( const BSONElement& e, - bool allowIncompleteDBRef ) { - if ( e.type() != Object ) - return false; + return Status::OK(); +} - BSONObj o = e.Obj(); - if ( o.isEmpty() ) - return false; +bool MatchExpressionParser::_isExpressionDocument(const BSONElement& e, bool allowIncompleteDBRef) { + if (e.type() != Object) + return false; - const char* name = o.firstElement().fieldName(); - if ( name[0] != '$' ) - return false; + BSONObj o = e.Obj(); + if (o.isEmpty()) + return false; - if ( _isDBRefDocument( o, allowIncompleteDBRef ) ) { - return false; - } + const char* name = o.firstElement().fieldName(); + if (name[0] != '$') + return false; - return true; + if (_isDBRefDocument(o, allowIncompleteDBRef)) { + return false; } - /** - * DBRef fields are ordered in the collection. - * In the query, we consider an embedded object a query on - * a DBRef as long as it contains $ref and $id. - * Required fields: $ref and $id (if incomplete DBRefs are not allowed) - * - * If incomplete DBRefs are allowed, we accept the BSON object as long as it - * contains $ref, $id or $db. - * - * Field names are checked but not field types. - */ - bool MatchExpressionParser::_isDBRefDocument( const BSONObj& obj, bool allowIncompleteDBRef ) { - bool hasRef = false; - bool hasID = false; - bool hasDB = false; - - BSONObjIterator i( obj ); - while ( i.more() && !( hasRef && hasID ) ) { - BSONElement element = i.next(); - const char *fieldName = element.fieldName(); - // $ref - if ( !hasRef && mongoutils::str::equals( "$ref", fieldName ) ) { - hasRef = true; - } - // $id - else if ( !hasID && mongoutils::str::equals( "$id", fieldName ) ) { - hasID = true; - } - // $db - else if ( !hasDB && mongoutils::str::equals( "$db", fieldName ) ) { - hasDB = true; - } - } + return true; +} - if (allowIncompleteDBRef) { - return hasRef || hasID || hasDB; +/** + * DBRef fields are ordered in the collection. + * In the query, we consider an embedded object a query on + * a DBRef as long as it contains $ref and $id. + * Required fields: $ref and $id (if incomplete DBRefs are not allowed) + * + * If incomplete DBRefs are allowed, we accept the BSON object as long as it + * contains $ref, $id or $db. + * + * Field names are checked but not field types. + */ +bool MatchExpressionParser::_isDBRefDocument(const BSONObj& obj, bool allowIncompleteDBRef) { + bool hasRef = false; + bool hasID = false; + bool hasDB = false; + + BSONObjIterator i(obj); + while (i.more() && !(hasRef && hasID)) { + BSONElement element = i.next(); + const char* fieldName = element.fieldName(); + // $ref + if (!hasRef && mongoutils::str::equals("$ref", fieldName)) { + hasRef = true; + } + // $id + else if (!hasID && mongoutils::str::equals("$id", fieldName)) { + hasID = true; + } + // $db + else if (!hasDB && mongoutils::str::equals("$db", fieldName)) { + hasDB = true; } - - return hasRef && hasID; } - StatusWithMatchExpression MatchExpressionParser::_parseMOD( const char* name, - const BSONElement& e ) { - - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, needs to be an array" ); - - BSONObjIterator i( e.Obj() ); - - if ( !i.more() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, not enough elements" ); - BSONElement d = i.next(); - if ( !d.isNumber() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, divisor not a number" ); - - if ( !i.more() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, not enough elements" ); - BSONElement r = i.next(); - if ( !d.isNumber() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, remainder not a number" ); + if (allowIncompleteDBRef) { + return hasRef || hasID || hasDB; + } - if ( i.more() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "malformed mod, too many elements" ); + return hasRef && hasID; +} - std::unique_ptr<ModMatchExpression> temp( new ModMatchExpression() ); - Status s = temp->init( name, d.numberInt(), r.numberInt() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); - } +StatusWithMatchExpression MatchExpressionParser::_parseMOD(const char* name, const BSONElement& e) { + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "malformed mod, needs to be an array"); + + BSONObjIterator i(e.Obj()); + + if (!i.more()) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "malformed mod, not enough elements"); + BSONElement d = i.next(); + if (!d.isNumber()) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "malformed mod, divisor not a number"); + + if (!i.more()) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "malformed mod, not enough elements"); + BSONElement r = i.next(); + if (!d.isNumber()) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "malformed mod, remainder not a number"); + + if (i.more()) + return StatusWithMatchExpression(ErrorCodes::BadValue, "malformed mod, too many elements"); + + std::unique_ptr<ModMatchExpression> temp(new ModMatchExpression()); + Status s = temp->init(name, d.numberInt(), r.numberInt()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); +} - StatusWithMatchExpression MatchExpressionParser::_parseRegexElement( const char* name, - const BSONElement& e ) { - if ( e.type() != RegEx ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "not a regex" ); +StatusWithMatchExpression MatchExpressionParser::_parseRegexElement(const char* name, + const BSONElement& e) { + if (e.type() != RegEx) + return StatusWithMatchExpression(ErrorCodes::BadValue, "not a regex"); - std::unique_ptr<RegexMatchExpression> temp( new RegexMatchExpression() ); - Status s = temp->init( name, e.regex(), e.regexFlags() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); - } + std::unique_ptr<RegexMatchExpression> temp(new RegexMatchExpression()); + Status s = temp->init(name, e.regex(), e.regexFlags()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); +} - StatusWithMatchExpression MatchExpressionParser::_parseRegexDocument( const char* name, - const BSONObj& doc ) { - string regex; - string regexOptions; +StatusWithMatchExpression MatchExpressionParser::_parseRegexDocument(const char* name, + const BSONObj& doc) { + string regex; + string regexOptions; - BSONObjIterator i( doc ); - while ( i.more() ) { - BSONElement e = i.next(); - switch ( e.getGtLtOp() ) { + BSONObjIterator i(doc); + while (i.more()) { + BSONElement e = i.next(); + switch (e.getGtLtOp()) { case BSONObj::opREGEX: - if ( e.type() == String ) { + if (e.type() == String) { regex = e.String(); - } - else if ( e.type() == RegEx ) { + } else if (e.type() == RegEx) { regex = e.regex(); regexOptions = e.regexFlags(); - } - else { - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$regex has to be a string" ); + } else { + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$regex has to be a string"); } break; case BSONObj::opOPTIONS: - if ( e.type() != String ) - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$options has to be a string" ); + if (e.type() != String) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$options has to be a string"); regexOptions = e.String(); break; default: break; - } - } - - std::unique_ptr<RegexMatchExpression> temp( new RegexMatchExpression() ); - Status s = temp->init( name, regex, regexOptions ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( temp.release() ); - } - Status MatchExpressionParser::_parseArrayFilterEntries( ArrayFilterEntries* entries, - const BSONObj& theArray ) { - - BSONObjIterator i( theArray ); - while ( i.more() ) { - BSONElement e = i.next(); + std::unique_ptr<RegexMatchExpression> temp(new RegexMatchExpression()); + Status s = temp->init(name, regex, regexOptions); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(temp.release()); +} - // allow DBRefs but reject all fields with names starting wiht $ - if ( _isExpressionDocument( e, false ) ) { - return Status( ErrorCodes::BadValue, "cannot nest $ under $in" ); - } +Status MatchExpressionParser::_parseArrayFilterEntries(ArrayFilterEntries* entries, + const BSONObj& theArray) { + BSONObjIterator i(theArray); + while (i.more()) { + BSONElement e = i.next(); - if ( e.type() == RegEx ) { - std::unique_ptr<RegexMatchExpression> r( new RegexMatchExpression() ); - Status s = r->init( "", e ); - if ( !s.isOK() ) - return s; - s = entries->addRegex( r.release() ); - if ( !s.isOK() ) - return s; - } - else { - Status s = entries->addEquality( e ); - if ( !s.isOK() ) - return s; - } + // allow DBRefs but reject all fields with names starting wiht $ + if (_isExpressionDocument(e, false)) { + return Status(ErrorCodes::BadValue, "cannot nest $ under $in"); } - return Status::OK(); + if (e.type() == RegEx) { + std::unique_ptr<RegexMatchExpression> r(new RegexMatchExpression()); + Status s = r->init("", e); + if (!s.isOK()) + return s; + s = entries->addRegex(r.release()); + if (!s.isOK()) + return s; + } else { + Status s = entries->addEquality(e); + if (!s.isOK()) + return s; + } } + return Status::OK(); +} - StatusWithMatchExpression MatchExpressionParser::_parseElemMatch( const char* name, - const BSONElement& e, - int level ) { - if ( e.type() != Object ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$elemMatch needs an Object" ); - - BSONObj obj = e.Obj(); - - // $elemMatch value case applies when the children all - // work on the field 'name'. - // This is the case when: - // 1) the argument is an expression document; and - // 2) expression is not a AND/NOR/OR logical operator. Children of - // these logical operators are initialized with field names. - // 3) expression is not a WHERE operator. WHERE works on objects instead - // of specific field. - bool isElemMatchValue = false; - if ( _isExpressionDocument( e, true ) ) { - BSONObj o = e.Obj(); - BSONElement elt = o.firstElement(); - invariant( !elt.eoo() ); - - isElemMatchValue = !mongoutils::str::equals( "$and", elt.fieldName() ) && - !mongoutils::str::equals( "$nor", elt.fieldName() ) && - !mongoutils::str::equals( "$or", elt.fieldName() ) && - !mongoutils::str::equals( "$where", elt.fieldName() ); - } +StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(const char* name, + const BSONElement& e, + int level) { + if (e.type() != Object) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$elemMatch needs an Object"); + + BSONObj obj = e.Obj(); + + // $elemMatch value case applies when the children all + // work on the field 'name'. + // This is the case when: + // 1) the argument is an expression document; and + // 2) expression is not a AND/NOR/OR logical operator. Children of + // these logical operators are initialized with field names. + // 3) expression is not a WHERE operator. WHERE works on objects instead + // of specific field. + bool isElemMatchValue = false; + if (_isExpressionDocument(e, true)) { + BSONObj o = e.Obj(); + BSONElement elt = o.firstElement(); + invariant(!elt.eoo()); - if ( isElemMatchValue ) { - // value case + isElemMatchValue = !mongoutils::str::equals("$and", elt.fieldName()) && + !mongoutils::str::equals("$nor", elt.fieldName()) && + !mongoutils::str::equals("$or", elt.fieldName()) && + !mongoutils::str::equals("$where", elt.fieldName()); + } - AndMatchExpression theAnd; - Status s = _parseSub( "", obj, &theAnd, level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); + if (isElemMatchValue) { + // value case - std::unique_ptr<ElemMatchValueMatchExpression> temp( new ElemMatchValueMatchExpression() ); - s = temp->init( name ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); + AndMatchExpression theAnd; + Status s = _parseSub("", obj, &theAnd, level); + if (!s.isOK()) + return StatusWithMatchExpression(s); - for ( size_t i = 0; i < theAnd.numChildren(); i++ ) { - temp->add( theAnd.getChild( i ) ); - } - theAnd.clearAndRelease(); + std::unique_ptr<ElemMatchValueMatchExpression> temp(new ElemMatchValueMatchExpression()); + s = temp->init(name); + if (!s.isOK()) + return StatusWithMatchExpression(s); - return StatusWithMatchExpression( temp.release() ); + for (size_t i = 0; i < theAnd.numChildren(); i++) { + temp->add(theAnd.getChild(i)); } + theAnd.clearAndRelease(); - // DBRef value case - // A DBRef document under a $elemMatch should be treated as an object case - // because it may contain non-DBRef fields in addition to $ref, $id and $db. - - // object case + return StatusWithMatchExpression(temp.release()); + } - StatusWithMatchExpression subRaw = _parse( obj, level ); - if ( !subRaw.isOK() ) - return subRaw; - std::unique_ptr<MatchExpression> sub( subRaw.getValue() ); + // DBRef value case + // A DBRef document under a $elemMatch should be treated as an object case + // because it may contain non-DBRef fields in addition to $ref, $id and $db. - // $where is not supported under $elemMatch because $where - // applies to top-level document, not array elements in a field. - if ( hasNode( sub.get(), MatchExpression::WHERE ) ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$elemMatch cannot contain $where expression" ); - } + // object case - std::unique_ptr<ElemMatchObjectMatchExpression> temp( new ElemMatchObjectMatchExpression() ); - Status status = temp->init( name, sub.release() ); - if ( !status.isOK() ) - return StatusWithMatchExpression( status ); + StatusWithMatchExpression subRaw = _parse(obj, level); + if (!subRaw.isOK()) + return subRaw; + std::unique_ptr<MatchExpression> sub(subRaw.getValue()); - return StatusWithMatchExpression( temp.release() ); + // $where is not supported under $elemMatch because $where + // applies to top-level document, not array elements in a field. + if (hasNode(sub.get(), MatchExpression::WHERE)) { + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$elemMatch cannot contain $where expression"); } - StatusWithMatchExpression MatchExpressionParser::_parseAll( const char* name, - const BSONElement& e, - int level ) { - if ( e.type() != Array ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$all needs an array" ); - - BSONObj arr = e.Obj(); - std::unique_ptr<AndMatchExpression> myAnd( new AndMatchExpression() ); - BSONObjIterator i( arr ); - - if ( arr.firstElement().type() == Object && - mongoutils::str::equals( "$elemMatch", - arr.firstElement().Obj().firstElement().fieldName() ) ) { - // $all : [ { $elemMatch : {} } ... ] - - while ( i.more() ) { - BSONElement hopefullyElemMatchElement = i.next(); - - if ( hopefullyElemMatchElement.type() != Object ) { - // $all : [ { $elemMatch : ... }, 5 ] - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$all/$elemMatch has to be consistent" ); - } + std::unique_ptr<ElemMatchObjectMatchExpression> temp(new ElemMatchObjectMatchExpression()); + Status status = temp->init(name, sub.release()); + if (!status.isOK()) + return StatusWithMatchExpression(status); - BSONObj hopefullyElemMatchObj = hopefullyElemMatchElement.Obj(); - if ( !mongoutils::str::equals( "$elemMatch", - hopefullyElemMatchObj.firstElement().fieldName() ) ) { - // $all : [ { $elemMatch : ... }, { x : 5 } ] - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$all/$elemMatch has to be consistent" ); - } + return StatusWithMatchExpression(temp.release()); +} - StatusWithMatchExpression inner = - _parseElemMatch( name, hopefullyElemMatchObj.firstElement(), level ); - if ( !inner.isOK() ) - return inner; - myAnd->add( inner.getValue() ); - } +StatusWithMatchExpression MatchExpressionParser::_parseAll(const char* name, + const BSONElement& e, + int level) { + if (e.type() != Array) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$all needs an array"); - return StatusWithMatchExpression( myAnd.release() ); - } + BSONObj arr = e.Obj(); + std::unique_ptr<AndMatchExpression> myAnd(new AndMatchExpression()); + BSONObjIterator i(arr); - while ( i.more() ) { - BSONElement e = i.next(); + if (arr.firstElement().type() == Object && + mongoutils::str::equals("$elemMatch", + arr.firstElement().Obj().firstElement().fieldName())) { + // $all : [ { $elemMatch : {} } ... ] - if ( e.type() == RegEx ) { - std::unique_ptr<RegexMatchExpression> r( new RegexMatchExpression() ); - Status s = r->init( name, e ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - myAnd->add( r.release() ); - } - else if ( e.type() == Object && e.Obj().firstElement().getGtLtOp(-1) != -1 ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, "no $ expressions in $all" ); + while (i.more()) { + BSONElement hopefullyElemMatchElement = i.next(); + + if (hopefullyElemMatchElement.type() != Object) { + // $all : [ { $elemMatch : ... }, 5 ] + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$all/$elemMatch has to be consistent"); } - else { - std::unique_ptr<EqualityMatchExpression> x( new EqualityMatchExpression() ); - Status s = x->init( name, e ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - myAnd->add( x.release() ); + + BSONObj hopefullyElemMatchObj = hopefullyElemMatchElement.Obj(); + if (!mongoutils::str::equals("$elemMatch", + hopefullyElemMatchObj.firstElement().fieldName())) { + // $all : [ { $elemMatch : ... }, { x : 5 } ] + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$all/$elemMatch has to be consistent"); } - } - if ( myAnd->numChildren() == 0 ) { - return StatusWithMatchExpression( new FalseMatchExpression() ); + StatusWithMatchExpression inner = + _parseElemMatch(name, hopefullyElemMatchObj.firstElement(), level); + if (!inner.isOK()) + return inner; + myAnd->add(inner.getValue()); } - return StatusWithMatchExpression( myAnd.release() ); + return StatusWithMatchExpression(myAnd.release()); } - StatusWithMatchExpression MatchExpressionParser::WhereCallback::parseWhere( - const BSONElement& where) const { - return StatusWithMatchExpression(ErrorCodes::NoWhereParseContext, - "no context for parsing $where"); + while (i.more()) { + BSONElement e = i.next(); + + if (e.type() == RegEx) { + std::unique_ptr<RegexMatchExpression> r(new RegexMatchExpression()); + Status s = r->init(name, e); + if (!s.isOK()) + return StatusWithMatchExpression(s); + myAnd->add(r.release()); + } else if (e.type() == Object && e.Obj().firstElement().getGtLtOp(-1) != -1) { + return StatusWithMatchExpression(ErrorCodes::BadValue, "no $ expressions in $all"); + } else { + std::unique_ptr<EqualityMatchExpression> x(new EqualityMatchExpression()); + Status s = x->init(name, e); + if (!s.isOK()) + return StatusWithMatchExpression(s); + myAnd->add(x.release()); + } } - // Geo - StatusWithMatchExpression expressionParserGeoCallbackDefault( const char* name, - int type, - const BSONObj& section ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, "geo not linked in" ); + if (myAnd->numChildren() == 0) { + return StatusWithMatchExpression(new FalseMatchExpression()); } - MatchExpressionParserGeoCallback expressionParserGeoCallback = - expressionParserGeoCallbackDefault; + return StatusWithMatchExpression(myAnd.release()); +} - // Text - StatusWithMatchExpression expressionParserTextCallbackDefault( const BSONObj& queryObj ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, "$text not linked in" ); - } +StatusWithMatchExpression MatchExpressionParser::WhereCallback::parseWhere( + const BSONElement& where) const { + return StatusWithMatchExpression(ErrorCodes::NoWhereParseContext, + "no context for parsing $where"); +} - MatchExpressionParserTextCallback expressionParserTextCallback = - expressionParserTextCallbackDefault; +// Geo +StatusWithMatchExpression expressionParserGeoCallbackDefault(const char* name, + int type, + const BSONObj& section) { + return StatusWithMatchExpression(ErrorCodes::BadValue, "geo not linked in"); +} + +MatchExpressionParserGeoCallback expressionParserGeoCallback = expressionParserGeoCallbackDefault; + +// Text +StatusWithMatchExpression expressionParserTextCallbackDefault(const BSONObj& queryObj) { + return StatusWithMatchExpression(ErrorCodes::BadValue, "$text not linked in"); +} +MatchExpressionParserTextCallback expressionParserTextCallback = + expressionParserTextCallbackDefault; } diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h index fdec56d1ed7..92c48bd7e11 100644 --- a/src/mongo/db/matcher/expression_parser.h +++ b/src/mongo/db/matcher/expression_parser.h @@ -39,175 +39,157 @@ namespace mongo { - class OperationContext; +class OperationContext; - typedef StatusWith<MatchExpression*> StatusWithMatchExpression; +typedef StatusWith<MatchExpression*> StatusWithMatchExpression; - class MatchExpressionParser { +class MatchExpressionParser { +public: + /** + * In general, expression parsing and matching should not require context, but the $where + * clause is an exception in that it needs to read the sys.js collection. + * + * The default behaviour is to return an error status that $where context is not present. + * + * Do not use this class to pass-in generic context as it should only be used for $where. + */ + class WhereCallback { public: + virtual StatusWithMatchExpression parseWhere(const BSONElement& where) const; - /** - * In general, expression parsing and matching should not require context, but the $where - * clause is an exception in that it needs to read the sys.js collection. - * - * The default behaviour is to return an error status that $where context is not present. - * - * Do not use this class to pass-in generic context as it should only be used for $where. - */ - class WhereCallback { - public: - virtual StatusWithMatchExpression parseWhere(const BSONElement& where) const; - - virtual ~WhereCallback() { } - }; - - /** - * caller has to maintain ownership obj - * the tree has views (BSONElement) into obj - */ - static StatusWithMatchExpression parse( - const BSONObj& obj, - const WhereCallback& whereCallback = WhereCallback()) { - // The 0 initializes the match expression tree depth. - return MatchExpressionParser(&whereCallback)._parse(obj, 0); - } - - private: - - explicit MatchExpressionParser(const WhereCallback* whereCallback) - : _whereCallback(whereCallback) { - - } - - /** - * 5 = false - * { a : 5 } = false - * { $lt : 5 } = true - * { $ref: "s", $id: "x" } = false - * { $ref: "s", $id: "x", $db: "mydb" } = false - * { $ref : "s" } = false (if incomplete DBRef is allowed) - * { $id : "x" } = false (if incomplete DBRef is allowed) - * { $db : "mydb" } = false (if incomplete DBRef is allowed) - */ - bool _isExpressionDocument( const BSONElement& e, bool allowIncompleteDBRef ); - - /** - * { $ref: "s", $id: "x" } = true - * { $ref : "s" } = true (if incomplete DBRef is allowed) - * { $id : "x" } = true (if incomplete DBRef is allowed) - * { $db : "x" } = true (if incomplete DBRef is allowed) - */ - bool _isDBRefDocument( const BSONObj& obj, bool allowIncompleteDBRef ); - - /** - * Parse 'obj' and return either a MatchExpression or an error. - * - * 'level' tracks the current depth of the tree across recursive calls to this - * function. Used in order to apply special logic at the top-level and to return an - * error if the tree exceeds the maximum allowed depth. - */ - StatusWithMatchExpression _parse( const BSONObj& obj, int level ); - - /** - * parses a field in a sub expression - * if the query is { x : { $gt : 5, $lt : 8 } } - * e is { $gt : 5, $lt : 8 } - */ - Status _parseSub( const char* name, - const BSONObj& obj, - AndMatchExpression* root, - int level ); - - /** - * parses a single field in a sub expression - * if the query is { x : { $gt : 5, $lt : 8 } } - * e is $gt : 5 - */ - StatusWithMatchExpression _parseSubField( const BSONObj& context, - const AndMatchExpression* andSoFar, - const char* name, - const BSONElement& e, - int level ); - - StatusWithMatchExpression _parseComparison( const char* name, - ComparisonMatchExpression* cmp, - const BSONElement& e ); - - StatusWithMatchExpression _parseMOD( const char* name, - const BSONElement& e ); - - StatusWithMatchExpression _parseRegexElement( const char* name, - const BSONElement& e ); - - StatusWithMatchExpression _parseRegexDocument( const char* name, - const BSONObj& doc ); - - - Status _parseArrayFilterEntries( ArrayFilterEntries* entries, - const BSONObj& theArray ); - - // arrays - - StatusWithMatchExpression _parseElemMatch( const char* name, - const BSONElement& e, - int level ); - - StatusWithMatchExpression _parseAll( const char* name, - const BSONElement& e, - int level ); - - // tree - - Status _parseTreeList( const BSONObj& arr, ListOfMatchExpression* out, int level ); - - StatusWithMatchExpression _parseNot( const char* name, - const BSONElement& e, - int level ); - - // The maximum allowed depth of a query tree. Just to guard against stack overflow. - static const int kMaximumTreeDepth; - - // Performs parsing for the $where clause. We do not own this pointer - it has to live - // as long as the parser is active. - const WhereCallback* _whereCallback; + virtual ~WhereCallback() {} }; /** - * This implementation is used for the server-side code. + * caller has to maintain ownership obj + * the tree has views (BSONElement) into obj */ - class WhereCallbackReal : public MatchExpressionParser::WhereCallback { - public: + static StatusWithMatchExpression parse(const BSONObj& obj, + const WhereCallback& whereCallback = WhereCallback()) { + // The 0 initializes the match expression tree depth. + return MatchExpressionParser(&whereCallback)._parse(obj, 0); + } - /** - * The OperationContext passed here is not owned, but just referenced. It gets assigned to - * any $where parsers, which this callback generates. Therefore, the op context must only - * be destroyed after these parsers and their clones (shallowClone) have been destroyed. - */ - WhereCallbackReal(OperationContext* txn, StringData dbName); +private: + explicit MatchExpressionParser(const WhereCallback* whereCallback) + : _whereCallback(whereCallback) {} - virtual StatusWithMatchExpression parseWhere(const BSONElement& where) const; + /** + * 5 = false + * { a : 5 } = false + * { $lt : 5 } = true + * { $ref: "s", $id: "x" } = false + * { $ref: "s", $id: "x", $db: "mydb" } = false + * { $ref : "s" } = false (if incomplete DBRef is allowed) + * { $id : "x" } = false (if incomplete DBRef is allowed) + * { $db : "mydb" } = false (if incomplete DBRef is allowed) + */ + bool _isExpressionDocument(const BSONElement& e, bool allowIncompleteDBRef); - private: - // Not owned here - OperationContext* const _txn; - const StringData _dbName; - }; + /** + * { $ref: "s", $id: "x" } = true + * { $ref : "s" } = true (if incomplete DBRef is allowed) + * { $id : "x" } = true (if incomplete DBRef is allowed) + * { $db : "x" } = true (if incomplete DBRef is allowed) + */ + bool _isDBRefDocument(const BSONObj& obj, bool allowIncompleteDBRef); /** - * This is just a pass-through implementation, used by sharding only. + * Parse 'obj' and return either a MatchExpression or an error. + * + * 'level' tracks the current depth of the tree across recursive calls to this + * function. Used in order to apply special logic at the top-level and to return an + * error if the tree exceeds the maximum allowed depth. */ - class WhereCallbackNoop : public MatchExpressionParser::WhereCallback { - public: - WhereCallbackNoop(); + StatusWithMatchExpression _parse(const BSONObj& obj, int level); - virtual StatusWithMatchExpression parseWhere(const BSONElement& where) const; - }; + /** + * parses a field in a sub expression + * if the query is { x : { $gt : 5, $lt : 8 } } + * e is { $gt : 5, $lt : 8 } + */ + Status _parseSub(const char* name, const BSONObj& obj, AndMatchExpression* root, int level); + + /** + * parses a single field in a sub expression + * if the query is { x : { $gt : 5, $lt : 8 } } + * e is $gt : 5 + */ + StatusWithMatchExpression _parseSubField(const BSONObj& context, + const AndMatchExpression* andSoFar, + const char* name, + const BSONElement& e, + int level); + + StatusWithMatchExpression _parseComparison(const char* name, + ComparisonMatchExpression* cmp, + const BSONElement& e); + + StatusWithMatchExpression _parseMOD(const char* name, const BSONElement& e); + + StatusWithMatchExpression _parseRegexElement(const char* name, const BSONElement& e); + + StatusWithMatchExpression _parseRegexDocument(const char* name, const BSONObj& doc); + + + Status _parseArrayFilterEntries(ArrayFilterEntries* entries, const BSONObj& theArray); + + // arrays + + StatusWithMatchExpression _parseElemMatch(const char* name, const BSONElement& e, int level); + + StatusWithMatchExpression _parseAll(const char* name, const BSONElement& e, int level); + + // tree + + Status _parseTreeList(const BSONObj& arr, ListOfMatchExpression* out, int level); + + StatusWithMatchExpression _parseNot(const char* name, const BSONElement& e, int level); + + // The maximum allowed depth of a query tree. Just to guard against stack overflow. + static const int kMaximumTreeDepth; + + // Performs parsing for the $where clause. We do not own this pointer - it has to live + // as long as the parser is active. + const WhereCallback* _whereCallback; +}; + +/** + * This implementation is used for the server-side code. + */ +class WhereCallbackReal : public MatchExpressionParser::WhereCallback { +public: + /** + * The OperationContext passed here is not owned, but just referenced. It gets assigned to + * any $where parsers, which this callback generates. Therefore, the op context must only + * be destroyed after these parsers and their clones (shallowClone) have been destroyed. + */ + WhereCallbackReal(OperationContext* txn, StringData dbName); + + virtual StatusWithMatchExpression parseWhere(const BSONElement& where) const; + +private: + // Not owned here + OperationContext* const _txn; + const StringData _dbName; +}; + +/** + * This is just a pass-through implementation, used by sharding only. + */ +class WhereCallbackNoop : public MatchExpressionParser::WhereCallback { +public: + WhereCallbackNoop(); + virtual StatusWithMatchExpression parseWhere(const BSONElement& where) const; +}; - typedef stdx::function<StatusWithMatchExpression(const char* name, int type, const BSONObj& section)> MatchExpressionParserGeoCallback; - extern MatchExpressionParserGeoCallback expressionParserGeoCallback; - typedef stdx::function<StatusWithMatchExpression(const BSONObj& queryObj)> MatchExpressionParserTextCallback; - extern MatchExpressionParserTextCallback expressionParserTextCallback; +typedef stdx::function<StatusWithMatchExpression( + const char* name, int type, const BSONObj& section)> MatchExpressionParserGeoCallback; +extern MatchExpressionParserGeoCallback expressionParserGeoCallback; +typedef stdx::function<StatusWithMatchExpression(const BSONObj& queryObj)> + MatchExpressionParserTextCallback; +extern MatchExpressionParserTextCallback expressionParserTextCallback; } diff --git a/src/mongo/db/matcher/expression_parser_array_test.cpp b/src/mongo/db/matcher/expression_parser_array_test.cpp index 26179f9b0c8..01bd6301d73 100644 --- a/src/mongo/db/matcher/expression_parser_array_test.cpp +++ b/src/mongo/db/matcher/expression_parser_array_test.cpp @@ -39,638 +39,664 @@ namespace mongo { - using std::string; - - TEST( MatchExpressionParserArrayTest, Size1 ) { - BSONObj query = BSON( "x" << BSON( "$size" << 2 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 << 3 ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, SizeAsString ) { - BSONObj query = BSON( "x" << BSON( "$size" << "a" ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSONArray() ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, SizeWithDouble ) { - BSONObj query = BSON( "x" << BSON( "$size" << 2.5 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSONArray() ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 << 3 ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, SizeBad ) { - BSONObj query = BSON( "x" << BSON( "$size" << BSONNULL ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - // --------- - - TEST( MatchExpressionParserArrayTest, ElemMatchArr1 ) { - BSONObj query = BSON( "x" << BSON( "$elemMatch" << BSON( "x" << 1 << "y" << 2 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSON( "x" << 1 ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << - BSON_ARRAY( BSON( "x" << 1 << "y" << 2 ) ) ) ) ); - delete result.getValue(); - - } - - TEST( MatchExpressionParserArrayTest, ElemMatchAnd ) { - BSONObj query = BSON( "x" << - BSON( "$elemMatch" << - BSON( "$and" << BSON_ARRAY( BSON( "x" << 1 << "y" << 2 ) ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSON( "x" << 1 ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << - BSON_ARRAY( BSON( "x" << 1 << "y" << 2 ) ) ) ) ); - delete result.getValue(); - - } - - TEST( MatchExpressionParserArrayTest, ElemMatchNor ) { - BSONObj query = BSON( "x" << - BSON( "$elemMatch" << - BSON( "$nor" << BSON_ARRAY( BSON( "x" << 1 ) ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSON( "x" << 1 ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << - BSON_ARRAY( BSON( "x" << 2 << "y" << 2 ) ) ) ) ); - delete result.getValue(); - - } - - TEST( MatchExpressionParserArrayTest, ElemMatchOr ) { - BSONObj query = BSON( "x" << - BSON( "$elemMatch" << - BSON( "$or" << BSON_ARRAY( BSON( "x" << 1 << "y" << 2 ) ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSON( "x" << 1 ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << - BSON_ARRAY( BSON( "x" << 1 << "y" << 2 ) ) ) ) ); - delete result.getValue(); - - } - - TEST( MatchExpressionParserArrayTest, ElemMatchVal1 ) { - BSONObj query = BSON( "x" << BSON( "$elemMatch" << BSON( "$gt" << 5 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 4 ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 6 ) ) ) ); - delete result.getValue(); - } - - // with explicit $eq - TEST( MatchExpressionParserArrayTest, ElemMatchDBRef1 ) { - OID oid = OID::gen(); - BSONObj match = BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ); - OID oidx = OID::gen(); - BSONObj notMatch = BSON( "$ref" << "coll" << "$id" << oidx << "$db" << "db" ); - - BSONObj query = BSON( "x" << BSON( "$elemMatch" << BSON( "$eq" << match ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << match ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( notMatch ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( match ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, ElemMatchDBRef2 ) { - OID oid = OID::gen(); - BSONObj match = BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ); - OID oidx = OID::gen(); - BSONObj notMatch = BSON( "$ref" << "coll" << "$id" << oidx << "$db" << "db" ); - - BSONObj query = BSON( "x" << BSON( "$elemMatch" << match ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << match ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( notMatch ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( match ) ) ) ); - delete result.getValue(); - } - - // Additional fields after $ref and $id. - TEST( MatchExpressionParserArrayTest, ElemMatchDBRef3 ) { - OID oid = OID::gen(); - BSONObj match = BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 ); - OID oidx = OID::gen(); - BSONObj notMatch = BSON( "$ref" << "coll" << "$id" << oidx << "foo" << 12345 ); - - BSONObj query = BSON( "x" << BSON( "$elemMatch" << match ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << match ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( notMatch ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( match ) ) ) ); - - // Document contains fields not referred to in $elemMatch query. - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 << "bar" << 678 ) ) ) ) ); - delete result.getValue(); - } - - // Query with DBRef fields out of order. - TEST( MatchExpressionParserArrayTest, ElemMatchDBRef4 ) { - OID oid = OID::gen(); - BSONObj match = BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ); - BSONObj matchOutOfOrder = BSON( "$db" << "db" << "$id" << oid << "$ref" << "coll" ); - OID oidx = OID::gen(); - BSONObj notMatch = BSON( "$ref" << "coll" << "$id" << oidx << "$db" << "db" ); - - BSONObj query = BSON( "x" << BSON( "$elemMatch" << matchOutOfOrder ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << match ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( notMatch ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( match ) ) ) ); - delete result.getValue(); - } - - // Query with DBRef fields out of order. - // Additional fields besides $ref and $id. - TEST( MatchExpressionParserArrayTest, ElemMatchDBRef5 ) { - OID oid = OID::gen(); - BSONObj match = BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 ); - BSONObj matchOutOfOrder = BSON( "foo" << 12345 << "$id" << oid << "$ref" << "coll" ); - OID oidx = OID::gen(); - BSONObj notMatch = BSON( "$ref" << "coll" << "$id" << oidx << "foo" << 12345 ); - - BSONObj query = BSON( "x" << BSON( "$elemMatch" << matchOutOfOrder ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << match ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( notMatch ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( match ) ) ) ); - - // Document contains fields not referred to in $elemMatch query. - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 << "bar" << 678 ) ) ) ) ); - delete result.getValue(); - } - - // Incomplete DBRef - $id missing. - TEST( MatchExpressionParserArrayTest, ElemMatchDBRef6 ) { - OID oid = OID::gen(); - BSONObj match = BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 ); - BSONObj matchMissingID = BSON( "$ref" << "coll" << "foo" << 12345 ); - BSONObj notMatch = BSON( "$ref" << "collx" << "$id" << oid << "foo" << 12345 ); - - BSONObj query = BSON( "x" << BSON( "$elemMatch" << matchMissingID ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << match ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( notMatch ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( match ) ) ) ); - - // Document contains fields not referred to in $elemMatch query. - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 << "bar" << 678 ) ) ) ) ); - delete result.getValue(); - } - - // Incomplete DBRef - $ref missing. - TEST( MatchExpressionParserArrayTest, ElemMatchDBRef7 ) { - OID oid = OID::gen(); - BSONObj match = BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 ); - BSONObj matchMissingRef = BSON( "$id" << oid << "foo" << 12345 ); - OID oidx = OID::gen(); - BSONObj notMatch = BSON( "$ref" << "coll" << "$id" << oidx << "foo" << 12345 ); - - BSONObj query = BSON( "x" << BSON( "$elemMatch" << matchMissingRef ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << match ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( notMatch ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( match ) ) ) ); - - // Document contains fields not referred to in $elemMatch query. - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 << "bar" << 678 ) ) ) ) ); - delete result.getValue(); - } - - // Incomplete DBRef - $db only. - TEST( MatchExpressionParserArrayTest, ElemMatchDBRef8 ) { - OID oid = OID::gen(); - BSONObj match = BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" - << "foo" << 12345 ); - BSONObj matchDBOnly = BSON( "$db" << "db" << "foo" << 12345 ); - BSONObj notMatch = BSON( "$ref" << "coll" << "$id" << oid << "$db" << "dbx" - << "foo" << 12345 ); - - BSONObj query = BSON( "x" << BSON( "$elemMatch" << matchDBOnly ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << match ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( notMatch ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( match ) ) ) ); - - // Document contains fields not referred to in $elemMatch query. - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" - << "foo" << 12345 << "bar" << 678 ) ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, All1 ) { - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( 1 << 2 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - // Verify that the $all got parsed to AND. - ASSERT_EQUALS( MatchExpression::AND, result.getValue()->matchType() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 2 ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 << 3 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 2 << 3 ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, AllNull ) { - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( BSONNULL ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - // Verify that the $all got parsed to AND. - ASSERT_EQUALS( MatchExpression::AND, result.getValue()->matchType() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSONNULL ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONNULL ) ) ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, AllBadArg ) { - BSONObj query = BSON( "x" << BSON( "$all" << 1 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - TEST( MatchExpressionParserArrayTest, AllBadRegexArg ) { - string tooLargePattern( 50 * 1000, 'z' ); - BSONObjBuilder allArray; - allArray.appendRegex( "0", tooLargePattern, "" ); - BSONObjBuilder operand; - operand.appendArray( "$all", allArray.obj() ); - - BSONObj query = BSON( "x" << operand.obj() ); - - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - - TEST( MatchExpressionParserArrayTest, AllRegex1 ) { - BSONObjBuilder allArray; - allArray.appendRegex( "0", "^a", "" ); - allArray.appendRegex( "1", "B", "i" ); - BSONObjBuilder all; - all.appendArray( "$all", allArray.obj() ); - BSONObj query = BSON( "a" << all.obj() ); - - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - // Verify that the $all got parsed to AND. - ASSERT_EQUALS( MatchExpression::AND, result.getValue()->matchType() ); - - BSONObj notMatchFirst = BSON( "a" << "ax" ); - BSONObj notMatchSecond = BSON( "a" << "qqb" ); - BSONObj matchesBoth = BSON( "a" << "ab" ); - - ASSERT( !result.getValue()->matchesSingleElement( notMatchFirst[ "a" ] ) ); - ASSERT( !result.getValue()->matchesSingleElement( notMatchSecond[ "a" ] ) ); - ASSERT( result.getValue()->matchesSingleElement( matchesBoth[ "a" ] ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, AllRegex2 ) { - BSONObjBuilder allArray; - allArray.appendRegex( "0", "^a", "" ); - allArray.append( "1", "abc" ); - BSONObjBuilder all; - all.appendArray( "$all", allArray.obj() ); - BSONObj query = BSON( "a" << all.obj() ); - - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - // Verify that the $all got parsed to AND. - ASSERT_EQUALS( MatchExpression::AND, result.getValue()->matchType() ); - - BSONObj notMatchFirst = BSON( "a" << "ax" ); - BSONObj matchesBoth = BSON( "a" << "abc" ); - - ASSERT( !result.getValue()->matchesSingleElement( notMatchFirst[ "a" ] ) ); - ASSERT( result.getValue()->matchesSingleElement( matchesBoth[ "a" ] ) ); - delete result.getValue(); - } - - TEST( MatchExpressionParserArrayTest, AllNonArray ) { - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( 5 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - // Verify that the $all got parsed to AND. - ASSERT_EQUALS( MatchExpression::AND, result.getValue()->matchType() ); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 5 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 5 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 4 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 4 ) ) ) ); - delete result.getValue(); - } - - - TEST( MatchExpressionParserArrayTest, AllElemMatch1 ) { - BSONObj internal = BSON( "x" << 1 << "y" << 2 ); - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( BSON( "$elemMatch" << internal ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - // Verify that the $all got parsed to an AND with a single ELEM_MATCH_OBJECT child. - ASSERT_EQUALS( MatchExpression::AND, result.getValue()->matchType() ); - ASSERT_EQUALS( 1U, result.getValue()->numChildren() ); - MatchExpression* child = result.getValue()->getChild( 0 ); - ASSERT_EQUALS( MatchExpression::ELEM_MATCH_OBJECT, child->matchType() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( 1 << 2 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSON( "x" << 1 ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << - BSON_ARRAY( BSON( "x" << 1 << "y" << 2 ) ) ) ) ); - delete result.getValue(); - - } - - // $all and $elemMatch on dotted field. - // Top level field can be either document or array. - TEST( MatchExpressionParserArrayTest, AllElemMatch2 ) { - BSONObj internal = BSON( "z" << 1 ); - BSONObj query = BSON( "x.y" << BSON( "$all" << - BSON_ARRAY( BSON( "$elemMatch" << internal ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - // Verify that the $all got parsed to an AND with a single ELEM_MATCH_OBJECT child. - ASSERT_EQUALS( MatchExpression::AND, result.getValue()->matchType() ); - ASSERT_EQUALS( 1U, result.getValue()->numChildren() ); - MatchExpression* child = result.getValue()->getChild( 0 ); - ASSERT_EQUALS( MatchExpression::ELEM_MATCH_OBJECT, child->matchType() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON( "y" << 1 ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON( "y" << - BSON_ARRAY( 1 << 2 ) ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << - BSON( "y" << - BSON_ARRAY( BSON( "x" << 1 ) ) ) ) ) ); - // x is a document. Internal document does not contain z. - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << - BSON( "y" << - BSON_ARRAY( - BSON( "x" << 1 << "y" << 1 ) ) ) ) ) ); - // x is an array. Internal document does not contain z. - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << - BSON_ARRAY( - BSON( "y" << - BSON_ARRAY( - BSON( "x" << 1 << "y" << 1 ) ) ) ) ) ) ); - // x is a document but y is not an array. - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << - BSON( "y" << - BSON( "x" << 1 << "z" << 1 ) ) ) ) ); - // x is an array but y is not an array. - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << - BSON_ARRAY( - BSON( "y" << - BSON( "x" << 1 << "z" << 1 ) ) ) ) ) ); - // x is a document. - ASSERT( result.getValue()->matchesBSON( BSON( "x" << - BSON( "y" << - BSON_ARRAY( - BSON( "x" << 1 << "z" << 1 ) ) ) ) ) ); - // x is an array. - ASSERT( result.getValue()->matchesBSON( BSON( "x" << - BSON_ARRAY( - BSON( "y" << - BSON_ARRAY( - BSON( "x" << 1 << "z" << 1 ) ) ) ) ) ) ); - delete result.getValue(); - } - - // Check the structure of the resulting MatchExpression, and make sure that the paths - // are correct. - TEST( MatchExpressionParserArrayTest, AllElemMatch3 ) { - BSONObj query = fromjson( "{x: {$all: [{$elemMatch: {y: 1, z: 1}}]}}" ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - std::unique_ptr<MatchExpression> expr( result.getValue() ); - - // Root node should be an AND with one child. - ASSERT_EQUALS( MatchExpression::AND, expr->matchType() ); - ASSERT_EQUALS( 1U, expr->numChildren() ); - - // Child should be an ELEM_MATCH_OBJECT with one child and path "x". - MatchExpression* emObject = expr->getChild( 0 ); - ASSERT_EQUALS( MatchExpression::ELEM_MATCH_OBJECT, emObject->matchType() ); - ASSERT_EQUALS( 1U, emObject->numChildren() ); - ASSERT_EQUALS( "x", emObject->path().toString() ); - - // Child should be another AND with two children. - MatchExpression* and2 = emObject->getChild( 0 ); - ASSERT_EQUALS( MatchExpression::AND, and2->matchType() ); - ASSERT_EQUALS( 2U, and2->numChildren() ); - - // Both children should be equalites, with paths "y" and "z". - MatchExpression* leaf1 = and2->getChild( 0 ); - ASSERT_EQUALS( MatchExpression::EQ, leaf1->matchType() ); - ASSERT_EQUALS( 0U, leaf1->numChildren() ); - ASSERT_EQUALS( "y", leaf1->path().toString() ); - MatchExpression* leaf2 = and2->getChild( 1 ); - ASSERT_EQUALS( MatchExpression::EQ, leaf2->matchType() ); - ASSERT_EQUALS( 0U, leaf2->numChildren() ); - ASSERT_EQUALS( "z", leaf2->path().toString() ); - } - - TEST( MatchExpressionParserArrayTest, AllElemMatchBad ) { - BSONObj internal = BSON( "x" << 1 << "y" << 2 ); - - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( BSON( "$elemMatch" << internal ) << 5 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - - query = BSON( "x" << BSON( "$all" << BSON_ARRAY( 5 << BSON( "$elemMatch" << internal ) ) ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - // You can't mix $elemMatch and regular equality inside $all. - TEST( MatchExpressionParserArrayTest, AllElemMatchBadMixed ) { - // $elemMatch first, equality second. - BSONObj bad1 = fromjson( "{x: {$all: [{$elemMatch: {y: 1}}, 3]}}" ); - StatusWithMatchExpression result1 = MatchExpressionParser::parse( bad1 ); - ASSERT_FALSE( result1.isOK() ); - - // equality first, $elemMatch second - BSONObj bad2 = fromjson( "{x: {$all: [3, {$elemMatch: {y: 1}}]}}" ); - StatusWithMatchExpression result2 = MatchExpressionParser::parse( bad2 ); - ASSERT_FALSE( result1.isOK() ); - - // $elemMatch first, object second - BSONObj bad3 = fromjson( "{x: {$all: [{$elemMatch: {y: 1}}, {z: 1}]}}" ); - StatusWithMatchExpression result3 = MatchExpressionParser::parse( bad3 ); - ASSERT_FALSE( result3.isOK() ); - - // object first, $elemMatch second - BSONObj bad4 = fromjson( "{x: {$all: [{z: 1}, {$elemMatch: {y: 1}}]}}" ); - StatusWithMatchExpression result4 = MatchExpressionParser::parse( bad4 ); - ASSERT_FALSE( result4.isOK() ); - } - - // $all with empty string. - TEST( MatchExpressionParserArrayTest, AllEmptyString ) { - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( "" ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "a" ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONNULL << "a" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONObj() << "a" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSONArray() ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << "" ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONNULL << "" ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONObj() << "" ) ) ) ); - delete result.getValue(); - } - - // $all with ISO date. - TEST( MatchExpressionParserArrayTest, AllISODate ) { - StatusWith<Date_t> matchResult = dateFromISOString("2014-12-31T00:00:00.000Z"); - ASSERT_TRUE( matchResult.isOK() ); - const Date_t& match = matchResult.getValue(); - StatusWith<Date_t> notMatchResult = dateFromISOString("2014-12-30T00:00:00.000Z"); - ASSERT_TRUE( notMatchResult.isOK() ); - const Date_t& notMatch = notMatchResult.getValue(); - - BSONObj query = BSON( "x" << BSON( "$all" << BSON_ARRAY( match ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << notMatch ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONNULL << - notMatch ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONObj() << - notMatch ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSONArray() ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << match ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONNULL << - match ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONObj() << - match ) ) ) ); - delete result.getValue(); - } - - // $all on array element with empty string. - TEST( MatchExpressionParserArrayTest, AllDottedEmptyString ) { - BSONObj query = BSON( "x.1" << BSON( "$all" << BSON_ARRAY( "" ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "a" ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONNULL << "a" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONObj() << "a" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( "" << BSONNULL ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( "" << BSONObj() ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSONArray() ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "" ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONNULL << "" ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONObj() << "" ) ) ) ); - delete result.getValue(); - } - - // $all on array element with ISO date. - TEST( MatchExpressionParserArrayTest, AllDottedISODate ) { - StatusWith<Date_t> matchResult = dateFromISOString("2014-12-31T00:00:00.000Z"); - ASSERT_TRUE( matchResult.isOK() ); - const Date_t& match = matchResult.getValue(); - StatusWith<Date_t> notMatchResult = dateFromISOString("2014-12-30T00:00:00.000Z"); - ASSERT_TRUE( notMatchResult.isOK() ); - const Date_t& notMatch = notMatchResult.getValue(); - - BSONObj query = BSON( "x.1" << BSON( "$all" << BSON_ARRAY( match ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << notMatch ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONNULL << - notMatch ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONObj() << - notMatch ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( match << - BSONNULL ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( match << - BSONObj() ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << BSONArray() ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << match ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONNULL << - match ) ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << BSON_ARRAY( BSONObj() << - match ) ) ) ); - delete result.getValue(); - } +using std::string; + +TEST(MatchExpressionParserArrayTest, Size1) { + BSONObj query = BSON("x" << BSON("$size" << 2)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, SizeAsString) { + BSONObj query = BSON("x" << BSON("$size" + << "a")); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSONArray()))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, SizeWithDouble) { + BSONObj query = BSON("x" << BSON("$size" << 2.5)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSONArray()))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, SizeBad) { + BSONObj query = BSON("x" << BSON("$size" << BSONNULL)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +// --------- + +TEST(MatchExpressionParserArrayTest, ElemMatchArr1) { + BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("x" << 1 << "y" << 2))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1))))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, ElemMatchAnd) { + BSONObj query = + BSON("x" << BSON("$elemMatch" << BSON("$and" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1))))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, ElemMatchNor) { + BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("$nor" << BSON_ARRAY(BSON("x" << 1))))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1))))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 2 << "y" << 2))))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, ElemMatchOr) { + BSONObj query = + BSON("x" << BSON("$elemMatch" << BSON("$or" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1))))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, ElemMatchVal1) { + BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("$gt" << 5))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(4)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(6)))); + delete result.getValue(); +} + +// with explicit $eq +TEST(MatchExpressionParserArrayTest, ElemMatchDBRef1) { + OID oid = OID::gen(); + BSONObj match = BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db"); + OID oidx = OID::gen(); + BSONObj notMatch = BSON("$ref" + << "coll" + << "$id" << oidx << "$db" + << "db"); + + BSONObj query = BSON("x" << BSON("$elemMatch" << BSON("$eq" << match))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, ElemMatchDBRef2) { + OID oid = OID::gen(); + BSONObj match = BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db"); + OID oidx = OID::gen(); + BSONObj notMatch = BSON("$ref" + << "coll" + << "$id" << oidx << "$db" + << "db"); + + BSONObj query = BSON("x" << BSON("$elemMatch" << match)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); + delete result.getValue(); +} + +// Additional fields after $ref and $id. +TEST(MatchExpressionParserArrayTest, ElemMatchDBRef3) { + OID oid = OID::gen(); + BSONObj match = BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345); + OID oidx = OID::gen(); + BSONObj notMatch = BSON("$ref" + << "coll" + << "$id" << oidx << "foo" << 12345); + + BSONObj query = BSON("x" << BSON("$elemMatch" << match)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); + + // Document contains fields not referred to in $elemMatch query. + ASSERT(result.getValue()->matchesBSON( + BSON("x" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345 << "bar" << 678))))); + delete result.getValue(); +} + +// Query with DBRef fields out of order. +TEST(MatchExpressionParserArrayTest, ElemMatchDBRef4) { + OID oid = OID::gen(); + BSONObj match = BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db"); + BSONObj matchOutOfOrder = BSON("$db" + << "db" + << "$id" << oid << "$ref" + << "coll"); + OID oidx = OID::gen(); + BSONObj notMatch = BSON("$ref" + << "coll" + << "$id" << oidx << "$db" + << "db"); + + BSONObj query = BSON("x" << BSON("$elemMatch" << matchOutOfOrder)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); + delete result.getValue(); +} + +// Query with DBRef fields out of order. +// Additional fields besides $ref and $id. +TEST(MatchExpressionParserArrayTest, ElemMatchDBRef5) { + OID oid = OID::gen(); + BSONObj match = BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345); + BSONObj matchOutOfOrder = BSON("foo" << 12345 << "$id" << oid << "$ref" + << "coll"); + OID oidx = OID::gen(); + BSONObj notMatch = BSON("$ref" + << "coll" + << "$id" << oidx << "foo" << 12345); + + BSONObj query = BSON("x" << BSON("$elemMatch" << matchOutOfOrder)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); + + // Document contains fields not referred to in $elemMatch query. + ASSERT(result.getValue()->matchesBSON( + BSON("x" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345 << "bar" << 678))))); + delete result.getValue(); +} + +// Incomplete DBRef - $id missing. +TEST(MatchExpressionParserArrayTest, ElemMatchDBRef6) { + OID oid = OID::gen(); + BSONObj match = BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345); + BSONObj matchMissingID = BSON("$ref" + << "coll" + << "foo" << 12345); + BSONObj notMatch = BSON("$ref" + << "collx" + << "$id" << oid << "foo" << 12345); + + BSONObj query = BSON("x" << BSON("$elemMatch" << matchMissingID)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); + + // Document contains fields not referred to in $elemMatch query. + ASSERT(result.getValue()->matchesBSON( + BSON("x" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345 << "bar" << 678))))); + delete result.getValue(); +} + +// Incomplete DBRef - $ref missing. +TEST(MatchExpressionParserArrayTest, ElemMatchDBRef7) { + OID oid = OID::gen(); + BSONObj match = BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345); + BSONObj matchMissingRef = BSON("$id" << oid << "foo" << 12345); + OID oidx = OID::gen(); + BSONObj notMatch = BSON("$ref" + << "coll" + << "$id" << oidx << "foo" << 12345); + + BSONObj query = BSON("x" << BSON("$elemMatch" << matchMissingRef)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); + + // Document contains fields not referred to in $elemMatch query. + ASSERT(result.getValue()->matchesBSON( + BSON("x" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345 << "bar" << 678))))); + delete result.getValue(); +} + +// Incomplete DBRef - $db only. +TEST(MatchExpressionParserArrayTest, ElemMatchDBRef8) { + OID oid = OID::gen(); + BSONObj match = BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db" + << "foo" << 12345); + BSONObj matchDBOnly = BSON("$db" + << "db" + << "foo" << 12345); + BSONObj notMatch = BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "dbx" + << "foo" << 12345); + + BSONObj query = BSON("x" << BSON("$elemMatch" << matchDBOnly)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(notMatch)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match)))); + + // Document contains fields not referred to in $elemMatch query. + ASSERT(result.getValue()->matchesBSON( + BSON("x" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db" + << "foo" << 12345 << "bar" << 678))))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, All1) { + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(1 << 2))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + // Verify that the $all got parsed to AND. + ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(2)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(2 << 3)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, AllNull) { + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(BSONNULL))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + // Verify that the $all got parsed to AND. + ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSONNULL))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, AllBadArg) { + BSONObj query = BSON("x" << BSON("$all" << 1)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserArrayTest, AllBadRegexArg) { + string tooLargePattern(50 * 1000, 'z'); + BSONObjBuilder allArray; + allArray.appendRegex("0", tooLargePattern, ""); + BSONObjBuilder operand; + operand.appendArray("$all", allArray.obj()); + + BSONObj query = BSON("x" << operand.obj()); + + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + + +TEST(MatchExpressionParserArrayTest, AllRegex1) { + BSONObjBuilder allArray; + allArray.appendRegex("0", "^a", ""); + allArray.appendRegex("1", "B", "i"); + BSONObjBuilder all; + all.appendArray("$all", allArray.obj()); + BSONObj query = BSON("a" << all.obj()); + + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + // Verify that the $all got parsed to AND. + ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); + + BSONObj notMatchFirst = BSON("a" + << "ax"); + BSONObj notMatchSecond = BSON("a" + << "qqb"); + BSONObj matchesBoth = BSON("a" + << "ab"); + + ASSERT(!result.getValue()->matchesSingleElement(notMatchFirst["a"])); + ASSERT(!result.getValue()->matchesSingleElement(notMatchSecond["a"])); + ASSERT(result.getValue()->matchesSingleElement(matchesBoth["a"])); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, AllRegex2) { + BSONObjBuilder allArray; + allArray.appendRegex("0", "^a", ""); + allArray.append("1", "abc"); + BSONObjBuilder all; + all.appendArray("$all", allArray.obj()); + BSONObj query = BSON("a" << all.obj()); + + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + // Verify that the $all got parsed to AND. + ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); + + BSONObj notMatchFirst = BSON("a" + << "ax"); + BSONObj matchesBoth = BSON("a" + << "abc"); + + ASSERT(!result.getValue()->matchesSingleElement(notMatchFirst["a"])); + ASSERT(result.getValue()->matchesSingleElement(matchesBoth["a"])); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, AllNonArray) { + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(5))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + // Verify that the $all got parsed to AND. + ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" << 5))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(5)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 4))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(4)))); + delete result.getValue(); +} + +TEST(MatchExpressionParserArrayTest, AllElemMatch1) { + BSONObj internal = BSON("x" << 1 << "y" << 2); + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(BSON("$elemMatch" << internal)))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + // Verify that the $all got parsed to an AND with a single ELEM_MATCH_OBJECT child. + ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); + ASSERT_EQUALS(1U, result.getValue()->numChildren()); + MatchExpression* child = result.getValue()->getChild(0); + ASSERT_EQUALS(MatchExpression::ELEM_MATCH_OBJECT, child->matchType()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1))))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("x" << 1 << "y" << 2))))); + delete result.getValue(); +} + +// $all and $elemMatch on dotted field. +// Top level field can be either document or array. +TEST(MatchExpressionParserArrayTest, AllElemMatch2) { + BSONObj internal = BSON("z" << 1); + BSONObj query = BSON("x.y" << BSON("$all" << BSON_ARRAY(BSON("$elemMatch" << internal)))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + // Verify that the $all got parsed to an AND with a single ELEM_MATCH_OBJECT child. + ASSERT_EQUALS(MatchExpression::AND, result.getValue()->matchType()); + ASSERT_EQUALS(1U, result.getValue()->numChildren()); + MatchExpression* child = result.getValue()->getChild(0); + ASSERT_EQUALS(MatchExpression::ELEM_MATCH_OBJECT, child->matchType()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("y" << 1)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("y" << BSON_ARRAY(1 << 2))))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("y" << BSON_ARRAY(BSON("x" << 1)))))); + // x is a document. Internal document does not contain z. + ASSERT(!result.getValue()->matchesBSON( + BSON("x" << BSON("y" << BSON_ARRAY(BSON("x" << 1 << "y" << 1)))))); + // x is an array. Internal document does not contain z. + ASSERT(!result.getValue()->matchesBSON( + BSON("x" << BSON_ARRAY(BSON("y" << BSON_ARRAY(BSON("x" << 1 << "y" << 1))))))); + // x is a document but y is not an array. + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("y" << BSON("x" << 1 << "z" << 1))))); + // x is an array but y is not an array. + ASSERT(!result.getValue()->matchesBSON( + BSON("x" << BSON_ARRAY(BSON("y" << BSON("x" << 1 << "z" << 1)))))); + // x is a document. + ASSERT(result.getValue()->matchesBSON( + BSON("x" << BSON("y" << BSON_ARRAY(BSON("x" << 1 << "z" << 1)))))); + // x is an array. + ASSERT(result.getValue()->matchesBSON( + BSON("x" << BSON_ARRAY(BSON("y" << BSON_ARRAY(BSON("x" << 1 << "z" << 1))))))); + delete result.getValue(); +} + +// Check the structure of the resulting MatchExpression, and make sure that the paths +// are correct. +TEST(MatchExpressionParserArrayTest, AllElemMatch3) { + BSONObj query = fromjson("{x: {$all: [{$elemMatch: {y: 1, z: 1}}]}}"); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + std::unique_ptr<MatchExpression> expr(result.getValue()); + + // Root node should be an AND with one child. + ASSERT_EQUALS(MatchExpression::AND, expr->matchType()); + ASSERT_EQUALS(1U, expr->numChildren()); + + // Child should be an ELEM_MATCH_OBJECT with one child and path "x". + MatchExpression* emObject = expr->getChild(0); + ASSERT_EQUALS(MatchExpression::ELEM_MATCH_OBJECT, emObject->matchType()); + ASSERT_EQUALS(1U, emObject->numChildren()); + ASSERT_EQUALS("x", emObject->path().toString()); + + // Child should be another AND with two children. + MatchExpression* and2 = emObject->getChild(0); + ASSERT_EQUALS(MatchExpression::AND, and2->matchType()); + ASSERT_EQUALS(2U, and2->numChildren()); + + // Both children should be equalites, with paths "y" and "z". + MatchExpression* leaf1 = and2->getChild(0); + ASSERT_EQUALS(MatchExpression::EQ, leaf1->matchType()); + ASSERT_EQUALS(0U, leaf1->numChildren()); + ASSERT_EQUALS("y", leaf1->path().toString()); + MatchExpression* leaf2 = and2->getChild(1); + ASSERT_EQUALS(MatchExpression::EQ, leaf2->matchType()); + ASSERT_EQUALS(0U, leaf2->numChildren()); + ASSERT_EQUALS("z", leaf2->path().toString()); +} + +TEST(MatchExpressionParserArrayTest, AllElemMatchBad) { + BSONObj internal = BSON("x" << 1 << "y" << 2); + + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(BSON("$elemMatch" << internal) << 5))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); + + query = BSON("x" << BSON("$all" << BSON_ARRAY(5 << BSON("$elemMatch" << internal)))); + result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +// You can't mix $elemMatch and regular equality inside $all. +TEST(MatchExpressionParserArrayTest, AllElemMatchBadMixed) { + // $elemMatch first, equality second. + BSONObj bad1 = fromjson("{x: {$all: [{$elemMatch: {y: 1}}, 3]}}"); + StatusWithMatchExpression result1 = MatchExpressionParser::parse(bad1); + ASSERT_FALSE(result1.isOK()); + + // equality first, $elemMatch second + BSONObj bad2 = fromjson("{x: {$all: [3, {$elemMatch: {y: 1}}]}}"); + StatusWithMatchExpression result2 = MatchExpressionParser::parse(bad2); + ASSERT_FALSE(result1.isOK()); + + // $elemMatch first, object second + BSONObj bad3 = fromjson("{x: {$all: [{$elemMatch: {y: 1}}, {z: 1}]}}"); + StatusWithMatchExpression result3 = MatchExpressionParser::parse(bad3); + ASSERT_FALSE(result3.isOK()); + + // object first, $elemMatch second + BSONObj bad4 = fromjson("{x: {$all: [{z: 1}, {$elemMatch: {y: 1}}]}}"); + StatusWithMatchExpression result4 = MatchExpressionParser::parse(bad4); + ASSERT_FALSE(result4.isOK()); +} + +// $all with empty string. +TEST(MatchExpressionParserArrayTest, AllEmptyString) { + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(""))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" + << "a"))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << "a")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << "a")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSONArray()))); + ASSERT(result.getValue()->matchesBSON(BSON("x" + << ""))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << "")))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << "")))); + delete result.getValue(); +} + +// $all with ISO date. +TEST(MatchExpressionParserArrayTest, AllISODate) { + StatusWith<Date_t> matchResult = dateFromISOString("2014-12-31T00:00:00.000Z"); + ASSERT_TRUE(matchResult.isOK()); + const Date_t& match = matchResult.getValue(); + StatusWith<Date_t> notMatchResult = dateFromISOString("2014-12-30T00:00:00.000Z"); + ASSERT_TRUE(notMatchResult.isOK()); + const Date_t& notMatch = notMatchResult.getValue(); + + BSONObj query = BSON("x" << BSON("$all" << BSON_ARRAY(match))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << notMatch))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << notMatch)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << notMatch)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSONArray()))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << match))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << match)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << match)))); + delete result.getValue(); +} + +// $all on array element with empty string. +TEST(MatchExpressionParserArrayTest, AllDottedEmptyString) { + BSONObj query = BSON("x.1" << BSON("$all" << BSON_ARRAY(""))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" + << "a"))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << "a")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << "a")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY("" << BSONNULL)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY("" << BSONObj())))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSONArray()))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" + << ""))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << "")))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << "")))); + delete result.getValue(); +} + +// $all on array element with ISO date. +TEST(MatchExpressionParserArrayTest, AllDottedISODate) { + StatusWith<Date_t> matchResult = dateFromISOString("2014-12-31T00:00:00.000Z"); + ASSERT_TRUE(matchResult.isOK()); + const Date_t& match = matchResult.getValue(); + StatusWith<Date_t> notMatchResult = dateFromISOString("2014-12-30T00:00:00.000Z"); + ASSERT_TRUE(notMatchResult.isOK()); + const Date_t& notMatch = notMatchResult.getValue(); + + BSONObj query = BSON("x.1" << BSON("$all" << BSON_ARRAY(match))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << notMatch))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << notMatch)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << notMatch)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match << BSONNULL)))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(match << BSONObj())))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSONArray()))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << match))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONNULL << match)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSONObj() << match)))); + delete result.getValue(); +} } diff --git a/src/mongo/db/matcher/expression_parser_geo.cpp b/src/mongo/db/matcher/expression_parser_geo.cpp index 837d61b448c..f740a93abac 100644 --- a/src/mongo/db/matcher/expression_parser_geo.cpp +++ b/src/mongo/db/matcher/expression_parser_geo.cpp @@ -37,52 +37,51 @@ namespace mongo { - using std::unique_ptr; +using std::unique_ptr; - StatusWithMatchExpression expressionParserGeoCallbackReal( const char* name, - int type, - const BSONObj& section ) { - if (BSONObj::opWITHIN == type || BSONObj::opGEO_INTERSECTS == type) { - unique_ptr<GeoExpression> gq(new GeoExpression(name)); - Status parseStatus = gq->parseFrom(section); +StatusWithMatchExpression expressionParserGeoCallbackReal(const char* name, + int type, + const BSONObj& section) { + if (BSONObj::opWITHIN == type || BSONObj::opGEO_INTERSECTS == type) { + unique_ptr<GeoExpression> gq(new GeoExpression(name)); + Status parseStatus = gq->parseFrom(section); - if (!parseStatus.isOK()) return StatusWithMatchExpression(parseStatus); + if (!parseStatus.isOK()) + return StatusWithMatchExpression(parseStatus); - unique_ptr<GeoMatchExpression> e( new GeoMatchExpression() ); + unique_ptr<GeoMatchExpression> e(new GeoMatchExpression()); - // Until the index layer accepts non-BSON predicates, or special indices are moved into - // stages, we have to clean up the raw object so it can be passed down to the index - // layer. - BSONObjBuilder bob; - bob.append(name, section); - Status s = e->init( name, gq.release(), bob.obj() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( e.release() ); + // Until the index layer accepts non-BSON predicates, or special indices are moved into + // stages, we have to clean up the raw object so it can be passed down to the index + // layer. + BSONObjBuilder bob; + bob.append(name, section); + Status s = e->init(name, gq.release(), bob.obj()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(e.release()); + } else { + verify(BSONObj::opNEAR == type); + unique_ptr<GeoNearExpression> nq(new GeoNearExpression(name)); + Status s = nq->parseFrom(section); + if (!s.isOK()) { + return StatusWithMatchExpression(s); } - else { - verify(BSONObj::opNEAR == type); - unique_ptr<GeoNearExpression> nq(new GeoNearExpression(name)); - Status s = nq->parseFrom( section ); - if ( !s.isOK() ) { - return StatusWithMatchExpression( s ); - } - unique_ptr<GeoNearMatchExpression> e( new GeoNearMatchExpression() ); - // Until the index layer accepts non-BSON predicates, or special indices are moved into - // stages, we have to clean up the raw object so it can be passed down to the index - // layer. - BSONObjBuilder bob; - bob.append(name, section); - s = e->init( name, nq.release(), bob.obj() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( e.release() ); - } - } - - MONGO_INITIALIZER( MatchExpressionParserGeo )( ::mongo::InitializerContext* context ) { - expressionParserGeoCallback = expressionParserGeoCallbackReal; - return Status::OK(); + unique_ptr<GeoNearMatchExpression> e(new GeoNearMatchExpression()); + // Until the index layer accepts non-BSON predicates, or special indices are moved into + // stages, we have to clean up the raw object so it can be passed down to the index + // layer. + BSONObjBuilder bob; + bob.append(name, section); + s = e->init(name, nq.release(), bob.obj()); + 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 index 503958ddbab..f86366754a9 100644 --- a/src/mongo/db/matcher/expression_parser_geo_test.cpp +++ b/src/mongo/db/matcher/expression_parser_geo_test.cpp @@ -39,42 +39,43 @@ namespace mongo { - TEST( MatchExpressionParserGeo, WithinBox ) { - BSONObj query = fromjson("{a:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); +TEST(MatchExpressionParserGeo, WithinBox) { + BSONObj query = fromjson("{a:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); - ASSERT(!result.getValue()->matchesBSON(fromjson("{a: [3,4]}"))); - ASSERT(result.getValue()->matchesBSON(fromjson("{a: [4,4]}"))); - ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5]}"))); - ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5.1]}"))); - ASSERT(result.getValue()->matchesBSON(fromjson("{a: {x: 5, y:5.1}}"))); - - } + ASSERT(!result.getValue()->matchesBSON(fromjson("{a: [3,4]}"))); + ASSERT(result.getValue()->matchesBSON(fromjson("{a: [4,4]}"))); + ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5]}"))); + ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5.1]}"))); + ASSERT(result.getValue()->matchesBSON(fromjson("{a: {x: 5, y:5.1}}"))); +} - TEST( MatchExpressionParserGeoNear, ParseNear ) { - BSONObj query = fromjson("{loc:{$near:{$maxDistance:100, " - "$geometry:{type:\"Point\", coordinates:[0,0]}}}}"); +TEST(MatchExpressionParserGeoNear, ParseNear) { + BSONObj query = fromjson( + "{loc:{$near:{$maxDistance:100, " + "$geometry:{type:\"Point\", coordinates:[0,0]}}}}"); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); - MatchExpression* exp = result.getValue(); - ASSERT_EQUALS(MatchExpression::GEO_NEAR, exp->matchType()); + MatchExpression* exp = result.getValue(); + ASSERT_EQUALS(MatchExpression::GEO_NEAR, exp->matchType()); - GeoNearMatchExpression* gnexp = static_cast<GeoNearMatchExpression*>(exp); - ASSERT_EQUALS(gnexp->getData().maxDistance, 100); - } + GeoNearMatchExpression* gnexp = static_cast<GeoNearMatchExpression*>(exp); + ASSERT_EQUALS(gnexp->getData().maxDistance, 100); +} - // $near must be the only field in the expression object. - TEST( MatchExpressionParserGeoNear, ParseNearExtraField ) { - BSONObj query = fromjson("{loc:{$near:{$maxDistance:100, " - "$geometry:{type:\"Point\", coordinates:[0,0]}}, foo: 1}}"); +// $near must be the only field in the expression object. +TEST(MatchExpressionParserGeoNear, ParseNearExtraField) { + BSONObj query = fromjson( + "{loc:{$near:{$maxDistance:100, " + "$geometry:{type:\"Point\", coordinates:[0,0]}}, foo: 1}}"); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} } diff --git a/src/mongo/db/matcher/expression_parser_leaf_test.cpp b/src/mongo/db/matcher/expression_parser_leaf_test.cpp index 10655322580..7660fa92b3b 100644 --- a/src/mongo/db/matcher/expression_parser_leaf_test.cpp +++ b/src/mongo/db/matcher/expression_parser_leaf_test.cpp @@ -42,528 +42,623 @@ namespace mongo { - using std::endl; - using std::string; - - TEST( MatchExpressionParserLeafTest, SimpleEQ2 ) { - BSONObj query = BSON( "x" << BSON( "$eq" << 2 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, SimpleEQUndefined ) { - BSONObj query = BSON( "x" << BSON( "$eq" << BSONUndefined ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - TEST( MatchExpressionParserLeafTest, SimpleGT1 ) { - BSONObj query = BSON( "x" << BSON( "$gt" << 2 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, SimpleLT1 ) { - BSONObj query = BSON( "x" << BSON( "$lt" << 2 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, SimpleGTE1 ) { - BSONObj query = BSON( "x" << BSON( "$gte" << 2 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, SimpleLTE1 ) { - BSONObj query = BSON( "x" << BSON( "$lte" << 2 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, SimpleNE1 ) { - BSONObj query = BSON( "x" << BSON( "$ne" << 2 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, SimpleModBad1 ) { - BSONObj query = BSON( "x" << BSON( "$mod" << BSON_ARRAY( 3 << 2 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy1(result.getValue()); - - query = BSON( "x" << BSON( "$mod" << BSON_ARRAY( 3 ) ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( !result.isOK() ); - - query = BSON( "x" << BSON( "$mod" << BSON_ARRAY( 3 << 2 << 4 ) ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( !result.isOK() ); - - query = BSON( "x" << BSON( "$mod" << BSON_ARRAY( "q" << 2 ) ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( !result.isOK() ); - - query = BSON( "x" << BSON( "$mod" << 3 ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( !result.isOK() ); - - query = BSON( "x" << BSON( "$mod" << BSON( "a" << 1 << "b" << 2 ) ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( !result.isOK() ); - } - - TEST( MatchExpressionParserLeafTest, SimpleMod1 ) { - BSONObj query = BSON( "x" << BSON( "$mod" << BSON_ARRAY( 3 << 2 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 5 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 4 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 8 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, SimpleModNotNumber ) { - BSONObj query = BSON( "x" << BSON( "$mod" << BSON_ARRAY( 2 << "r" ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 4 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "a" ) ) ); - } - - - TEST( MatchExpressionParserLeafTest, SimpleIN1 ) { - BSONObj query = BSON( "x" << BSON( "$in" << BSON_ARRAY( 2 << 3 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, INSingleDBRef ) { - OID oid = OID::gen(); - BSONObj query = - BSON( "x" << BSON( "$in" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - OID oidx = OID::gen(); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON( "$ref" << "collx" << "$id" << oidx << "$db" << "db" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON( "$ref" << "coll" << "$id" << oidx << "$db" << "db" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON( "$id" << oid << "$ref" << "coll" << "$db" << "db" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON( "$id" << oid << "$ref" << "coll" << "$db" << "db" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$id" << oid << "$ref" << "coll" << "$db" << "db" ) ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON( "$ref" << "coll" << "$id" << oid << "$db" << "dbx" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON( "$db" << "db" << "$ref" << "coll" << "$id" << oid ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "collx" << "$id" << oidx << "$db" << "db" ) << - BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ) ) ) ) ); - } - - TEST( MatchExpressionParserLeafTest, INMultipleDBRef ) { - OID oid = OID::gen(); - OID oidy = OID::gen(); - BSONObj query = BSON( "x" << BSON( "$in" << BSON_ARRAY( - BSON( "$ref" << "colly" << "$id" << oidy << "$db" << "db" ) << - BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - OID oidx = OID::gen(); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON( "$ref" << "collx" << "$id" << oidx << "$db" << "db" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON( "$ref" << "coll" << "$id" << oidx << "$db" << "db" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON( "$id" << oid << "$ref" << "coll" << "$db" << "db" ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oidy << "$db" << "db" ) ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "colly" << "$id" << oid << "$db" << "db" ) ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$id" << oid << "$ref" << "coll" << "$db" << "db" ) ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "$db" << "dbx" ) ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$id" << oidy << "$ref" << "colly" << "$db" << "db" ) ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "collx" << "$id" << oidx << "$db" << "db" ) << - BSON( "$ref" << "coll" << "$id" << oidx << "$db" << "db" ) ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "collx" << "$id" << oidx << "$db" << "db" ) << - BSON( "$ref" << "colly" << "$id" << oidx << "$db" << "db" ) ) ) ) ); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "collx" << "$id" << oidx << "$db" << "db" ) << - BSON( "$ref" << "coll" << "$id" << oid << "$db" << "dbx" ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON( "$ref" << "colly" << "$id" << oidy << "$db" << "db" ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "colly" << "$id" << oidy << "$db" << "db" ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "collx" << "$id" << oidx << "$db" << "db" ) << - BSON( "$ref" << "coll" << "$id" << oid << "$db" << "db" ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "collx" << "$id" << oidx << "$db" << "db" ) << - BSON( "$ref" << "colly" << "$id" << oidy << "$db" << "db" ) ) ) ) ); - } - - TEST( MatchExpressionParserLeafTest, INDBRefWithOptionalField1 ) { - OID oid = OID::gen(); - BSONObj query = - BSON( "x" << BSON( "$in" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - OID oidx = OID::gen(); - ASSERT( !result.getValue()->matchesBSON( - BSON( "x" << BSON( "$ref" << "coll" << "$id" << oidx << "$db" << "db" ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 ) ) ) ) ); - ASSERT( result.getValue()->matchesBSON( - BSON( "x" << BSON_ARRAY( - BSON( "$ref" << "collx" << "$id" << oidx << "foo" << 12345 ) << - BSON( "$ref" << "coll" << "$id" << oid << "foo" << 12345 ) ) ) ) ); - } - - TEST( MatchExpressionParserLeafTest, INInvalidDBRefs ) { - // missing $id - BSONObj query = BSON( "x" << BSON( "$in" << BSON_ARRAY( - BSON( "$ref" << "coll" ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - result = MatchExpressionParser::parse( query ); - - // second field is not $id - query = BSON( "x" << BSON( "$in" << BSON_ARRAY( - BSON( "$ref" << "coll" << - "$foo" << 1 ) ) ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - - OID oid = OID::gen(); - - // missing $ref field - query = BSON( "x" << BSON( "$in" << BSON_ARRAY( - BSON( "$id" << oid << - "foo" << 3 ) ) ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - - // missing $id and $ref field - query = BSON( "x" << BSON( "$in" << BSON_ARRAY( - BSON( "$db" << "test" << - "foo" << 3 ) ) ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - - } - - TEST( MatchExpressionParserLeafTest, INExpressionDocument ) { - BSONObj query = BSON( "x" << BSON( "$in" << BSON_ARRAY( BSON( "$foo" << 1 ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - TEST( MatchExpressionParserLeafTest, INNotArray ) { - BSONObj query = BSON( "x" << BSON( "$in" << 5 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - TEST( MatchExpressionParserLeafTest, INUndefined ) { - BSONObj query = BSON( "x" << BSON( "$in" << BSON_ARRAY( BSONUndefined ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - TEST( MatchExpressionParserLeafTest, INNotElemMatch ) { - BSONObj query = BSON( "x" << BSON( "$in" << BSON_ARRAY( BSON( "$elemMatch" << 1 ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - TEST( MatchExpressionParserLeafTest, INRegexTooLong ) { - string tooLargePattern( 50 * 1000, 'z' ); - BSONObjBuilder inArray; - inArray.appendRegex( "0", tooLargePattern, "" ); - BSONObjBuilder operand; - operand.appendArray( "$in", inArray.obj() ); - BSONObj query = BSON( "x" << operand.obj() ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - TEST( MatchExpressionParserLeafTest, INRegexTooLong2 ) { - string tooLargePattern( 50 * 1000, 'z' ); - BSONObj query = BSON( "x" << BSON( "$in" << BSON_ARRAY( BSON( "$regex" << tooLargePattern ) ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - TEST( MatchExpressionParserLeafTest, INRegexStuff ) { - BSONObjBuilder inArray; - inArray.appendRegex( "0", "^a", "" ); - inArray.appendRegex( "1", "B", "i" ); - inArray.append( "2", 4 ); - BSONObjBuilder operand; - operand.appendArray( "$in", inArray.obj() ); - - BSONObj query = BSON( "a" << operand.obj() ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - BSONObj matchFirst = BSON( "a" << "ax" ); - BSONObj matchFirstRegex = BSONObjBuilder().appendRegex( "a", "^a", "" ).obj(); - BSONObj matchSecond = BSON( "a" << "qqb" ); - BSONObj matchSecondRegex = BSONObjBuilder().appendRegex( "a", "B", "i" ).obj(); - BSONObj matchThird = BSON( "a" << 4 ); - BSONObj notMatch = BSON( "a" << "l" ); - BSONObj notMatchRegex = BSONObjBuilder().appendRegex( "a", "B", "" ).obj(); - - ASSERT( result.getValue()->matchesBSON( matchFirst ) ); - ASSERT( result.getValue()->matchesBSON( matchFirstRegex ) ); - ASSERT( result.getValue()->matchesBSON( matchSecond ) ); - ASSERT( result.getValue()->matchesBSON( matchSecondRegex ) ); - ASSERT( result.getValue()->matchesBSON( matchThird ) ); - ASSERT( !result.getValue()->matchesBSON( notMatch ) ); - ASSERT( !result.getValue()->matchesBSON( notMatchRegex ) ); - } - - TEST( MatchExpressionParserLeafTest, SimpleNIN1 ) { - BSONObj query = BSON( "x" << BSON( "$nin" << BSON_ARRAY( 2 << 3 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, NINNotArray ) { - BSONObj query = BSON( "x" << BSON( "$nin" << 5 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - - TEST( MatchExpressionParserLeafTest, Regex1 ) { - BSONObjBuilder b; - b.appendRegex( "x", "abc", "i" ); - BSONObj query = b.obj(); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << "abc" ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << "ABC" ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "AC" ) ) ); - } - - TEST( MatchExpressionParserLeafTest, Regex2 ) { - BSONObj query = BSON( "x" << BSON( "$regex" << "abc" << "$options" << "i" ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << "abc" ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << "ABC" ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "AC" ) ) ); - } - - TEST( MatchExpressionParserLeafTest, Regex3 ) { - BSONObj query = BSON( "x" << BSON( "$options" << "i" << "$regex" << "abc" ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - log() << "result: " << result.getStatus() << endl; - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << "abc" ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << "ABC" ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "AC" ) ) ); - } - - - TEST( MatchExpressionParserLeafTest, RegexBad ) { - BSONObj query = BSON( "x" << BSON( "$regex" << "abc" << "$optionas" << "i" ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - - // $regex does not with numbers - query = BSON( "x" << BSON( "$regex" << 123 ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - - query = BSON( "x" << BSON( "$regex" << BSON_ARRAY("abc") ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - - query = BSON( "x" << BSON( "$optionas" << "i" ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - - query = BSON( "x" << BSON( "$options" << "i" ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } - - TEST( MatchExpressionParserLeafTest, ExistsYes1 ) { - BSONObjBuilder b; - b.appendBool( "$exists", true ); - BSONObj query = BSON( "x" << b.obj() ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << "abc" ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "y" << "AC" ) ) ); - } - - TEST( MatchExpressionParserLeafTest, ExistsNO1 ) { - BSONObjBuilder b; - b.appendBool( "$exists", false ); - BSONObj query = BSON( "x" << b.obj() ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "abc" ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "y" << "AC" ) ) ); - } - - TEST( MatchExpressionParserLeafTest, Type1 ) { - BSONObj query = BSON( "x" << BSON( "$type" << String ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << "abc" ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, Type2 ) { - BSONObj query = BSON( "x" << BSON( "$type" << (double)NumberDouble ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 5.3 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, TypeDoubleOperator ) { - BSONObj query = BSON( "x" << BSON( "$type" << 1.5 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5.3 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, TypeNull ) { - BSONObj query = BSON( "x" << BSON( "$type" << jstNULL ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( !result.getValue()->matchesBSON( BSONObj() ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5 ) ) ); - BSONObjBuilder b; - b.appendNull( "x" ); - ASSERT( result.getValue()->matchesBSON( b.obj() ) ); - } - - TEST( MatchExpressionParserLeafTest, TypeBadType ) { - BSONObjBuilder b; - b.append( "$type", ( JSTypeMax + 1 ) ); - BSONObj query = BSON( "x" << b.obj() ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - std::unique_ptr<MatchExpression> destroy(result.getValue()); - - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5.3 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5 ) ) ); - } - - TEST( MatchExpressionParserLeafTest, TypeBad ) { - BSONObj query = BSON( "x" << BSON( "$type" << BSON( "x" << 1 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } +using std::endl; +using std::string; + +TEST(MatchExpressionParserLeafTest, SimpleEQ2) { + BSONObj query = BSON("x" << BSON("$eq" << 2)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 3))); +} + +TEST(MatchExpressionParserLeafTest, SimpleEQUndefined) { + BSONObj query = BSON("x" << BSON("$eq" << BSONUndefined)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, SimpleGT1) { + BSONObj query = BSON("x" << BSON("$gt" << 2)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 3))); +} + +TEST(MatchExpressionParserLeafTest, SimpleLT1) { + BSONObj query = BSON("x" << BSON("$lt" << 2)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 3))); +} + +TEST(MatchExpressionParserLeafTest, SimpleGTE1) { + BSONObj query = BSON("x" << BSON("$gte" << 2)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 3))); +} + +TEST(MatchExpressionParserLeafTest, SimpleLTE1) { + BSONObj query = BSON("x" << BSON("$lte" << 2)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 3))); +} + +TEST(MatchExpressionParserLeafTest, SimpleNE1) { + BSONObj query = BSON("x" << BSON("$ne" << 2)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 3))); +} + +TEST(MatchExpressionParserLeafTest, SimpleModBad1) { + BSONObj query = BSON("x" << BSON("$mod" << BSON_ARRAY(3 << 2))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy1(result.getValue()); + + query = BSON("x" << BSON("$mod" << BSON_ARRAY(3))); + result = MatchExpressionParser::parse(query); + ASSERT_TRUE(!result.isOK()); + + query = BSON("x" << BSON("$mod" << BSON_ARRAY(3 << 2 << 4))); + result = MatchExpressionParser::parse(query); + ASSERT_TRUE(!result.isOK()); + + query = BSON("x" << BSON("$mod" << BSON_ARRAY("q" << 2))); + result = MatchExpressionParser::parse(query); + ASSERT_TRUE(!result.isOK()); + + query = BSON("x" << BSON("$mod" << 3)); + result = MatchExpressionParser::parse(query); + ASSERT_TRUE(!result.isOK()); + + query = BSON("x" << BSON("$mod" << BSON("a" << 1 << "b" << 2))); + result = MatchExpressionParser::parse(query); + ASSERT_TRUE(!result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, SimpleMod1) { + BSONObj query = BSON("x" << BSON("$mod" << BSON_ARRAY(3 << 2))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" << 5))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 4))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 8))); +} + +TEST(MatchExpressionParserLeafTest, SimpleModNotNumber) { + BSONObj query = BSON("x" << BSON("$mod" << BSON_ARRAY(2 << "r"))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 4))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" + << "a"))); +} + + +TEST(MatchExpressionParserLeafTest, SimpleIN1) { + BSONObj query = BSON("x" << BSON("$in" << BSON_ARRAY(2 << 3))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 3))); +} + +TEST(MatchExpressionParserLeafTest, INSingleDBRef) { + OID oid = OID::gen(); + BSONObj query = BSON("x" << BSON("$in" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db")))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + OID oidx = OID::gen(); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("$ref" + << "collx" + << "$id" << oidx << "$db" + << "db")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("$ref" + << "coll" + << "$id" << oidx << "$db" + << "db")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("$id" << oid << "$ref" + << "coll" + << "$db" + << "db")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("$id" << oid << "$ref" + << "coll" + << "$db" + << "db")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$id" << oid << "$ref" + << "coll" + << "$db" + << "db"))))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "dbx")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("$db" + << "db" + << "$ref" + << "coll" + << "$id" << oid)))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db")))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db"))))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "collx" + << "$id" << oidx << "$db" + << "db") + << BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db"))))); +} + +TEST(MatchExpressionParserLeafTest, INMultipleDBRef) { + OID oid = OID::gen(); + OID oidy = OID::gen(); + BSONObj query = BSON("x" << BSON("$in" << BSON_ARRAY(BSON("$ref" + << "colly" + << "$id" << oidy << "$db" + << "db") + << BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db")))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + OID oidx = OID::gen(); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("$ref" + << "collx" + << "$id" << oidx << "$db" + << "db")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("$ref" + << "coll" + << "$id" << oidx << "$db" + << "db")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("$id" << oid << "$ref" + << "coll" + << "$db" + << "db")))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oidy << "$db" + << "db"))))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "colly" + << "$id" << oid << "$db" + << "db"))))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$id" << oid << "$ref" + << "coll" + << "$db" + << "db"))))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "dbx"))))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$id" << oidy << "$ref" + << "colly" + << "$db" + << "db"))))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "collx" + << "$id" << oidx << "$db" + << "db") + << BSON("$ref" + << "coll" + << "$id" << oidx << "$db" + << "db"))))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "collx" + << "$id" << oidx << "$db" + << "db") + << BSON("$ref" + << "colly" + << "$id" << oidx << "$db" + << "db"))))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "collx" + << "$id" << oidx << "$db" + << "db") + << BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "dbx"))))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db")))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON("$ref" + << "colly" + << "$id" << oidy << "$db" + << "db")))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db"))))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "colly" + << "$id" << oidy << "$db" + << "db"))))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "collx" + << "$id" << oidx << "$db" + << "db") + << BSON("$ref" + << "coll" + << "$id" << oid << "$db" + << "db"))))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(BSON("$ref" + << "collx" + << "$id" << oidx << "$db" + << "db") + << BSON("$ref" + << "colly" + << "$id" << oidy << "$db" + << "db"))))); +} + +TEST(MatchExpressionParserLeafTest, INDBRefWithOptionalField1) { + OID oid = OID::gen(); + BSONObj query = BSON("x" << BSON("$in" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345)))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + OID oidx = OID::gen(); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON("$ref" + << "coll" + << "$id" << oidx << "$db" + << "db")))); + ASSERT(result.getValue()->matchesBSON( + BSON("x" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345))))); + ASSERT(result.getValue()->matchesBSON( + BSON("x" << BSON_ARRAY(BSON("$ref" + << "collx" + << "$id" << oidx << "foo" << 12345) + << BSON("$ref" + << "coll" + << "$id" << oid << "foo" << 12345))))); +} + +TEST(MatchExpressionParserLeafTest, INInvalidDBRefs) { + // missing $id + BSONObj query = BSON("x" << BSON("$in" << BSON_ARRAY(BSON("$ref" + << "coll")))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + result = MatchExpressionParser::parse(query); + + // second field is not $id + query = BSON("x" << BSON("$in" << BSON_ARRAY(BSON("$ref" + << "coll" + << "$foo" << 1)))); + result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); + + OID oid = OID::gen(); + + // missing $ref field + query = BSON("x" << BSON("$in" << BSON_ARRAY(BSON("$id" << oid << "foo" << 3)))); + result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); + + // missing $id and $ref field + query = BSON("x" << BSON("$in" << BSON_ARRAY(BSON("$db" + << "test" + << "foo" << 3)))); + result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, INExpressionDocument) { + BSONObj query = BSON("x" << BSON("$in" << BSON_ARRAY(BSON("$foo" << 1)))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, INNotArray) { + BSONObj query = BSON("x" << BSON("$in" << 5)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, INUndefined) { + BSONObj query = BSON("x" << BSON("$in" << BSON_ARRAY(BSONUndefined))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, INNotElemMatch) { + BSONObj query = BSON("x" << BSON("$in" << BSON_ARRAY(BSON("$elemMatch" << 1)))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, INRegexTooLong) { + string tooLargePattern(50 * 1000, 'z'); + BSONObjBuilder inArray; + inArray.appendRegex("0", tooLargePattern, ""); + BSONObjBuilder operand; + operand.appendArray("$in", inArray.obj()); + BSONObj query = BSON("x" << operand.obj()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, INRegexTooLong2) { + string tooLargePattern(50 * 1000, 'z'); + BSONObj query = BSON("x" << BSON("$in" << BSON_ARRAY(BSON("$regex" << tooLargePattern)))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, INRegexStuff) { + BSONObjBuilder inArray; + inArray.appendRegex("0", "^a", ""); + inArray.appendRegex("1", "B", "i"); + inArray.append("2", 4); + BSONObjBuilder operand; + operand.appendArray("$in", inArray.obj()); + + BSONObj query = BSON("a" << operand.obj()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + BSONObj matchFirst = BSON("a" + << "ax"); + BSONObj matchFirstRegex = BSONObjBuilder().appendRegex("a", "^a", "").obj(); + BSONObj matchSecond = BSON("a" + << "qqb"); + BSONObj matchSecondRegex = BSONObjBuilder().appendRegex("a", "B", "i").obj(); + BSONObj matchThird = BSON("a" << 4); + BSONObj notMatch = BSON("a" + << "l"); + BSONObj notMatchRegex = BSONObjBuilder().appendRegex("a", "B", "").obj(); + + ASSERT(result.getValue()->matchesBSON(matchFirst)); + ASSERT(result.getValue()->matchesBSON(matchFirstRegex)); + ASSERT(result.getValue()->matchesBSON(matchSecond)); + ASSERT(result.getValue()->matchesBSON(matchSecondRegex)); + ASSERT(result.getValue()->matchesBSON(matchThird)); + ASSERT(!result.getValue()->matchesBSON(notMatch)); + ASSERT(!result.getValue()->matchesBSON(notMatchRegex)); +} + +TEST(MatchExpressionParserLeafTest, SimpleNIN1) { + BSONObj query = BSON("x" << BSON("$nin" << BSON_ARRAY(2 << 3))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 3))); +} + +TEST(MatchExpressionParserLeafTest, NINNotArray) { + BSONObj query = BSON("x" << BSON("$nin" << 5)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + + +TEST(MatchExpressionParserLeafTest, Regex1) { + BSONObjBuilder b; + b.appendRegex("x", "abc", "i"); + BSONObj query = b.obj(); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" + << "abc"))); + ASSERT(result.getValue()->matchesBSON(BSON("x" + << "ABC"))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" + << "AC"))); +} + +TEST(MatchExpressionParserLeafTest, Regex2) { + BSONObj query = BSON("x" << BSON("$regex" + << "abc" + << "$options" + << "i")); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" + << "abc"))); + ASSERT(result.getValue()->matchesBSON(BSON("x" + << "ABC"))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" + << "AC"))); +} + +TEST(MatchExpressionParserLeafTest, Regex3) { + BSONObj query = BSON("x" << BSON("$options" + << "i" + << "$regex" + << "abc")); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + log() << "result: " << result.getStatus() << endl; + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" + << "abc"))); + ASSERT(result.getValue()->matchesBSON(BSON("x" + << "ABC"))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" + << "AC"))); +} + +TEST(MatchExpressionParserLeafTest, RegexBad) { + BSONObj query = BSON("x" << BSON("$regex" + << "abc" + << "$optionas" + << "i")); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); + + // $regex does not with numbers + query = BSON("x" << BSON("$regex" << 123)); + result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); + + query = BSON("x" << BSON("$regex" << BSON_ARRAY("abc"))); + result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); + + query = BSON("x" << BSON("$optionas" + << "i")); + result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); + + query = BSON("x" << BSON("$options" + << "i")); + result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, ExistsYes1) { + BSONObjBuilder b; + b.appendBool("$exists", true); + BSONObj query = BSON("x" << b.obj()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" + << "abc"))); + ASSERT(!result.getValue()->matchesBSON(BSON("y" + << "AC"))); +} + +TEST(MatchExpressionParserLeafTest, ExistsNO1) { + BSONObjBuilder b; + b.appendBool("$exists", false); + BSONObj query = BSON("x" << b.obj()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" + << "abc"))); + ASSERT(result.getValue()->matchesBSON(BSON("y" + << "AC"))); +} + +TEST(MatchExpressionParserLeafTest, Type1) { + BSONObj query = BSON("x" << BSON("$type" << String)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" + << "abc"))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5))); +} + +TEST(MatchExpressionParserLeafTest, Type2) { + BSONObj query = BSON("x" << BSON("$type" << (double)NumberDouble)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" << 5.3))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5))); +} + +TEST(MatchExpressionParserLeafTest, TypeDoubleOperator) { + BSONObj query = BSON("x" << BSON("$type" << 1.5)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5.3))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5))); +} + +TEST(MatchExpressionParserLeafTest, TypeNull) { + BSONObj query = BSON("x" << BSON("$type" << jstNULL)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(!result.getValue()->matchesBSON(BSONObj())); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5))); + BSONObjBuilder b; + b.appendNull("x"); + ASSERT(result.getValue()->matchesBSON(b.obj())); +} + +TEST(MatchExpressionParserLeafTest, TypeBadType) { + BSONObjBuilder b; + b.append("$type", (JSTypeMax + 1)); + BSONObj query = BSON("x" << b.obj()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + std::unique_ptr<MatchExpression> destroy(result.getValue()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5.3))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5))); +} + +TEST(MatchExpressionParserLeafTest, TypeBad) { + BSONObj query = BSON("x" << BSON("$type" << BSON("x" << 1))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} } diff --git a/src/mongo/db/matcher/expression_parser_test.cpp b/src/mongo/db/matcher/expression_parser_test.cpp index 291c3465c8f..5af0c7a2843 100644 --- a/src/mongo/db/matcher/expression_parser_test.cpp +++ b/src/mongo/db/matcher/expression_parser_test.cpp @@ -39,77 +39,79 @@ namespace mongo { - TEST( MatchExpressionParserTest, SimpleEQ1 ) { - BSONObj query = BSON( "x" << 2 ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - - delete result.getValue(); - } - - TEST( MatchExpressionParserTest, Multiple1 ) { - BSONObj query = BSON( "x" << 5 << "y" << BSON( "$gt" << 5 << "$lt" << 8 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 5 << "y" << 7 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 5 << "y" << 6 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 6 << "y" << 7 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5 << "y" << 9 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 5 << "y" << 4 ) ) ); - - delete result.getValue(); - } - - TEST( AtomicMatchExpressionTest, Simple1 ) { - BSONObj query = BSON( "x" << 5 << "$atomic" << BSON( "$gt" << 5 << "$lt" << 8 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - delete result.getValue(); - - query = BSON( "x" << 5 << "$isolated" << 1 ); - result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - delete result.getValue(); - - query = BSON( "x" << 5 << "y" << BSON( "$isolated" << 1 ) ); - result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( 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 ); - StatusWith<int> a = fib( n - 1 ); - StatusWith<int> b = fib( n - 2 ); - if ( !a.isOK() ) return a; - if ( !b.isOK() ) return b; - return StatusWith<int>( a.getValue() + b.getValue() ); - } - - TEST( StatusWithTest, Fib1 ) { - StatusWith<int> x = fib( -2 ); - ASSERT( !x.isOK() ); - - x = fib(0); - ASSERT( x.isOK() ); - ASSERT( 1 == x.getValue() ); - - x = fib(1); - ASSERT( x.isOK() ); - ASSERT( 1 == x.getValue() ); - - x = fib(2); - ASSERT( x.isOK() ); - ASSERT( 2 == x.getValue() ); - - x = fib(3); - ASSERT( x.isOK() ); - ASSERT( 3 == x.getValue() ); - - - } +TEST(MatchExpressionParserTest, SimpleEQ1) { + BSONObj query = BSON("x" << 2); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 3))); + + delete result.getValue(); +} + +TEST(MatchExpressionParserTest, Multiple1) { + BSONObj query = BSON("x" << 5 << "y" << BSON("$gt" << 5 << "$lt" << 8)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 7))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 6))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 6 << "y" << 7))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 9))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 5 << "y" << 4))); + + delete result.getValue(); +} + +TEST(AtomicMatchExpressionTest, Simple1) { + BSONObj query = BSON("x" << 5 << "$atomic" << BSON("$gt" << 5 << "$lt" << 8)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + delete result.getValue(); + + query = BSON("x" << 5 << "$isolated" << 1); + result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + delete result.getValue(); + + query = BSON("x" << 5 << "y" << BSON("$isolated" << 1)); + result = MatchExpressionParser::parse(query); + ASSERT_FALSE(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); + StatusWith<int> a = fib(n - 1); + StatusWith<int> b = fib(n - 2); + if (!a.isOK()) + return a; + if (!b.isOK()) + return b; + return StatusWith<int>(a.getValue() + b.getValue()); +} + +TEST(StatusWithTest, Fib1) { + StatusWith<int> x = fib(-2); + ASSERT(!x.isOK()); + + x = fib(0); + ASSERT(x.isOK()); + ASSERT(1 == x.getValue()); + + x = fib(1); + ASSERT(x.isOK()); + ASSERT(1 == x.getValue()); + + x = fib(2); + ASSERT(x.isOK()); + ASSERT(2 == x.getValue()); + + x = fib(3); + ASSERT(x.isOK()); + ASSERT(3 == x.getValue()); +} } diff --git a/src/mongo/db/matcher/expression_parser_text.cpp b/src/mongo/db/matcher/expression_parser_text.cpp index f8b40a1823b..de46f9e1169 100644 --- a/src/mongo/db/matcher/expression_parser_text.cpp +++ b/src/mongo/db/matcher/expression_parser_text.cpp @@ -37,64 +37,62 @@ namespace mongo { - using std::unique_ptr; - using std::string; +using std::unique_ptr; +using std::string; - StatusWithMatchExpression expressionParserTextCallbackReal( const BSONObj& queryObj ) { - // Validate queryObj, but defer construction of FTSQuery (which requires access to the - // target namespace) until stage building time. +StatusWithMatchExpression expressionParserTextCallbackReal(const BSONObj& queryObj) { + // Validate queryObj, but defer construction of FTSQuery (which requires access to the + // target namespace) until stage building time. - int expectedFieldCount = 1; + int expectedFieldCount = 1; - if ( mongo::String != queryObj["$search"].type() ) { - return StatusWithMatchExpression( ErrorCodes::TypeMismatch, - "$search requires a string value" ); - } + if (mongo::String != queryObj["$search"].type()) { + return StatusWithMatchExpression(ErrorCodes::TypeMismatch, + "$search requires a string value"); + } - string language = ""; - BSONElement languageElt = queryObj["$language"]; - if ( !languageElt.eoo() ) { - expectedFieldCount++; - if ( mongo::String != languageElt.type() ) { - return StatusWithMatchExpression( ErrorCodes::TypeMismatch, - "$language requires a string value" ); - } - language = languageElt.String(); - Status status = - fts::FTSLanguage::make( language, fts::TEXT_INDEX_VERSION_2 ).getStatus(); - if ( !status.isOK() ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, - "$language specifies unsupported language" ); - } + string language = ""; + BSONElement languageElt = queryObj["$language"]; + if (!languageElt.eoo()) { + expectedFieldCount++; + if (mongo::String != languageElt.type()) { + return StatusWithMatchExpression(ErrorCodes::TypeMismatch, + "$language requires a string value"); } - string query = queryObj["$search"].String(); - - BSONElement caseSensitiveElt = queryObj["$caseSensitive"]; - bool caseSensitive = fts::FTSQuery::caseSensitiveDefault; - if ( !caseSensitiveElt.eoo() ) { - expectedFieldCount++; - if ( mongo::Bool != caseSensitiveElt.type() ) { - return StatusWithMatchExpression( ErrorCodes::TypeMismatch, - "$caseSensitive requires a boolean value" ); - } - caseSensitive = caseSensitiveElt.trueValue(); + language = languageElt.String(); + Status status = fts::FTSLanguage::make(language, fts::TEXT_INDEX_VERSION_2).getStatus(); + if (!status.isOK()) { + return StatusWithMatchExpression(ErrorCodes::BadValue, + "$language specifies unsupported language"); } + } + string query = queryObj["$search"].String(); - if ( queryObj.nFields() != expectedFieldCount ) { - return StatusWithMatchExpression( ErrorCodes::BadValue, "extra fields in $text" ); + BSONElement caseSensitiveElt = queryObj["$caseSensitive"]; + bool caseSensitive = fts::FTSQuery::caseSensitiveDefault; + if (!caseSensitiveElt.eoo()) { + expectedFieldCount++; + if (mongo::Bool != caseSensitiveElt.type()) { + return StatusWithMatchExpression(ErrorCodes::TypeMismatch, + "$caseSensitive requires a boolean value"); } + caseSensitive = caseSensitiveElt.trueValue(); + } - unique_ptr<TextMatchExpression> e( new TextMatchExpression() ); - Status s = e->init( query, language, caseSensitive ); - if ( !s.isOK() ) { - return StatusWithMatchExpression( s ); - } - return StatusWithMatchExpression( e.release() ); + if (queryObj.nFields() != expectedFieldCount) { + return StatusWithMatchExpression(ErrorCodes::BadValue, "extra fields in $text"); } - MONGO_INITIALIZER( MatchExpressionParserText )( ::mongo::InitializerContext* context ) { - expressionParserTextCallback = expressionParserTextCallbackReal; - return Status::OK(); + unique_ptr<TextMatchExpression> e(new TextMatchExpression()); + Status s = e->init(query, language, caseSensitive); + if (!s.isOK()) { + return StatusWithMatchExpression(s); } + return StatusWithMatchExpression(e.release()); +} +MONGO_INITIALIZER(MatchExpressionParserText)(::mongo::InitializerContext* context) { + expressionParserTextCallback = expressionParserTextCallbackReal; + return Status::OK(); +} } diff --git a/src/mongo/db/matcher/expression_parser_text_test.cpp b/src/mongo/db/matcher/expression_parser_text_test.cpp index 2933c825b08..fe5eaebc0f7 100644 --- a/src/mongo/db/matcher/expression_parser_text_test.cpp +++ b/src/mongo/db/matcher/expression_parser_text_test.cpp @@ -39,55 +39,55 @@ namespace mongo { - TEST( MatchExpressionParserText, Basic ) { - BSONObj query = fromjson( "{$text: {$search:\"awesome\", $language:\"english\"}}" ); - - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT_EQUALS( MatchExpression::TEXT, result.getValue()->matchType() ); - std::unique_ptr<TextMatchExpression> textExp( - static_cast<TextMatchExpression*>( result.getValue() ) ); - ASSERT_EQUALS( textExp->getQuery(), "awesome" ); - ASSERT_EQUALS( textExp->getLanguage(), "english" ); - ASSERT_EQUALS( textExp->getCaseSensitive(), fts::FTSQuery::caseSensitiveDefault ); - } +TEST(MatchExpressionParserText, Basic) { + BSONObj query = fromjson("{$text: {$search:\"awesome\", $language:\"english\"}}"); + + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT_EQUALS(MatchExpression::TEXT, result.getValue()->matchType()); + std::unique_ptr<TextMatchExpression> textExp( + static_cast<TextMatchExpression*>(result.getValue())); + ASSERT_EQUALS(textExp->getQuery(), "awesome"); + ASSERT_EQUALS(textExp->getLanguage(), "english"); + ASSERT_EQUALS(textExp->getCaseSensitive(), fts::FTSQuery::caseSensitiveDefault); +} - TEST( MatchExpressionParserText, LanguageError ) { - BSONObj query = fromjson( "{$text: {$search:\"awesome\", $language:\"spanglish\"}}" ); +TEST(MatchExpressionParserText, LanguageError) { + BSONObj query = fromjson("{$text: {$search:\"awesome\", $language:\"spanglish\"}}"); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} - TEST( MatchExpressionParserText, CaseSensitiveTrue ) { - BSONObj query = fromjson( "{$text: {$search:\"awesome\", $caseSensitive: true}}" ); +TEST(MatchExpressionParserText, CaseSensitiveTrue) { + BSONObj query = fromjson("{$text: {$search:\"awesome\", $caseSensitive: true}}"); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); - ASSERT_EQUALS( MatchExpression::TEXT, result.getValue()->matchType() ); - std::unique_ptr<TextMatchExpression> textExp( - static_cast<TextMatchExpression*>( result.getValue() ) ); - ASSERT_EQUALS( textExp->getCaseSensitive(), true ); - } + ASSERT_EQUALS(MatchExpression::TEXT, result.getValue()->matchType()); + std::unique_ptr<TextMatchExpression> textExp( + static_cast<TextMatchExpression*>(result.getValue())); + ASSERT_EQUALS(textExp->getCaseSensitive(), true); +} - TEST( MatchExpressionParserText, CaseSensitiveFalse ) { - BSONObj query = fromjson( "{$text: {$search:\"awesome\", $caseSensitive: false}}" ); +TEST(MatchExpressionParserText, CaseSensitiveFalse) { + BSONObj query = fromjson("{$text: {$search:\"awesome\", $caseSensitive: false}}"); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); - ASSERT_EQUALS( MatchExpression::TEXT, result.getValue()->matchType() ); - std::unique_ptr<TextMatchExpression> textExp( - static_cast<TextMatchExpression*>( result.getValue() ) ); - ASSERT_EQUALS( textExp->getCaseSensitive(), false ); - } + ASSERT_EQUALS(MatchExpression::TEXT, result.getValue()->matchType()); + std::unique_ptr<TextMatchExpression> textExp( + static_cast<TextMatchExpression*>(result.getValue())); + ASSERT_EQUALS(textExp->getCaseSensitive(), false); +} - TEST( MatchExpressionParserText, CaseSensitiveError ) { - BSONObj query = fromjson( "{$text:{$search:\"awesome\", $caseSensitive: 0}}" ); +TEST(MatchExpressionParserText, CaseSensitiveError) { + BSONObj query = fromjson("{$text:{$search:\"awesome\", $caseSensitive: 0}}"); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); - } + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} } diff --git a/src/mongo/db/matcher/expression_parser_tree.cpp b/src/mongo/db/matcher/expression_parser_tree.cpp index 7c5359e6318..9a3149a8c5e 100644 --- a/src/mongo/db/matcher/expression_parser_tree.cpp +++ b/src/mongo/db/matcher/expression_parser_tree.cpp @@ -39,71 +39,68 @@ namespace mongo { - // static - const int MatchExpressionParser::kMaximumTreeDepth = 100; - - Status MatchExpressionParser::_parseTreeList( const BSONObj& arr, - ListOfMatchExpression* out, - int level ) { - if ( arr.isEmpty() ) - return Status( ErrorCodes::BadValue, - "$and/$or/$nor must be a nonempty array" ); - - BSONObjIterator i( arr ); - while ( i.more() ) { - BSONElement e = i.next(); - - if ( e.type() != Object ) - return Status( ErrorCodes::BadValue, - "$or/$and/$nor entries need to be full objects" ); - - StatusWithMatchExpression sub = _parse( e.Obj(), level ); - if ( !sub.isOK() ) - return sub.getStatus(); - - out->add( sub.getValue() ); - } - return Status::OK(); +// static +const int MatchExpressionParser::kMaximumTreeDepth = 100; + +Status MatchExpressionParser::_parseTreeList(const BSONObj& arr, + ListOfMatchExpression* out, + int level) { + if (arr.isEmpty()) + return Status(ErrorCodes::BadValue, "$and/$or/$nor must be a nonempty array"); + + BSONObjIterator i(arr); + while (i.more()) { + BSONElement e = i.next(); + + if (e.type() != Object) + return Status(ErrorCodes::BadValue, "$or/$and/$nor entries need to be full objects"); + + StatusWithMatchExpression sub = _parse(e.Obj(), level); + if (!sub.isOK()) + return sub.getStatus(); + + out->add(sub.getValue()); } + return Status::OK(); +} - StatusWithMatchExpression MatchExpressionParser::_parseNot( const char* name, - const BSONElement& e, - int level ) { - if ( e.type() == RegEx ) { - StatusWithMatchExpression s = _parseRegexElement( name, e ); - if ( !s.isOK() ) - return s; - std::unique_ptr<NotMatchExpression> n( new NotMatchExpression() ); - Status s2 = n->init( s.getValue() ); - if ( !s2.isOK() ) - return StatusWithMatchExpression( s2 ); - return StatusWithMatchExpression( n.release() ); - } - - if ( e.type() != Object ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$not needs a regex or a document" ); - - BSONObj notObject = e.Obj(); - if ( notObject.isEmpty() ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$not cannot be empty" ); - - std::unique_ptr<AndMatchExpression> theAnd( new AndMatchExpression() ); - Status s = _parseSub( name, notObject, theAnd.get(), level ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - - // TODO: this seems arbitrary? - // tested in jstests/not2.js - for ( unsigned i = 0; i < theAnd->numChildren(); i++ ) - if ( theAnd->getChild(i)->matchType() == MatchExpression::REGEX ) - return StatusWithMatchExpression( ErrorCodes::BadValue, "$not cannot have a regex" ); - - std::unique_ptr<NotMatchExpression> theNot( new NotMatchExpression() ); - s = theNot->init( theAnd.release() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - - return StatusWithMatchExpression( theNot.release() ); +StatusWithMatchExpression MatchExpressionParser::_parseNot(const char* name, + const BSONElement& e, + int level) { + if (e.type() == RegEx) { + StatusWithMatchExpression s = _parseRegexElement(name, e); + if (!s.isOK()) + return s; + std::unique_ptr<NotMatchExpression> n(new NotMatchExpression()); + Status s2 = n->init(s.getValue()); + if (!s2.isOK()) + return StatusWithMatchExpression(s2); + return StatusWithMatchExpression(n.release()); } + if (e.type() != Object) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$not needs a regex or a document"); + + BSONObj notObject = e.Obj(); + if (notObject.isEmpty()) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$not cannot be empty"); + + std::unique_ptr<AndMatchExpression> theAnd(new AndMatchExpression()); + Status s = _parseSub(name, notObject, theAnd.get(), level); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + // TODO: this seems arbitrary? + // tested in jstests/not2.js + for (unsigned i = 0; i < theAnd->numChildren(); i++) + if (theAnd->getChild(i)->matchType() == MatchExpression::REGEX) + return StatusWithMatchExpression(ErrorCodes::BadValue, "$not cannot have a regex"); + + std::unique_ptr<NotMatchExpression> theNot(new NotMatchExpression()); + s = theNot->init(theAnd.release()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + + return StatusWithMatchExpression(theNot.release()); +} } diff --git a/src/mongo/db/matcher/expression_parser_tree_test.cpp b/src/mongo/db/matcher/expression_parser_tree_test.cpp index a5cc3356463..a650803d283 100644 --- a/src/mongo/db/matcher/expression_parser_tree_test.cpp +++ b/src/mongo/db/matcher/expression_parser_tree_test.cpp @@ -39,163 +39,161 @@ namespace mongo { - TEST( MatchExpressionParserTreeTest, OR1 ) { - BSONObj query = BSON( "$or" << BSON_ARRAY( BSON( "x" << 1 ) << - BSON( "y" << 2 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); - - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "y" << 2 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "y" << 1 ) ) ); - - delete result.getValue(); - } +TEST(MatchExpressionParserTreeTest, OR1) { + BSONObj query = BSON("$or" << BSON_ARRAY(BSON("x" << 1) << BSON("y" << 2))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); - TEST( MatchExpressionParserTreeTest, OREmbedded ) { - BSONObj query1 = BSON( "$or" << BSON_ARRAY( BSON( "x" << 1 ) << - BSON( "y" << 2 ) ) ); - BSONObj query2 = BSON( "$or" << BSON_ARRAY( query1 ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query2 ); - ASSERT_TRUE( result.isOK() ); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(result.getValue()->matchesBSON(BSON("y" << 2))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 3))); + ASSERT(!result.getValue()->matchesBSON(BSON("y" << 1))); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "y" << 2 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "y" << 1 ) ) ); + delete result.getValue(); +} - delete result.getValue(); - } +TEST(MatchExpressionParserTreeTest, OREmbedded) { + BSONObj query1 = BSON("$or" << BSON_ARRAY(BSON("x" << 1) << BSON("y" << 2))); + BSONObj query2 = BSON("$or" << BSON_ARRAY(query1)); + StatusWithMatchExpression result = MatchExpressionParser::parse(query2); + ASSERT_TRUE(result.isOK()); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(result.getValue()->matchesBSON(BSON("y" << 2))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 3))); + ASSERT(!result.getValue()->matchesBSON(BSON("y" << 1))); - TEST( MatchExpressionParserTreeTest, AND1 ) { - BSONObj query = BSON( "$and" << BSON_ARRAY( BSON( "x" << 1 ) << - BSON( "y" << 2 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); + delete result.getValue(); +} - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "y" << 2 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "y" << 1 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 1 << "y" << 2 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 2 << "y" << 2 ) ) ); - delete result.getValue(); - } +TEST(MatchExpressionParserTreeTest, AND1) { + BSONObj query = BSON("$and" << BSON_ARRAY(BSON("x" << 1) << BSON("y" << 2))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); - TEST( MatchExpressionParserTreeTest, NOREmbedded ) { - BSONObj query = BSON( "$nor" << BSON_ARRAY( BSON( "x" << 1 ) << - BSON( "y" << 2 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("y" << 2))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 3))); + ASSERT(!result.getValue()->matchesBSON(BSON("y" << 1))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 1 << "y" << 2))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 2 << "y" << 2))); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 1 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "y" << 2 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 3 ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "y" << 1 ) ) ); + delete result.getValue(); +} - delete result.getValue(); - } +TEST(MatchExpressionParserTreeTest, NOREmbedded) { + BSONObj query = BSON("$nor" << BSON_ARRAY(BSON("x" << 1) << BSON("y" << 2))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); - TEST( MatchExpressionParserTreeTest, NOT1 ) { - BSONObj query = BSON( "x" << BSON( "$not" << BSON( "$gt" << 5 ) ) ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); + ASSERT(!result.getValue()->matchesBSON(BSON("y" << 2))); + ASSERT(result.getValue()->matchesBSON(BSON("x" << 3))); + ASSERT(result.getValue()->matchesBSON(BSON("y" << 1))); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << 2 ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << 8 ) ) ); + delete result.getValue(); +} - delete result.getValue(); - } +TEST(MatchExpressionParserTreeTest, NOT1) { + BSONObj query = BSON("x" << BSON("$not" << BSON("$gt" << 5))); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); - // Test a deep match tree that is not deep enough to hit the maximum depth limit. - TEST( MatchExpressionParserTreeTest, MaximumTreeDepthNotExceed ) { - static const int depth = 60; - - std::stringstream ss; - for (int i = 0; i < depth/2; i++) { - ss << "{$and: [{a: 3}, {$or: [{b: 2},"; - } - ss << "{b: 4}"; - for (int i = 0; i < depth/2; i++) { - ss << "]}]}"; - } - - BSONObj query = fromjson( ss.str() ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT( result.isOK() ); - delete result.getValue(); - } + ASSERT(result.getValue()->matchesBSON(BSON("x" << 2))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" << 8))); - // Test a tree that exceeds the maximum depth limit. - TEST( MatchExpressionParserTreeTest, MaximumTreeDepthExceed ) { - static const int depth = 105; - - std::stringstream ss; - for (int i = 0; i < depth/2; i++) { - ss << "{$and: [{a: 3}, {$or: [{b: 2},"; - } - ss << "{b: 4}"; - for (int i = 0; i < depth/2; i++) { - ss << "]}]}"; - } - - BSONObj query = fromjson( ss.str() ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); + delete result.getValue(); +} + +// Test a deep match tree that is not deep enough to hit the maximum depth limit. +TEST(MatchExpressionParserTreeTest, MaximumTreeDepthNotExceed) { + static const int depth = 60; + + std::stringstream ss; + for (int i = 0; i < depth / 2; i++) { + ss << "{$and: [{a: 3}, {$or: [{b: 2},"; + } + ss << "{b: 4}"; + for (int i = 0; i < depth / 2; i++) { + ss << "]}]}"; } - // We should also exceed the depth limit through deeply nested $not. - TEST( MatchExpressionParserTreeTest, MaximumTreeDepthExceededNestedNots ) { - static const int depth = 105; - - std::stringstream ss; - ss << "{a: "; - for (int i = 0; i < depth; i++) { - ss << "{$not: "; - } - ss << "{$eq: 5}"; - for (int i = 0; i < depth+1; i++) { - ss << "}"; - } - - BSONObj query = fromjson( ss.str() ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); + BSONObj query = fromjson(ss.str()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT(result.isOK()); + delete result.getValue(); +} + +// Test a tree that exceeds the maximum depth limit. +TEST(MatchExpressionParserTreeTest, MaximumTreeDepthExceed) { + static const int depth = 105; + + std::stringstream ss; + for (int i = 0; i < depth / 2; i++) { + ss << "{$and: [{a: 3}, {$or: [{b: 2},"; + } + ss << "{b: 4}"; + for (int i = 0; i < depth / 2; i++) { + ss << "]}]}"; } - // Depth limit with nested $elemMatch object. - TEST( MatchExpressionParserTreeTest, MaximumTreeDepthExceededNestedElemMatch ) { - static const int depth = 105; - - std::stringstream ss; - for (int i = 0; i < depth; i++) { - ss << "{a: {$elemMatch: "; - } - ss << "{b: 5}"; - for (int i = 0; i < depth; i++) { - ss << "}}"; - } - - BSONObj query = fromjson( ss.str() ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_FALSE( result.isOK() ); + BSONObj query = fromjson(ss.str()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +// We should also exceed the depth limit through deeply nested $not. +TEST(MatchExpressionParserTreeTest, MaximumTreeDepthExceededNestedNots) { + static const int depth = 105; + + std::stringstream ss; + ss << "{a: "; + for (int i = 0; i < depth; i++) { + ss << "{$not: "; + } + ss << "{$eq: 5}"; + for (int i = 0; i < depth + 1; i++) { + ss << "}"; } - TEST( MatchExpressionParserLeafTest, NotRegex1 ) { - BSONObjBuilder b; - b.appendRegex( "$not", "abc", "i" ); - BSONObj query = BSON( "x" << b.obj() ); - StatusWithMatchExpression result = MatchExpressionParser::parse( query ); - ASSERT_TRUE( result.isOK() ); + BSONObj query = fromjson(ss.str()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "abc" ) ) ); - ASSERT( !result.getValue()->matchesBSON( BSON( "x" << "ABC" ) ) ); - ASSERT( result.getValue()->matchesBSON( BSON( "x" << "AC" ) ) ); +// Depth limit with nested $elemMatch object. +TEST(MatchExpressionParserTreeTest, MaximumTreeDepthExceededNestedElemMatch) { + static const int depth = 105; - delete result.getValue(); + std::stringstream ss; + for (int i = 0; i < depth; i++) { + ss << "{a: {$elemMatch: "; + } + ss << "{b: 5}"; + for (int i = 0; i < depth; i++) { + ss << "}}"; } + BSONObj query = fromjson(ss.str()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_FALSE(result.isOK()); +} + +TEST(MatchExpressionParserLeafTest, NotRegex1) { + BSONObjBuilder b; + b.appendRegex("$not", "abc", "i"); + BSONObj query = BSON("x" << b.obj()); + StatusWithMatchExpression result = MatchExpressionParser::parse(query); + ASSERT_TRUE(result.isOK()); + + ASSERT(!result.getValue()->matchesBSON(BSON("x" + << "abc"))); + ASSERT(!result.getValue()->matchesBSON(BSON("x" + << "ABC"))); + ASSERT(result.getValue()->matchesBSON(BSON("x" + << "AC"))); + + delete result.getValue(); +} } diff --git a/src/mongo/db/matcher/expression_test.cpp b/src/mongo/db/matcher/expression_test.cpp index 62b4631a717..90d50cf5f47 100644 --- a/src/mongo/db/matcher/expression_test.cpp +++ b/src/mongo/db/matcher/expression_test.cpp @@ -39,69 +39,66 @@ namespace mongo { - TEST( MatchExpressionTest, Parse1 ) { - //TreeMatchExpression* e = NULL; - //Status s = MatchExpression::parse( BSON( "x" << 1 ), &e ); - //ASSERT_TRUE( s.isOK() ); - } +TEST(MatchExpressionTest, Parse1) { + // TreeMatchExpression* e = NULL; + // Status s = MatchExpression::parse( BSON( "x" << 1 ), &e ); + // ASSERT_TRUE( s.isOK() ); +} - TEST( LeafMatchExpressionTest, Equal1 ) { - BSONObj temp = BSON( "x" << 5 ); - EqualityMatchExpression e; - e.init( "x", temp["x"] ); +TEST(LeafMatchExpressionTest, Equal1) { + BSONObj temp = BSON("x" << 5); + EqualityMatchExpression e; + e.init("x", temp["x"]); + + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : 5 }"))); + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : [5] }"))); + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : [1,5] }"))); + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : [1,5,2] }"))); + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : [5,2] }"))); + + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : null }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 6 }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : [4,2] }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : [[5]] }"))); +} - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : 5 }" ) ) ); - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : [5] }" ) ) ); - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : [1,5] }" ) ) ); - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : [1,5,2] }" ) ) ); - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : [5,2] }" ) ) ); +TEST(LeafMatchExpressionTest, Comp1) { + BSONObj temp = BSON("x" << 5); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : null }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 6 }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : [4,2] }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : [[5]] }" ) ) ); + { + LTEMatchExpression e; + e.init("x", temp["x"]); + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : 5 }"))); + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : 4 }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 6 }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 'eliot' }"))); } - TEST( LeafMatchExpressionTest, Comp1 ) { - BSONObj temp = BSON( "x" << 5 ); - - { - LTEMatchExpression e; - e.init( "x", temp["x"] ); - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : 5 }" ) ) ); - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : 4 }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 6 }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 'eliot' }" ) ) ); - } - - { - LTMatchExpression e; - e.init( "x", temp["x"] ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 5 }" ) ) ); - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : 4 }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 6 }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 'eliot' }" ) ) ); - } - - { - GTEMatchExpression e; - e.init( "x", temp["x"] ); - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : 5 }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 4 }" ) ) ); - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : 6 }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 'eliot' }" ) ) ); - } - - { - GTMatchExpression e; - e.init( "x", temp["x"] ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 5 }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 4 }" ) ) ); - ASSERT_TRUE( e.matchesBSON( fromjson( "{ x : 6 }" ) ) ); - ASSERT_FALSE( e.matchesBSON( fromjson( "{ x : 'eliot' }" ) ) ); - } - + { + LTMatchExpression e; + e.init("x", temp["x"]); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 5 }"))); + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : 4 }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 6 }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 'eliot' }"))); + } + { + GTEMatchExpression e; + e.init("x", temp["x"]); + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : 5 }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 4 }"))); + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : 6 }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 'eliot' }"))); } + { + GTMatchExpression e; + e.init("x", temp["x"]); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 5 }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 4 }"))); + ASSERT_TRUE(e.matchesBSON(fromjson("{ x : 6 }"))); + ASSERT_FALSE(e.matchesBSON(fromjson("{ x : 'eliot' }"))); + } +} } diff --git a/src/mongo/db/matcher/expression_text.cpp b/src/mongo/db/matcher/expression_text.cpp index 320136c8751..e1f1ccd065f 100644 --- a/src/mongo/db/matcher/expression_text.cpp +++ b/src/mongo/db/matcher/expression_text.cpp @@ -33,72 +33,67 @@ namespace mongo { - using std::string; +using std::string; - Status TextMatchExpression::init( const string& query, - const string& language, - bool caseSensitive ) { - _query = query; - _language = language; - _caseSensitive = caseSensitive; - return initPath( "_fts" ); - } - - bool TextMatchExpression::matchesSingleElement( const BSONElement& e ) const { - // See ops/update.cpp. - // This node is removed by the query planner. It's only ever called if we're getting an - // elemMatchKey. - return true; - } +Status TextMatchExpression::init(const string& query, const string& language, bool caseSensitive) { + _query = query; + _language = language; + _caseSensitive = caseSensitive; + return initPath("_fts"); +} - void TextMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace(debug, level); - debug << "TEXT : query=" << _query << ", language=" - << _language << ", caseSensitive=" - << _caseSensitive << ", tag="; - MatchExpression::TagData* td = getTag(); - if ( NULL != td ) { - td->debugString( &debug ); - } - else { - debug << "NULL"; - } - debug << "\n"; - } +bool TextMatchExpression::matchesSingleElement(const BSONElement& e) const { + // See ops/update.cpp. + // This node is removed by the query planner. It's only ever called if we're getting an + // elemMatchKey. + return true; +} - void TextMatchExpression::toBSON(BSONObjBuilder* out) const { - out->append("$text", BSON("$search" << _query << - "$language" << _language << - "$caseSensitive" << _caseSensitive)); +void TextMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "TEXT : query=" << _query << ", language=" << _language + << ", caseSensitive=" << _caseSensitive << ", tag="; + MatchExpression::TagData* td = getTag(); + if (NULL != td) { + td->debugString(&debug); + } else { + debug << "NULL"; } + debug << "\n"; +} - bool TextMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) { - return false; - } - const TextMatchExpression* realOther = static_cast<const TextMatchExpression*>( other ); +void TextMatchExpression::toBSON(BSONObjBuilder* out) const { + out->append("$text", + BSON("$search" << _query << "$language" << _language << "$caseSensitive" + << _caseSensitive)); +} - // TODO This is way too crude. It looks for string equality, but it should be looking for - // common parsed form - if ( realOther->getQuery() != _query ) { - return false; - } - if ( realOther->getLanguage() != _language ) { - return false; - } - if ( realOther->getCaseSensitive() != _caseSensitive ) { - return false; - } - return true; +bool TextMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) { + return false; } + const TextMatchExpression* realOther = static_cast<const TextMatchExpression*>(other); - LeafMatchExpression* TextMatchExpression::shallowClone() const { - TextMatchExpression* next = new TextMatchExpression(); - next->init( _query, _language, _caseSensitive ); - if ( getTag() ) { - next->setTag( getTag()->clone() ); - } - return next; + // TODO This is way too crude. It looks for string equality, but it should be looking for + // common parsed form + if (realOther->getQuery() != _query) { + return false; + } + if (realOther->getLanguage() != _language) { + return false; + } + if (realOther->getCaseSensitive() != _caseSensitive) { + return false; } + return true; +} +LeafMatchExpression* TextMatchExpression::shallowClone() const { + TextMatchExpression* next = new TextMatchExpression(); + next->init(_query, _language, _caseSensitive); + if (getTag()) { + next->setTag(getTag()->clone()); + } + return next; +} } diff --git a/src/mongo/db/matcher/expression_text.h b/src/mongo/db/matcher/expression_text.h index 8d853de5621..3841bf4a608 100644 --- a/src/mongo/db/matcher/expression_text.h +++ b/src/mongo/db/matcher/expression_text.h @@ -36,30 +36,37 @@ namespace mongo { - class TextMatchExpression : public LeafMatchExpression { - public: - TextMatchExpression() : LeafMatchExpression( TEXT ) {} - virtual ~TextMatchExpression() {} +class TextMatchExpression : public LeafMatchExpression { +public: + TextMatchExpression() : LeafMatchExpression(TEXT) {} + virtual ~TextMatchExpression() {} - Status init( const std::string& query, const std::string& language, bool caseSensitive ); + Status init(const std::string& query, const std::string& language, bool caseSensitive); - virtual bool matchesSingleElement( const BSONElement& e ) const; + virtual bool matchesSingleElement(const BSONElement& e) const; - virtual void debugString( StringBuilder& debug, int level = 0 ) const; + virtual void debugString(StringBuilder& debug, int level = 0) const; - virtual void toBSON(BSONObjBuilder* out) const; + virtual void toBSON(BSONObjBuilder* out) const; - virtual bool equivalent( const MatchExpression* other ) const; + virtual bool equivalent(const MatchExpression* other) const; - virtual LeafMatchExpression* shallowClone() const; + virtual LeafMatchExpression* shallowClone() const; - const std::string& getQuery() const { return _query; } - const std::string& getLanguage() const { return _language; } - bool getCaseSensitive() const { return _caseSensitive; } - private: - std::string _query; - std::string _language; - bool _caseSensitive; - }; + const std::string& getQuery() const { + return _query; + } + const std::string& getLanguage() const { + return _language; + } + bool getCaseSensitive() const { + return _caseSensitive; + } -} // namespace mongo +private: + std::string _query; + std::string _language; + bool _caseSensitive; +}; + +} // namespace mongo diff --git a/src/mongo/db/matcher/expression_tree.cpp b/src/mongo/db/matcher/expression_tree.cpp index 84c929f4c37..bc07445d0cc 100644 --- a/src/mongo/db/matcher/expression_tree.cpp +++ b/src/mongo/db/matcher/expression_tree.cpp @@ -36,164 +36,163 @@ namespace mongo { - ListOfMatchExpression::~ListOfMatchExpression() { - for ( unsigned i = 0; i < _expressions.size(); i++ ) - delete _expressions[i]; - _expressions.clear(); - } +ListOfMatchExpression::~ListOfMatchExpression() { + for (unsigned i = 0; i < _expressions.size(); i++) + delete _expressions[i]; + _expressions.clear(); +} - void ListOfMatchExpression::add( MatchExpression* e ) { - verify( e ); - _expressions.push_back( e ); - } +void ListOfMatchExpression::add(MatchExpression* e) { + verify(e); + _expressions.push_back(e); +} - void ListOfMatchExpression::_debugList( StringBuilder& debug, int level ) const { - for ( unsigned i = 0; i < _expressions.size(); i++ ) - _expressions[i]->debugString( debug, level + 1 ); - } +void ListOfMatchExpression::_debugList(StringBuilder& debug, int level) const { + for (unsigned i = 0; i < _expressions.size(); i++) + _expressions[i]->debugString(debug, level + 1); +} - void ListOfMatchExpression::_listToBSON(BSONArrayBuilder* out) const { - for ( unsigned i = 0; i < _expressions.size(); i++ ) { - BSONObjBuilder childBob(out->subobjStart()); - _expressions[i]->toBSON(&childBob); - } - out->doneFast(); +void ListOfMatchExpression::_listToBSON(BSONArrayBuilder* out) const { + for (unsigned i = 0; i < _expressions.size(); i++) { + BSONObjBuilder childBob(out->subobjStart()); + _expressions[i]->toBSON(&childBob); } + out->doneFast(); +} - bool ListOfMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; +bool ListOfMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) + return false; - const ListOfMatchExpression* realOther = static_cast<const ListOfMatchExpression*>( other ); + const ListOfMatchExpression* realOther = static_cast<const ListOfMatchExpression*>(other); - if ( _expressions.size() != realOther->_expressions.size() ) - return false; + 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; + // TOOD: order doesn't matter + for (unsigned i = 0; i < _expressions.size(); i++) + if (!_expressions[i]->equivalent(realOther->_expressions[i])) + return false; - return true; - } + return true; +} - // ----- +// ----- - bool AndMatchExpression::matches( const MatchableDocument* doc, MatchDetails* details ) const { - for ( size_t i = 0; i < numChildren(); i++ ) { - if ( !getChild(i)->matches( doc, details ) ) { - if ( details ) - details->resetOutput(); - return false; - } +bool AndMatchExpression::matches(const MatchableDocument* doc, MatchDetails* details) const { + for (size_t i = 0; i < numChildren(); i++) { + if (!getChild(i)->matches(doc, details)) { + if (details) + details->resetOutput(); + return false; } - return true; } + return true; +} - bool AndMatchExpression::matchesSingleElement( const BSONElement& e ) const { - for ( size_t i = 0; i < numChildren(); i++ ) { - if ( !getChild(i)->matchesSingleElement( e ) ) { - return false; - } +bool AndMatchExpression::matchesSingleElement(const BSONElement& e) const { + for (size_t i = 0; i < numChildren(); i++) { + if (!getChild(i)->matchesSingleElement(e)) { + return false; } - return true; } + return true; +} - void AndMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << "$and\n"; - _debugList( debug, level ); - } +void AndMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "$and\n"; + _debugList(debug, level); +} - void AndMatchExpression::toBSON(BSONObjBuilder* out) const { - BSONArrayBuilder arrBob(out->subarrayStart("$and")); - _listToBSON(&arrBob); - } +void AndMatchExpression::toBSON(BSONObjBuilder* out) const { + BSONArrayBuilder arrBob(out->subarrayStart("$and")); + _listToBSON(&arrBob); +} - // ----- +// ----- - bool OrMatchExpression::matches( const MatchableDocument* doc, MatchDetails* details ) const { - for ( size_t i = 0; i < numChildren(); i++ ) { - if ( getChild(i)->matches( doc, NULL ) ) { - return true; - } +bool OrMatchExpression::matches(const MatchableDocument* doc, MatchDetails* details) const { + for (size_t i = 0; i < numChildren(); i++) { + if (getChild(i)->matches(doc, NULL)) { + return true; } - return false; } + return false; +} - bool OrMatchExpression::matchesSingleElement( const BSONElement& e ) const { - for ( size_t i = 0; i < numChildren(); i++ ) { - if ( getChild(i)->matchesSingleElement( e ) ) { - return true; - } +bool OrMatchExpression::matchesSingleElement(const BSONElement& e) const { + for (size_t i = 0; i < numChildren(); i++) { + if (getChild(i)->matchesSingleElement(e)) { + return true; } - return false; } + return false; +} - void OrMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << "$or\n"; - _debugList( debug, level ); - } +void OrMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "$or\n"; + _debugList(debug, level); +} - void OrMatchExpression::toBSON(BSONObjBuilder* out) const { - BSONArrayBuilder arrBob(out->subarrayStart("$or")); - _listToBSON(&arrBob); - } +void OrMatchExpression::toBSON(BSONObjBuilder* out) const { + BSONArrayBuilder arrBob(out->subarrayStart("$or")); + _listToBSON(&arrBob); +} - // ---- +// ---- - bool NorMatchExpression::matches( const MatchableDocument* doc, MatchDetails* details ) const { - for ( size_t i = 0; i < numChildren(); i++ ) { - if ( getChild(i)->matches( doc, NULL ) ) { - return false; - } +bool NorMatchExpression::matches(const MatchableDocument* doc, MatchDetails* details) const { + for (size_t i = 0; i < numChildren(); i++) { + if (getChild(i)->matches(doc, NULL)) { + return false; } - return true; } + return true; +} - bool NorMatchExpression::matchesSingleElement( const BSONElement& e ) const { - for ( size_t i = 0; i < numChildren(); i++ ) { - if ( getChild(i)->matchesSingleElement( e ) ) { - return false; - } +bool NorMatchExpression::matchesSingleElement(const BSONElement& e) const { + for (size_t i = 0; i < numChildren(); i++) { + if (getChild(i)->matchesSingleElement(e)) { + return false; } - return true; - } - - void NorMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << "$nor\n"; - _debugList( debug, level ); } + return true; +} - void NorMatchExpression::toBSON(BSONObjBuilder* out) const { - BSONArrayBuilder arrBob(out->subarrayStart("$nor")); - _listToBSON(&arrBob); - } +void NorMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "$nor\n"; + _debugList(debug, level); +} - // ------- +void NorMatchExpression::toBSON(BSONObjBuilder* out) const { + BSONArrayBuilder arrBob(out->subarrayStart("$nor")); + _listToBSON(&arrBob); +} - void NotMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << "$not\n"; - _exp->debugString( debug, level + 1 ); - } +// ------- - void NotMatchExpression::toBSON(BSONObjBuilder* out) const { - BSONObjBuilder childBob(out->subobjStart("$not")); - _exp->toBSON(&childBob); - childBob.doneFast(); - } +void NotMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "$not\n"; + _exp->debugString(debug, level + 1); +} - bool NotMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; +void NotMatchExpression::toBSON(BSONObjBuilder* out) const { + BSONObjBuilder childBob(out->subobjStart("$not")); + _exp->toBSON(&childBob); + childBob.doneFast(); +} - return _exp->equivalent( other->getChild(0) ); - } +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 6e35af8df72..5069045727c 100644 --- a/src/mongo/db/matcher/expression_tree.h +++ b/src/mongo/db/matcher/expression_tree.h @@ -39,157 +39,172 @@ */ namespace mongo { - class ListOfMatchExpression : public MatchExpression { - public: - ListOfMatchExpression( MatchType type ) : MatchExpression( type ){} - virtual ~ListOfMatchExpression(); - - /** - * @param e - I take ownership - */ - void add( MatchExpression* e ); - - /** - * clears all the thingsd we own, and does NOT delete - * someone else has taken ownership - */ - void clearAndRelease() { _expressions.clear(); } - - virtual size_t numChildren() const { return _expressions.size(); } - - virtual MatchExpression* getChild( size_t i ) const { return _expressions[i]; } - - virtual std::vector<MatchExpression*>* getChildVector() { return &_expressions; } - - bool equivalent( const MatchExpression* other ) const; - - protected: - void _debugList( StringBuilder& debug, int level ) const; - - void _listToBSON(BSONArrayBuilder* out) const; - - private: - std::vector< MatchExpression* > _expressions; - }; - - class AndMatchExpression : public ListOfMatchExpression { - public: - AndMatchExpression() : ListOfMatchExpression( AND ){} - virtual ~AndMatchExpression(){} - - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const; - virtual bool matchesSingleElement( const BSONElement& e ) const; - - virtual MatchExpression* shallowClone() const { - AndMatchExpression* self = new AndMatchExpression(); - for (size_t i = 0; i < numChildren(); ++i) { - self->add(getChild(i)->shallowClone()); - } - if ( getTag() ) { - self->setTag(getTag()->clone()); - } - return self; +class ListOfMatchExpression : public MatchExpression { +public: + ListOfMatchExpression(MatchType type) : MatchExpression(type) {} + virtual ~ListOfMatchExpression(); + + /** + * @param e - I take ownership + */ + void add(MatchExpression* e); + + /** + * clears all the thingsd we own, and does NOT delete + * someone else has taken ownership + */ + void clearAndRelease() { + _expressions.clear(); + } + + virtual size_t numChildren() const { + return _expressions.size(); + } + + virtual MatchExpression* getChild(size_t i) const { + return _expressions[i]; + } + + virtual std::vector<MatchExpression*>* getChildVector() { + return &_expressions; + } + + bool equivalent(const MatchExpression* other) const; + +protected: + void _debugList(StringBuilder& debug, int level) const; + + void _listToBSON(BSONArrayBuilder* out) const; + +private: + std::vector<MatchExpression*> _expressions; +}; + +class AndMatchExpression : public ListOfMatchExpression { +public: + AndMatchExpression() : ListOfMatchExpression(AND) {} + virtual ~AndMatchExpression() {} + + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const; + virtual bool matchesSingleElement(const BSONElement& e) const; + + virtual MatchExpression* shallowClone() const { + AndMatchExpression* self = new AndMatchExpression(); + for (size_t i = 0; i < numChildren(); ++i) { + self->add(getChild(i)->shallowClone()); + } + if (getTag()) { + self->setTag(getTag()->clone()); } + return self; + } - virtual void debugString( StringBuilder& debug, int level = 0 ) const; + virtual void debugString(StringBuilder& debug, int level = 0) const; - virtual void toBSON(BSONObjBuilder* out) const; - }; + virtual void toBSON(BSONObjBuilder* out) const; +}; - class OrMatchExpression : public ListOfMatchExpression { - public: - OrMatchExpression() : ListOfMatchExpression( OR ){} - virtual ~OrMatchExpression(){} +class OrMatchExpression : public ListOfMatchExpression { +public: + OrMatchExpression() : ListOfMatchExpression(OR) {} + virtual ~OrMatchExpression() {} - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const; - virtual bool matchesSingleElement( const BSONElement& e ) const; + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const; + virtual bool matchesSingleElement(const BSONElement& e) const; - virtual MatchExpression* shallowClone() const { - OrMatchExpression* self = new OrMatchExpression(); - for (size_t i = 0; i < numChildren(); ++i) { - self->add(getChild(i)->shallowClone()); - } - if ( getTag() ) { - self->setTag(getTag()->clone()); - } - return self; + virtual MatchExpression* shallowClone() const { + OrMatchExpression* self = new OrMatchExpression(); + for (size_t i = 0; i < numChildren(); ++i) { + self->add(getChild(i)->shallowClone()); } + if (getTag()) { + self->setTag(getTag()->clone()); + } + return self; + } - virtual void debugString( StringBuilder& debug, int level = 0 ) const; + virtual void debugString(StringBuilder& debug, int level = 0) const; - virtual void toBSON(BSONObjBuilder* out) const; - }; + virtual void toBSON(BSONObjBuilder* out) const; +}; - class NorMatchExpression : public ListOfMatchExpression { - public: - NorMatchExpression() : ListOfMatchExpression( NOR ){} - virtual ~NorMatchExpression(){} +class NorMatchExpression : public ListOfMatchExpression { +public: + NorMatchExpression() : ListOfMatchExpression(NOR) {} + virtual ~NorMatchExpression() {} - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const; - virtual bool matchesSingleElement( const BSONElement& e ) const; + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const; + virtual bool matchesSingleElement(const BSONElement& e) const; - virtual MatchExpression* shallowClone() const { - NorMatchExpression* self = new NorMatchExpression(); - for (size_t i = 0; i < numChildren(); ++i) { - self->add(getChild(i)->shallowClone()); - } - if ( getTag() ) { - self->setTag(getTag()->clone()); - } - return self; + virtual MatchExpression* shallowClone() const { + NorMatchExpression* self = new NorMatchExpression(); + for (size_t i = 0; i < numChildren(); ++i) { + self->add(getChild(i)->shallowClone()); } - - virtual void debugString( StringBuilder& debug, int level = 0 ) const; - - virtual void toBSON(BSONObjBuilder* out) const; - }; - - class NotMatchExpression : public MatchExpression { - public: - NotMatchExpression() : MatchExpression( NOT ){} - NotMatchExpression( MatchExpression* e ) : MatchExpression( NOT ), _exp( e ){} - /** - * @param exp - I own it, and will delete - */ - virtual Status init( MatchExpression* exp ) { - _exp.reset( exp ); - return Status::OK(); + if (getTag()) { + self->setTag(getTag()->clone()); } - - virtual MatchExpression* shallowClone() const { - NotMatchExpression* self = new NotMatchExpression(); - MatchExpression* child = _exp->shallowClone(); - self->init(child); - if ( getTag() ) { - self->setTag(getTag()->clone()); - } - return self; - } - - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const { - return !_exp->matches( doc, NULL ); + return self; + } + + virtual void debugString(StringBuilder& debug, int level = 0) const; + + virtual void toBSON(BSONObjBuilder* out) const; +}; + +class NotMatchExpression : public MatchExpression { +public: + NotMatchExpression() : MatchExpression(NOT) {} + NotMatchExpression(MatchExpression* e) : MatchExpression(NOT), _exp(e) {} + /** + * @param exp - I own it, and will delete + */ + virtual Status init(MatchExpression* exp) { + _exp.reset(exp); + return Status::OK(); + } + + virtual MatchExpression* shallowClone() const { + NotMatchExpression* self = new NotMatchExpression(); + MatchExpression* child = _exp->shallowClone(); + self->init(child); + if (getTag()) { + self->setTag(getTag()->clone()); } + return self; + } - virtual bool matchesSingleElement( const BSONElement& e ) const { - return !_exp->matchesSingleElement( e ); - } + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const { + return !_exp->matches(doc, NULL); + } - virtual void debugString( StringBuilder& debug, int level = 0 ) const; + virtual bool matchesSingleElement(const BSONElement& e) const { + return !_exp->matchesSingleElement(e); + } - virtual void toBSON(BSONObjBuilder* out) const; + virtual void debugString(StringBuilder& debug, int level = 0) const; - bool equivalent( const MatchExpression* other ) const; + virtual void toBSON(BSONObjBuilder* out) const; - virtual size_t numChildren() const { return 1; } + bool equivalent(const MatchExpression* other) const; - virtual MatchExpression* getChild( size_t i ) const { return _exp.get(); } + virtual size_t numChildren() const { + return 1; + } - MatchExpression* releaseChild(void) { return _exp.release(); } + virtual MatchExpression* getChild(size_t i) const { + return _exp.get(); + } - void resetChild( MatchExpression* newChild) { _exp.reset(newChild); } + MatchExpression* releaseChild(void) { + return _exp.release(); + } - private: - std::unique_ptr<MatchExpression> _exp; - }; + void resetChild(MatchExpression* newChild) { + _exp.reset(newChild); + } +private: + std::unique_ptr<MatchExpression> _exp; +}; } diff --git a/src/mongo/db/matcher/expression_tree_test.cpp b/src/mongo/db/matcher/expression_tree_test.cpp index 72e9766404a..a63439a1de2 100644 --- a/src/mongo/db/matcher/expression_tree_test.cpp +++ b/src/mongo/db/matcher/expression_tree_test.cpp @@ -38,536 +38,536 @@ namespace mongo { - using std::unique_ptr; - - TEST( NotMatchExpression, MatchesScalar ) { - BSONObj baseOperand = BSON( "$lt" << 5 ); - unique_ptr<ComparisonMatchExpression> lt( new LTMatchExpression() ); - ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); - NotMatchExpression notOp; - ASSERT( notOp.init( lt.release() ).isOK() ); - ASSERT( notOp.matchesBSON( BSON( "a" << 6 ), NULL ) ); - ASSERT( !notOp.matchesBSON( BSON( "a" << 4 ), NULL ) ); - } - - TEST( NotMatchExpression, MatchesArray ) { - BSONObj baseOperand = BSON( "$lt" << 5 ); - unique_ptr<ComparisonMatchExpression> lt( new LTMatchExpression() ); - ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); - NotMatchExpression notOp; - ASSERT( notOp.init( lt.release() ).isOK() ); - ASSERT( notOp.matchesBSON( BSON( "a" << BSON_ARRAY( 6 ) ), NULL ) ); - ASSERT( !notOp.matchesBSON( BSON( "a" << BSON_ARRAY( 4 ) ), NULL ) ); - // All array elements must match. - ASSERT( !notOp.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 5 << 6 ) ), NULL ) ); - } - - TEST( NotMatchExpression, ElemMatchKey ) { - BSONObj baseOperand = BSON( "$lt" << 5 ); - unique_ptr<ComparisonMatchExpression> lt( new LTMatchExpression() ); - ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); - NotMatchExpression notOp; - ASSERT( notOp.init( lt.release() ).isOK() ); - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !notOp.matchesBSON( BSON( "a" << BSON_ARRAY( 1 ) ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( notOp.matchesBSON( BSON( "a" << 6 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( notOp.matchesBSON( BSON( "a" << BSON_ARRAY( 6 ) ), &details ) ); - // elemMatchKey is not implemented for negative match operators. - ASSERT( !details.hasElemMatchKey() ); - } - /* - TEST( NotMatchExpression, MatchesIndexKey ) { - BSONObj baseOperand = BSON( "$lt" << 5 ); - unique_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() ); - ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); - NotMatchExpression notOp; - ASSERT( notOp.init( lt.release() ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - BSONObj indexKey = BSON( "" << "7" ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - notOp.matchesIndexKey( indexKey, indexSpec ) ); - } - */ - - /** - TEST( AndOp, MatchesElementSingleClause ) { - BSONObj baseOperand = BSON( "$lt" << 5 ); - BSONObj match = BSON( "a" << 4 ); - BSONObj notMatch = BSON( "a" << 5 ); - unique_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() ); - ASSERT( lt->init( "", baseOperand[ "$lt" ] ).isOK() ); - OwnedPointerVector<MatchMatchExpression> subMatchExpressions; - subMatchExpressions.mutableVector().push_back( lt.release() ); - AndOp andOp; - ASSERT( andOp.init( &subMatchExpressions ).isOK() ); - ASSERT( andOp.matchesSingleElement( match[ "a" ] ) ); - ASSERT( !andOp.matchesSingleElement( notMatch[ "a" ] ) ); - } - */ - - TEST( AndOp, NoClauses ) { - AndMatchExpression andMatchExpression; - ASSERT( andMatchExpression.matchesBSON( BSONObj(), NULL ) ); - } - - TEST( AndOp, MatchesElementThreeClauses ) { - BSONObj baseOperand1 = BSON( "$lt" << "z1" ); - BSONObj baseOperand2 = BSON( "$gt" << "a1" ); - BSONObj match = BSON( "a" << "r1" ); - BSONObj notMatch1 = BSON( "a" << "z1" ); - BSONObj notMatch2 = BSON( "a" << "a1" ); - BSONObj notMatch3 = BSON( "a" << "r" ); - - unique_ptr<ComparisonMatchExpression> sub1( new LTMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "$lt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new GTMatchExpression() ); - ASSERT( sub2->init( "a", baseOperand2[ "$gt" ] ).isOK() ); - unique_ptr<RegexMatchExpression> sub3( new RegexMatchExpression() ); - ASSERT( sub3->init( "a", "1", "" ).isOK() ); - - AndMatchExpression andOp; - andOp.add( sub1.release() ); - andOp.add( sub2.release() ); - andOp.add( sub3.release() ); - - ASSERT( andOp.matchesBSON( match ) ); - ASSERT( !andOp.matchesBSON( notMatch1 ) ); - ASSERT( !andOp.matchesBSON( notMatch2 ) ); - ASSERT( !andOp.matchesBSON( notMatch3 ) ); - } - - TEST( AndOp, MatchesSingleClause ) { - BSONObj baseOperand = BSON( "$ne" << 5 ); - unique_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); - ASSERT( eq->init( "a", baseOperand[ "$ne" ] ).isOK() ); - unique_ptr<NotMatchExpression> ne( new NotMatchExpression() ); - ASSERT( ne->init( eq.release() ).isOK() ); - - AndMatchExpression andOp; - andOp.add( ne.release() ); - - ASSERT( andOp.matchesBSON( BSON( "a" << 4 ), NULL ) ); - ASSERT( andOp.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 6 ) ), NULL ) ); - ASSERT( !andOp.matchesBSON( BSON( "a" << 5 ), NULL ) ); - ASSERT( !andOp.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 5 ) ), NULL ) ); - } - - TEST( AndOp, MatchesThreeClauses ) { - BSONObj baseOperand1 = BSON( "$gt" << 1 ); - BSONObj baseOperand2 = BSON( "$lt" << 10 ); - BSONObj baseOperand3 = BSON( "$lt" << 100 ); - - unique_ptr<ComparisonMatchExpression> sub1( new GTMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); - - unique_ptr<ComparisonMatchExpression> sub2( new LTMatchExpression() ); - ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); - - unique_ptr<ComparisonMatchExpression> sub3( new LTMatchExpression() ); - ASSERT( sub3->init( "b", baseOperand3[ "$lt" ] ).isOK() ); - - AndMatchExpression andOp; - andOp.add( sub1.release() ); - andOp.add( sub2.release() ); - andOp.add( sub3.release() ); - - ASSERT( andOp.matchesBSON( BSON( "a" << 5 << "b" << 6 ), NULL ) ); - ASSERT( !andOp.matchesBSON( BSON( "a" << 5 ), NULL ) ); - ASSERT( !andOp.matchesBSON( BSON( "b" << 6 ), NULL ) ); - ASSERT( !andOp.matchesBSON( BSON( "a" << 1 << "b" << 6 ), NULL ) ); - ASSERT( !andOp.matchesBSON( BSON( "a" << 10 << "b" << 6 ), NULL ) ); - } - - TEST( AndOp, ElemMatchKey ) { - BSONObj baseOperand1 = BSON( "a" << 1 ); - BSONObj baseOperand2 = BSON( "b" << 2 ); - - unique_ptr<ComparisonMatchExpression> sub1( new EqualityMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "a" ] ).isOK() ); - - unique_ptr<ComparisonMatchExpression> sub2( new EqualityMatchExpression() ); - ASSERT( sub2->init( "b", baseOperand2[ "b" ] ).isOK() ); - - AndMatchExpression andOp; - andOp.add( sub1.release() ); - andOp.add( sub2.release() ); - - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !andOp.matchesBSON( BSON( "a" << BSON_ARRAY( 1 ) ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( !andOp.matchesBSON( BSON( "b" << BSON_ARRAY( 2 ) ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( andOp.matchesBSON( BSON( "a" << BSON_ARRAY( 1 ) << "b" << BSON_ARRAY( 1 << 2 ) ), - &details ) ); - ASSERT( details.hasElemMatchKey() ); - // The elem match key for the second $and clause is recorded. - ASSERT_EQUALS( "1", details.elemMatchKey() ); - } - - /** - TEST( AndOp, MatchesIndexKeyWithoutUnknown ) { - BSONObj baseOperand1 = BSON( "$gt" << 1 ); - BSONObj baseOperand2 = BSON( "$lt" << 5 ); - unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); - ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); - OwnedPointerVector<MatchMatchExpression> subMatchExpressions; - subMatchExpressions.mutableVector().push_back( sub1.release() ); - subMatchExpressions.mutableVector().push_back( sub2.release() ); - AndOp andOp; - ASSERT( andOp.init( &subMatchExpressions ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - andOp.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - andOp.matchesIndexKey( BSON( "" << 0 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - andOp.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - } - - TEST( AndOp, MatchesIndexKeyWithUnknown ) { - BSONObj baseOperand1 = BSON( "$gt" << 1 ); - BSONObj baseOperand2 = BSON( "$lt" << 5 ); - // This part will return PartialMatchResult_Unknown. - BSONObj baseOperand3 = BSON( "$ne" << 5 ); - unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); - ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); - unique_ptr<NeOp> sub3( new NeOp() ); - ASSERT( sub3->init( "a", baseOperand3[ "$ne" ] ).isOK() ); - OwnedPointerVector<MatchMatchExpression> subMatchExpressions; - subMatchExpressions.mutableVector().push_back( sub1.release() ); - subMatchExpressions.mutableVector().push_back( sub2.release() ); - subMatchExpressions.mutableVector().push_back( sub3.release() ); - AndOp andOp; - ASSERT( andOp.init( &subMatchExpressions ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - andOp.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - andOp.matchesIndexKey( BSON( "" << 0 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - andOp.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - } - */ - - /** - TEST( OrOp, MatchesElementSingleClause ) { - BSONObj baseOperand = BSON( "$lt" << 5 ); - BSONObj match = BSON( "a" << 4 ); - BSONObj notMatch = BSON( "a" << 5 ); - unique_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() ); - ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); - OwnedPointerVector<MatchMatchExpression> subMatchExpressions; - subMatchExpressions.mutableVector().push_back( lt.release() ); - OrOp orOp; - ASSERT( orOp.init( &subMatchExpressions ).isOK() ); - ASSERT( orOp.matchesSingleElement( match[ "a" ] ) ); - ASSERT( !orOp.matchesSingleElement( notMatch[ "a" ] ) ); - } - */ - - TEST( OrOp, NoClauses ) { - OrMatchExpression orOp; - ASSERT( !orOp.matchesBSON( BSONObj(), NULL ) ); - } - /* - TEST( OrOp, MatchesElementThreeClauses ) { - BSONObj baseOperand1 = BSON( "$lt" << 0 ); - BSONObj baseOperand2 = BSON( "$gt" << 10 ); - BSONObj baseOperand3 = BSON( "a" << 5 ); - BSONObj match1 = BSON( "a" << -1 ); - BSONObj match2 = BSON( "a" << 11 ); - BSONObj match3 = BSON( "a" << 5 ); - BSONObj notMatch = BSON( "a" << "6" ); - unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "$lt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); - ASSERT( sub2->init( "a", baseOperand2[ "$gt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub3( new ComparisonMatchExpression() ); - ASSERT( sub3->init( "a", baseOperand3[ "a" ] ).isOK() ); - OwnedPointerVector<MatchMatchExpression> subMatchExpressions; - subMatchExpressions.mutableVector().push_back( sub1.release() ); - subMatchExpressions.mutableVector().push_back( sub2.release() ); - subMatchExpressions.mutableVector().push_back( sub3.release() ); - OrOp orOp; - ASSERT( orOp.init( &subMatchExpressions ).isOK() ); - ASSERT( orOp.matchesSingleElement( match1[ "a" ] ) ); - ASSERT( orOp.matchesSingleElement( match2[ "a" ] ) ); - ASSERT( orOp.matchesSingleElement( match3[ "a" ] ) ); - ASSERT( !orOp.matchesSingleElement( notMatch[ "a" ] ) ); - } - */ - TEST( OrOp, MatchesSingleClause ) { - BSONObj baseOperand = BSON( "$ne" << 5 ); - unique_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); - ASSERT( eq->init( "a", baseOperand[ "$ne" ] ).isOK() ); - unique_ptr<NotMatchExpression> ne( new NotMatchExpression() ); - ASSERT( ne->init( eq.release() ).isOK() ); - - OrMatchExpression orOp; - orOp.add( ne.release() ); - - ASSERT( orOp.matchesBSON( BSON( "a" << 4 ), NULL ) ); - ASSERT( orOp.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 6 ) ), NULL ) ); - ASSERT( !orOp.matchesBSON( BSON( "a" << 5 ), NULL ) ); - ASSERT( !orOp.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 5 ) ), NULL ) ); - } - - TEST( OrOp, MatchesThreeClauses ) { - BSONObj baseOperand1 = BSON( "$gt" << 10 ); - BSONObj baseOperand2 = BSON( "$lt" << 0 ); - BSONObj baseOperand3 = BSON( "b" << 100 ); - unique_ptr<ComparisonMatchExpression> sub1( new GTMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new LTMatchExpression() ); - ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub3( new EqualityMatchExpression() ); - ASSERT( sub3->init( "b", baseOperand3[ "b" ] ).isOK() ); - - OrMatchExpression orOp; - orOp.add( sub1.release() ); - orOp.add( sub2.release() ); - orOp.add( sub3.release() ); - - ASSERT( orOp.matchesBSON( BSON( "a" << -1 ), NULL ) ); - ASSERT( orOp.matchesBSON( BSON( "a" << 11 ), NULL ) ); - ASSERT( !orOp.matchesBSON( BSON( "a" << 5 ), NULL ) ); - ASSERT( orOp.matchesBSON( BSON( "b" << 100 ), NULL ) ); - ASSERT( !orOp.matchesBSON( BSON( "b" << 101 ), NULL ) ); - ASSERT( !orOp.matchesBSON( BSONObj(), NULL ) ); - ASSERT( orOp.matchesBSON( BSON( "a" << 11 << "b" << 100 ), NULL ) ); - } - - TEST( OrOp, ElemMatchKey ) { - BSONObj baseOperand1 = BSON( "a" << 1 ); - BSONObj baseOperand2 = BSON( "b" << 2 ); - unique_ptr<ComparisonMatchExpression> sub1( new EqualityMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "a" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new EqualityMatchExpression() ); - ASSERT( sub2->init( "b", baseOperand2[ "b" ] ).isOK() ); - - OrMatchExpression orOp; - orOp.add( sub1.release() ); - orOp.add( sub2.release() ); - - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !orOp.matchesBSON( BSONObj(), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( !orOp.matchesBSON( BSON( "a" << BSON_ARRAY( 10 ) << "b" << BSON_ARRAY( 10 ) ), - &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( orOp.matchesBSON( BSON( "a" << BSON_ARRAY( 1 ) << "b" << BSON_ARRAY( 1 << 2 ) ), - &details ) ); - // The elem match key feature is not implemented for $or. - ASSERT( !details.hasElemMatchKey() ); - } - - /** - TEST( OrOp, MatchesIndexKeyWithoutUnknown ) { - BSONObj baseOperand1 = BSON( "$gt" << 5 ); - BSONObj baseOperand2 = BSON( "$lt" << 1 ); - unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); - ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); - OwnedPointerVector<MatchMatchExpression> subMatchExpressions; - subMatchExpressions.mutableVector().push_back( sub1.release() ); - subMatchExpressions.mutableVector().push_back( sub2.release() ); - OrOp orOp; - ASSERT( orOp.init( &subMatchExpressions ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_False == - orOp.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - orOp.matchesIndexKey( BSON( "" << 0 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - orOp.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - } - - TEST( OrOp, MatchesIndexKeyWithUnknown ) { - BSONObj baseOperand1 = BSON( "$gt" << 5 ); - BSONObj baseOperand2 = BSON( "$lt" << 1 ); - // This part will return PartialMatchResult_Unknown. - BSONObj baseOperand3 = BSON( "$ne" << 5 ); - unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); - ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); - unique_ptr<NeOp> sub3( new NeOp() ); - ASSERT( sub3->init( "a", baseOperand3[ "$ne" ] ).isOK() ); - OwnedPointerVector<MatchMatchExpression> subMatchExpressions; - subMatchExpressions.mutableVector().push_back( sub1.release() ); - subMatchExpressions.mutableVector().push_back( sub2.release() ); - subMatchExpressions.mutableVector().push_back( sub3.release() ); - OrOp orOp; - ASSERT( orOp.init( &subMatchExpressions ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - orOp.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - orOp.matchesIndexKey( BSON( "" << 0 ), indexSpec ) ); - ASSERT( MatchMatchExpression::PartialMatchResult_True == - orOp.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); - } - */ - - /** - TEST( NorOp, MatchesElementSingleClause ) { - BSONObj baseOperand = BSON( "$lt" << 5 ); - BSONObj match = BSON( "a" << 5 ); - BSONObj notMatch = BSON( "a" << 4 ); - unique_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() ); - ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); - OwnedPointerVector<MatchMatchExpression> subMatchExpressions; - subMatchExpressions.mutableVector().push_back( lt.release() ); - NorOp norOp; - ASSERT( norOp.init( &subMatchExpressions ).isOK() ); - ASSERT( norOp.matchesSingleElement( match[ "a" ] ) ); - ASSERT( !norOp.matchesSingleElement( notMatch[ "a" ] ) ); - } - */ - - TEST( NorOp, NoClauses ) { - NorMatchExpression norOp; - ASSERT( norOp.matchesBSON( BSONObj(), NULL ) ); - } - /* - TEST( NorOp, MatchesElementThreeClauses ) { - BSONObj baseOperand1 = BSON( "$lt" << 0 ); - BSONObj baseOperand2 = BSON( "$gt" << 10 ); - BSONObj baseOperand3 = BSON( "a" << 5 ); - BSONObj notMatch1 = BSON( "a" << -1 ); - BSONObj notMatch2 = BSON( "a" << 11 ); - BSONObj notMatch3 = BSON( "a" << 5 ); - BSONObj match = BSON( "a" << "6" ); - unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "$lt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); - ASSERT( sub2->init( "a", baseOperand2[ "$gt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub3( new ComparisonMatchExpression() ); - ASSERT( sub3->init( "a", baseOperand3[ "a" ] ).isOK() ); - OwnedPointerVector<MatchMatchExpression> subMatchExpressions; - subMatchExpressions.mutableVector().push_back( sub1.release() ); - subMatchExpressions.mutableVector().push_back( sub2.release() ); - subMatchExpressions.mutableVector().push_back( sub3.release() ); - NorOp norOp; - ASSERT( norOp.init( &subMatchExpressions ).isOK() ); - ASSERT( !norOp.matchesSingleElement( notMatch1[ "a" ] ) ); - ASSERT( !norOp.matchesSingleElement( notMatch2[ "a" ] ) ); - ASSERT( !norOp.matchesSingleElement( notMatch3[ "a" ] ) ); - ASSERT( norOp.matchesSingleElement( match[ "a" ] ) ); - } - */ - - TEST( NorOp, MatchesSingleClause ) { - BSONObj baseOperand = BSON( "$ne" << 5 ); - unique_ptr<ComparisonMatchExpression> eq( new EqualityMatchExpression() ); - ASSERT( eq->init( "a", baseOperand[ "$ne" ] ).isOK() ); - unique_ptr<NotMatchExpression> ne( new NotMatchExpression() ); - ASSERT( ne->init( eq.release() ).isOK() ); - - NorMatchExpression norOp; - norOp.add( ne.release() ); - - ASSERT( !norOp.matchesBSON( BSON( "a" << 4 ), NULL ) ); - ASSERT( !norOp.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 6 ) ), NULL ) ); - ASSERT( norOp.matchesBSON( BSON( "a" << 5 ), NULL ) ); - ASSERT( norOp.matchesBSON( BSON( "a" << BSON_ARRAY( 4 << 5 ) ), NULL ) ); - } - - TEST( NorOp, MatchesThreeClauses ) { - BSONObj baseOperand1 = BSON( "$gt" << 10 ); - BSONObj baseOperand2 = BSON( "$lt" << 0 ); - BSONObj baseOperand3 = BSON( "b" << 100 ); - - unique_ptr<ComparisonMatchExpression> sub1( new GTMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new LTMatchExpression() ); - ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub3( new EqualityMatchExpression() ); - ASSERT( sub3->init( "b", baseOperand3[ "b" ] ).isOK() ); - - NorMatchExpression norOp; - norOp.add( sub1.release() ); - norOp.add( sub2.release() ); - norOp.add( sub3.release() ); - - ASSERT( !norOp.matchesBSON( BSON( "a" << -1 ), NULL ) ); - ASSERT( !norOp.matchesBSON( BSON( "a" << 11 ), NULL ) ); - ASSERT( norOp.matchesBSON( BSON( "a" << 5 ), NULL ) ); - ASSERT( !norOp.matchesBSON( BSON( "b" << 100 ), NULL ) ); - ASSERT( norOp.matchesBSON( BSON( "b" << 101 ), NULL ) ); - ASSERT( norOp.matchesBSON( BSONObj(), NULL ) ); - ASSERT( !norOp.matchesBSON( BSON( "a" << 11 << "b" << 100 ), NULL ) ); - } - - TEST( NorOp, ElemMatchKey ) { - BSONObj baseOperand1 = BSON( "a" << 1 ); - BSONObj baseOperand2 = BSON( "b" << 2 ); - unique_ptr<ComparisonMatchExpression> sub1( new EqualityMatchExpression() ); - ASSERT( sub1->init( "a", baseOperand1[ "a" ] ).isOK() ); - unique_ptr<ComparisonMatchExpression> sub2( new EqualityMatchExpression() ); - ASSERT( sub2->init( "b", baseOperand2[ "b" ] ).isOK() ); - - NorMatchExpression norOp; - norOp.add( sub1.release() ); - norOp.add( sub2.release() ); - - MatchDetails details; - details.requestElemMatchKey(); - ASSERT( !norOp.matchesBSON( BSON( "a" << 1 ), &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( !norOp.matchesBSON( BSON( "a" << BSON_ARRAY( 1 ) << "b" << BSON_ARRAY( 10 ) ), - &details ) ); - ASSERT( !details.hasElemMatchKey() ); - ASSERT( norOp.matchesBSON( BSON( "a" << BSON_ARRAY( 3 ) << "b" << BSON_ARRAY( 4 ) ), - &details ) ); - // The elem match key feature is not implemented for $nor. - 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 ); - unique_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() ); - ASSERT( eq->init( "a", baseOperand[ "a" ] ).isOK() ); - OwnedPointerVector<MatchMatchExpression> subMatchExpressions; - subMatchExpressions.mutableVector().push_back( eq.release() ); - NorOp norOp; - ASSERT( norOp.init( &subMatchExpressions ).isOK() ); - IndexSpec indexSpec( BSON( "a" << 1 ) ); - BSONObj indexKey = BSON( "" << "7" ); - ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == - norOp.matchesIndexKey( indexKey, indexSpec ) ); - } - */ +using std::unique_ptr; + +TEST(NotMatchExpression, MatchesScalar) { + BSONObj baseOperand = BSON("$lt" << 5); + unique_ptr<ComparisonMatchExpression> lt(new LTMatchExpression()); + ASSERT(lt->init("a", baseOperand["$lt"]).isOK()); + NotMatchExpression notOp; + ASSERT(notOp.init(lt.release()).isOK()); + ASSERT(notOp.matchesBSON(BSON("a" << 6), NULL)); + ASSERT(!notOp.matchesBSON(BSON("a" << 4), NULL)); +} + +TEST(NotMatchExpression, MatchesArray) { + BSONObj baseOperand = BSON("$lt" << 5); + unique_ptr<ComparisonMatchExpression> lt(new LTMatchExpression()); + ASSERT(lt->init("a", baseOperand["$lt"]).isOK()); + NotMatchExpression notOp; + ASSERT(notOp.init(lt.release()).isOK()); + ASSERT(notOp.matchesBSON(BSON("a" << BSON_ARRAY(6)), NULL)); + ASSERT(!notOp.matchesBSON(BSON("a" << BSON_ARRAY(4)), NULL)); + // All array elements must match. + ASSERT(!notOp.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5 << 6)), NULL)); +} + +TEST(NotMatchExpression, ElemMatchKey) { + BSONObj baseOperand = BSON("$lt" << 5); + unique_ptr<ComparisonMatchExpression> lt(new LTMatchExpression()); + ASSERT(lt->init("a", baseOperand["$lt"]).isOK()); + NotMatchExpression notOp; + ASSERT(notOp.init(lt.release()).isOK()); + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!notOp.matchesBSON(BSON("a" << BSON_ARRAY(1)), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(notOp.matchesBSON(BSON("a" << 6), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(notOp.matchesBSON(BSON("a" << BSON_ARRAY(6)), &details)); + // elemMatchKey is not implemented for negative match operators. + ASSERT(!details.hasElemMatchKey()); +} +/* + TEST( NotMatchExpression, MatchesIndexKey ) { + BSONObj baseOperand = BSON( "$lt" << 5 ); + unique_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() ); + ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); + NotMatchExpression notOp; + ASSERT( notOp.init( lt.release() ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + BSONObj indexKey = BSON( "" << "7" ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + notOp.matchesIndexKey( indexKey, indexSpec ) ); + } +*/ + +/** +TEST( AndOp, MatchesElementSingleClause ) { + BSONObj baseOperand = BSON( "$lt" << 5 ); + BSONObj match = BSON( "a" << 4 ); + BSONObj notMatch = BSON( "a" << 5 ); + unique_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() ); + ASSERT( lt->init( "", baseOperand[ "$lt" ] ).isOK() ); + OwnedPointerVector<MatchMatchExpression> subMatchExpressions; + subMatchExpressions.mutableVector().push_back( lt.release() ); + AndOp andOp; + ASSERT( andOp.init( &subMatchExpressions ).isOK() ); + ASSERT( andOp.matchesSingleElement( match[ "a" ] ) ); + ASSERT( !andOp.matchesSingleElement( notMatch[ "a" ] ) ); +} +*/ + +TEST(AndOp, NoClauses) { + AndMatchExpression andMatchExpression; + ASSERT(andMatchExpression.matchesBSON(BSONObj(), NULL)); +} + +TEST(AndOp, MatchesElementThreeClauses) { + BSONObj baseOperand1 = BSON("$lt" + << "z1"); + BSONObj baseOperand2 = BSON("$gt" + << "a1"); + BSONObj match = BSON("a" + << "r1"); + BSONObj notMatch1 = BSON("a" + << "z1"); + BSONObj notMatch2 = BSON("a" + << "a1"); + BSONObj notMatch3 = BSON("a" + << "r"); + + unique_ptr<ComparisonMatchExpression> sub1(new LTMatchExpression()); + ASSERT(sub1->init("a", baseOperand1["$lt"]).isOK()); + unique_ptr<ComparisonMatchExpression> sub2(new GTMatchExpression()); + ASSERT(sub2->init("a", baseOperand2["$gt"]).isOK()); + unique_ptr<RegexMatchExpression> sub3(new RegexMatchExpression()); + ASSERT(sub3->init("a", "1", "").isOK()); + + AndMatchExpression andOp; + andOp.add(sub1.release()); + andOp.add(sub2.release()); + andOp.add(sub3.release()); + + ASSERT(andOp.matchesBSON(match)); + ASSERT(!andOp.matchesBSON(notMatch1)); + ASSERT(!andOp.matchesBSON(notMatch2)); + ASSERT(!andOp.matchesBSON(notMatch3)); +} + +TEST(AndOp, MatchesSingleClause) { + BSONObj baseOperand = BSON("$ne" << 5); + unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); + ASSERT(eq->init("a", baseOperand["$ne"]).isOK()); + unique_ptr<NotMatchExpression> ne(new NotMatchExpression()); + ASSERT(ne->init(eq.release()).isOK()); + + AndMatchExpression andOp; + andOp.add(ne.release()); + + ASSERT(andOp.matchesBSON(BSON("a" << 4), NULL)); + ASSERT(andOp.matchesBSON(BSON("a" << BSON_ARRAY(4 << 6)), NULL)); + ASSERT(!andOp.matchesBSON(BSON("a" << 5), NULL)); + ASSERT(!andOp.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5)), NULL)); +} + +TEST(AndOp, MatchesThreeClauses) { + BSONObj baseOperand1 = BSON("$gt" << 1); + BSONObj baseOperand2 = BSON("$lt" << 10); + BSONObj baseOperand3 = BSON("$lt" << 100); + + unique_ptr<ComparisonMatchExpression> sub1(new GTMatchExpression()); + ASSERT(sub1->init("a", baseOperand1["$gt"]).isOK()); + + unique_ptr<ComparisonMatchExpression> sub2(new LTMatchExpression()); + ASSERT(sub2->init("a", baseOperand2["$lt"]).isOK()); + + unique_ptr<ComparisonMatchExpression> sub3(new LTMatchExpression()); + ASSERT(sub3->init("b", baseOperand3["$lt"]).isOK()); + + AndMatchExpression andOp; + andOp.add(sub1.release()); + andOp.add(sub2.release()); + andOp.add(sub3.release()); + + ASSERT(andOp.matchesBSON(BSON("a" << 5 << "b" << 6), NULL)); + ASSERT(!andOp.matchesBSON(BSON("a" << 5), NULL)); + ASSERT(!andOp.matchesBSON(BSON("b" << 6), NULL)); + ASSERT(!andOp.matchesBSON(BSON("a" << 1 << "b" << 6), NULL)); + ASSERT(!andOp.matchesBSON(BSON("a" << 10 << "b" << 6), NULL)); +} + +TEST(AndOp, ElemMatchKey) { + BSONObj baseOperand1 = BSON("a" << 1); + BSONObj baseOperand2 = BSON("b" << 2); + + unique_ptr<ComparisonMatchExpression> sub1(new EqualityMatchExpression()); + ASSERT(sub1->init("a", baseOperand1["a"]).isOK()); + + unique_ptr<ComparisonMatchExpression> sub2(new EqualityMatchExpression()); + ASSERT(sub2->init("b", baseOperand2["b"]).isOK()); + + AndMatchExpression andOp; + andOp.add(sub1.release()); + andOp.add(sub2.release()); + + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!andOp.matchesBSON(BSON("a" << BSON_ARRAY(1)), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(!andOp.matchesBSON(BSON("b" << BSON_ARRAY(2)), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(andOp.matchesBSON(BSON("a" << BSON_ARRAY(1) << "b" << BSON_ARRAY(1 << 2)), &details)); + ASSERT(details.hasElemMatchKey()); + // The elem match key for the second $and clause is recorded. + ASSERT_EQUALS("1", details.elemMatchKey()); +} + +/** +TEST( AndOp, MatchesIndexKeyWithoutUnknown ) { + BSONObj baseOperand1 = BSON( "$gt" << 1 ); + BSONObj baseOperand2 = BSON( "$lt" << 5 ); + unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); + ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); + unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); + ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); + OwnedPointerVector<MatchMatchExpression> subMatchExpressions; + subMatchExpressions.mutableVector().push_back( sub1.release() ); + subMatchExpressions.mutableVector().push_back( sub2.release() ); + AndOp andOp; + ASSERT( andOp.init( &subMatchExpressions ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + andOp.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + andOp.matchesIndexKey( BSON( "" << 0 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + andOp.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); +} + +TEST( AndOp, MatchesIndexKeyWithUnknown ) { + BSONObj baseOperand1 = BSON( "$gt" << 1 ); + BSONObj baseOperand2 = BSON( "$lt" << 5 ); + // This part will return PartialMatchResult_Unknown. + BSONObj baseOperand3 = BSON( "$ne" << 5 ); + unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); + ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); + unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); + ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); + unique_ptr<NeOp> sub3( new NeOp() ); + ASSERT( sub3->init( "a", baseOperand3[ "$ne" ] ).isOK() ); + OwnedPointerVector<MatchMatchExpression> subMatchExpressions; + subMatchExpressions.mutableVector().push_back( sub1.release() ); + subMatchExpressions.mutableVector().push_back( sub2.release() ); + subMatchExpressions.mutableVector().push_back( sub3.release() ); + AndOp andOp; + ASSERT( andOp.init( &subMatchExpressions ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + andOp.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + andOp.matchesIndexKey( BSON( "" << 0 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + andOp.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); +} +*/ + +/** +TEST( OrOp, MatchesElementSingleClause ) { + BSONObj baseOperand = BSON( "$lt" << 5 ); + BSONObj match = BSON( "a" << 4 ); + BSONObj notMatch = BSON( "a" << 5 ); + unique_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() ); + ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); + OwnedPointerVector<MatchMatchExpression> subMatchExpressions; + subMatchExpressions.mutableVector().push_back( lt.release() ); + OrOp orOp; + ASSERT( orOp.init( &subMatchExpressions ).isOK() ); + ASSERT( orOp.matchesSingleElement( match[ "a" ] ) ); + ASSERT( !orOp.matchesSingleElement( notMatch[ "a" ] ) ); +} +*/ +TEST(OrOp, NoClauses) { + OrMatchExpression orOp; + ASSERT(!orOp.matchesBSON(BSONObj(), NULL)); +} +/* +TEST( OrOp, MatchesElementThreeClauses ) { + BSONObj baseOperand1 = BSON( "$lt" << 0 ); + BSONObj baseOperand2 = BSON( "$gt" << 10 ); + BSONObj baseOperand3 = BSON( "a" << 5 ); + BSONObj match1 = BSON( "a" << -1 ); + BSONObj match2 = BSON( "a" << 11 ); + BSONObj match3 = BSON( "a" << 5 ); + BSONObj notMatch = BSON( "a" << "6" ); + unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); + ASSERT( sub1->init( "a", baseOperand1[ "$lt" ] ).isOK() ); + unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); + ASSERT( sub2->init( "a", baseOperand2[ "$gt" ] ).isOK() ); + unique_ptr<ComparisonMatchExpression> sub3( new ComparisonMatchExpression() ); + ASSERT( sub3->init( "a", baseOperand3[ "a" ] ).isOK() ); + OwnedPointerVector<MatchMatchExpression> subMatchExpressions; + subMatchExpressions.mutableVector().push_back( sub1.release() ); + subMatchExpressions.mutableVector().push_back( sub2.release() ); + subMatchExpressions.mutableVector().push_back( sub3.release() ); + OrOp orOp; + ASSERT( orOp.init( &subMatchExpressions ).isOK() ); + ASSERT( orOp.matchesSingleElement( match1[ "a" ] ) ); + ASSERT( orOp.matchesSingleElement( match2[ "a" ] ) ); + ASSERT( orOp.matchesSingleElement( match3[ "a" ] ) ); + ASSERT( !orOp.matchesSingleElement( notMatch[ "a" ] ) ); +} +*/ +TEST(OrOp, MatchesSingleClause) { + BSONObj baseOperand = BSON("$ne" << 5); + unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); + ASSERT(eq->init("a", baseOperand["$ne"]).isOK()); + unique_ptr<NotMatchExpression> ne(new NotMatchExpression()); + ASSERT(ne->init(eq.release()).isOK()); + + OrMatchExpression orOp; + orOp.add(ne.release()); + + ASSERT(orOp.matchesBSON(BSON("a" << 4), NULL)); + ASSERT(orOp.matchesBSON(BSON("a" << BSON_ARRAY(4 << 6)), NULL)); + ASSERT(!orOp.matchesBSON(BSON("a" << 5), NULL)); + ASSERT(!orOp.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5)), NULL)); +} + +TEST(OrOp, MatchesThreeClauses) { + BSONObj baseOperand1 = BSON("$gt" << 10); + BSONObj baseOperand2 = BSON("$lt" << 0); + BSONObj baseOperand3 = BSON("b" << 100); + unique_ptr<ComparisonMatchExpression> sub1(new GTMatchExpression()); + ASSERT(sub1->init("a", baseOperand1["$gt"]).isOK()); + unique_ptr<ComparisonMatchExpression> sub2(new LTMatchExpression()); + ASSERT(sub2->init("a", baseOperand2["$lt"]).isOK()); + unique_ptr<ComparisonMatchExpression> sub3(new EqualityMatchExpression()); + ASSERT(sub3->init("b", baseOperand3["b"]).isOK()); + + OrMatchExpression orOp; + orOp.add(sub1.release()); + orOp.add(sub2.release()); + orOp.add(sub3.release()); + + ASSERT(orOp.matchesBSON(BSON("a" << -1), NULL)); + ASSERT(orOp.matchesBSON(BSON("a" << 11), NULL)); + ASSERT(!orOp.matchesBSON(BSON("a" << 5), NULL)); + ASSERT(orOp.matchesBSON(BSON("b" << 100), NULL)); + ASSERT(!orOp.matchesBSON(BSON("b" << 101), NULL)); + ASSERT(!orOp.matchesBSON(BSONObj(), NULL)); + ASSERT(orOp.matchesBSON(BSON("a" << 11 << "b" << 100), NULL)); +} + +TEST(OrOp, ElemMatchKey) { + BSONObj baseOperand1 = BSON("a" << 1); + BSONObj baseOperand2 = BSON("b" << 2); + unique_ptr<ComparisonMatchExpression> sub1(new EqualityMatchExpression()); + ASSERT(sub1->init("a", baseOperand1["a"]).isOK()); + unique_ptr<ComparisonMatchExpression> sub2(new EqualityMatchExpression()); + ASSERT(sub2->init("b", baseOperand2["b"]).isOK()); + + OrMatchExpression orOp; + orOp.add(sub1.release()); + orOp.add(sub2.release()); + + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!orOp.matchesBSON(BSONObj(), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(!orOp.matchesBSON(BSON("a" << BSON_ARRAY(10) << "b" << BSON_ARRAY(10)), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(orOp.matchesBSON(BSON("a" << BSON_ARRAY(1) << "b" << BSON_ARRAY(1 << 2)), &details)); + // The elem match key feature is not implemented for $or. + ASSERT(!details.hasElemMatchKey()); +} + +/** +TEST( OrOp, MatchesIndexKeyWithoutUnknown ) { + BSONObj baseOperand1 = BSON( "$gt" << 5 ); + BSONObj baseOperand2 = BSON( "$lt" << 1 ); + unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); + ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); + unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); + ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); + OwnedPointerVector<MatchMatchExpression> subMatchExpressions; + subMatchExpressions.mutableVector().push_back( sub1.release() ); + subMatchExpressions.mutableVector().push_back( sub2.release() ); + OrOp orOp; + ASSERT( orOp.init( &subMatchExpressions ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_False == + orOp.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + orOp.matchesIndexKey( BSON( "" << 0 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + orOp.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); +} + +TEST( OrOp, MatchesIndexKeyWithUnknown ) { + BSONObj baseOperand1 = BSON( "$gt" << 5 ); + BSONObj baseOperand2 = BSON( "$lt" << 1 ); + // This part will return PartialMatchResult_Unknown. + BSONObj baseOperand3 = BSON( "$ne" << 5 ); + unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); + ASSERT( sub1->init( "a", baseOperand1[ "$gt" ] ).isOK() ); + unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); + ASSERT( sub2->init( "a", baseOperand2[ "$lt" ] ).isOK() ); + unique_ptr<NeOp> sub3( new NeOp() ); + ASSERT( sub3->init( "a", baseOperand3[ "$ne" ] ).isOK() ); + OwnedPointerVector<MatchMatchExpression> subMatchExpressions; + subMatchExpressions.mutableVector().push_back( sub1.release() ); + subMatchExpressions.mutableVector().push_back( sub2.release() ); + subMatchExpressions.mutableVector().push_back( sub3.release() ); + OrOp orOp; + ASSERT( orOp.init( &subMatchExpressions ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + orOp.matchesIndexKey( BSON( "" << 3 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + orOp.matchesIndexKey( BSON( "" << 0 ), indexSpec ) ); + ASSERT( MatchMatchExpression::PartialMatchResult_True == + orOp.matchesIndexKey( BSON( "" << 6 ), indexSpec ) ); +} +*/ + +/** +TEST( NorOp, MatchesElementSingleClause ) { + BSONObj baseOperand = BSON( "$lt" << 5 ); + BSONObj match = BSON( "a" << 5 ); + BSONObj notMatch = BSON( "a" << 4 ); + unique_ptr<ComparisonMatchExpression> lt( new ComparisonMatchExpression() ); + ASSERT( lt->init( "a", baseOperand[ "$lt" ] ).isOK() ); + OwnedPointerVector<MatchMatchExpression> subMatchExpressions; + subMatchExpressions.mutableVector().push_back( lt.release() ); + NorOp norOp; + ASSERT( norOp.init( &subMatchExpressions ).isOK() ); + ASSERT( norOp.matchesSingleElement( match[ "a" ] ) ); + ASSERT( !norOp.matchesSingleElement( notMatch[ "a" ] ) ); +} +*/ + +TEST(NorOp, NoClauses) { + NorMatchExpression norOp; + ASSERT(norOp.matchesBSON(BSONObj(), NULL)); +} +/* +TEST( NorOp, MatchesElementThreeClauses ) { + BSONObj baseOperand1 = BSON( "$lt" << 0 ); + BSONObj baseOperand2 = BSON( "$gt" << 10 ); + BSONObj baseOperand3 = BSON( "a" << 5 ); + BSONObj notMatch1 = BSON( "a" << -1 ); + BSONObj notMatch2 = BSON( "a" << 11 ); + BSONObj notMatch3 = BSON( "a" << 5 ); + BSONObj match = BSON( "a" << "6" ); + unique_ptr<ComparisonMatchExpression> sub1( new ComparisonMatchExpression() ); + ASSERT( sub1->init( "a", baseOperand1[ "$lt" ] ).isOK() ); + unique_ptr<ComparisonMatchExpression> sub2( new ComparisonMatchExpression() ); + ASSERT( sub2->init( "a", baseOperand2[ "$gt" ] ).isOK() ); + unique_ptr<ComparisonMatchExpression> sub3( new ComparisonMatchExpression() ); + ASSERT( sub3->init( "a", baseOperand3[ "a" ] ).isOK() ); + OwnedPointerVector<MatchMatchExpression> subMatchExpressions; + subMatchExpressions.mutableVector().push_back( sub1.release() ); + subMatchExpressions.mutableVector().push_back( sub2.release() ); + subMatchExpressions.mutableVector().push_back( sub3.release() ); + NorOp norOp; + ASSERT( norOp.init( &subMatchExpressions ).isOK() ); + ASSERT( !norOp.matchesSingleElement( notMatch1[ "a" ] ) ); + ASSERT( !norOp.matchesSingleElement( notMatch2[ "a" ] ) ); + ASSERT( !norOp.matchesSingleElement( notMatch3[ "a" ] ) ); + ASSERT( norOp.matchesSingleElement( match[ "a" ] ) ); +} +*/ + +TEST(NorOp, MatchesSingleClause) { + BSONObj baseOperand = BSON("$ne" << 5); + unique_ptr<ComparisonMatchExpression> eq(new EqualityMatchExpression()); + ASSERT(eq->init("a", baseOperand["$ne"]).isOK()); + unique_ptr<NotMatchExpression> ne(new NotMatchExpression()); + ASSERT(ne->init(eq.release()).isOK()); + + NorMatchExpression norOp; + norOp.add(ne.release()); + + ASSERT(!norOp.matchesBSON(BSON("a" << 4), NULL)); + ASSERT(!norOp.matchesBSON(BSON("a" << BSON_ARRAY(4 << 6)), NULL)); + ASSERT(norOp.matchesBSON(BSON("a" << 5), NULL)); + ASSERT(norOp.matchesBSON(BSON("a" << BSON_ARRAY(4 << 5)), NULL)); +} + +TEST(NorOp, MatchesThreeClauses) { + BSONObj baseOperand1 = BSON("$gt" << 10); + BSONObj baseOperand2 = BSON("$lt" << 0); + BSONObj baseOperand3 = BSON("b" << 100); + + unique_ptr<ComparisonMatchExpression> sub1(new GTMatchExpression()); + ASSERT(sub1->init("a", baseOperand1["$gt"]).isOK()); + unique_ptr<ComparisonMatchExpression> sub2(new LTMatchExpression()); + ASSERT(sub2->init("a", baseOperand2["$lt"]).isOK()); + unique_ptr<ComparisonMatchExpression> sub3(new EqualityMatchExpression()); + ASSERT(sub3->init("b", baseOperand3["b"]).isOK()); + + NorMatchExpression norOp; + norOp.add(sub1.release()); + norOp.add(sub2.release()); + norOp.add(sub3.release()); + + ASSERT(!norOp.matchesBSON(BSON("a" << -1), NULL)); + ASSERT(!norOp.matchesBSON(BSON("a" << 11), NULL)); + ASSERT(norOp.matchesBSON(BSON("a" << 5), NULL)); + ASSERT(!norOp.matchesBSON(BSON("b" << 100), NULL)); + ASSERT(norOp.matchesBSON(BSON("b" << 101), NULL)); + ASSERT(norOp.matchesBSON(BSONObj(), NULL)); + ASSERT(!norOp.matchesBSON(BSON("a" << 11 << "b" << 100), NULL)); +} + +TEST(NorOp, ElemMatchKey) { + BSONObj baseOperand1 = BSON("a" << 1); + BSONObj baseOperand2 = BSON("b" << 2); + unique_ptr<ComparisonMatchExpression> sub1(new EqualityMatchExpression()); + ASSERT(sub1->init("a", baseOperand1["a"]).isOK()); + unique_ptr<ComparisonMatchExpression> sub2(new EqualityMatchExpression()); + ASSERT(sub2->init("b", baseOperand2["b"]).isOK()); + + NorMatchExpression norOp; + norOp.add(sub1.release()); + norOp.add(sub2.release()); + + MatchDetails details; + details.requestElemMatchKey(); + ASSERT(!norOp.matchesBSON(BSON("a" << 1), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(!norOp.matchesBSON(BSON("a" << BSON_ARRAY(1) << "b" << BSON_ARRAY(10)), &details)); + ASSERT(!details.hasElemMatchKey()); + ASSERT(norOp.matchesBSON(BSON("a" << BSON_ARRAY(3) << "b" << BSON_ARRAY(4)), &details)); + // The elem match key feature is not implemented for $nor. + 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 ); + unique_ptr<ComparisonMatchExpression> eq( new ComparisonMatchExpression() ); + ASSERT( eq->init( "a", baseOperand[ "a" ] ).isOK() ); + OwnedPointerVector<MatchMatchExpression> subMatchExpressions; + subMatchExpressions.mutableVector().push_back( eq.release() ); + NorOp norOp; + ASSERT( norOp.init( &subMatchExpressions ).isOK() ); + IndexSpec indexSpec( BSON( "a" << 1 ) ); + BSONObj indexKey = BSON( "" << "7" ); + ASSERT( MatchMatchExpression::PartialMatchResult_Unknown == + norOp.matchesIndexKey( indexKey, indexSpec ) ); +} +*/ } diff --git a/src/mongo/db/matcher/expression_where.cpp b/src/mongo/db/matcher/expression_where.cpp index 804ae7dc687..f3d99b79136 100644 --- a/src/mongo/db/matcher/expression_where.cpp +++ b/src/mongo/db/matcher/expression_where.cpp @@ -42,175 +42,162 @@ namespace mongo { - using std::unique_ptr; - using std::endl; - using std::string; - using std::stringstream; +using std::unique_ptr; +using std::endl; +using std::string; +using std::stringstream; - class WhereMatchExpression : public MatchExpression { - public: - WhereMatchExpression(OperationContext* txn) - : MatchExpression(WHERE), - _txn(txn) { +class WhereMatchExpression : public MatchExpression { +public: + WhereMatchExpression(OperationContext* txn) : MatchExpression(WHERE), _txn(txn) { + invariant(_txn != NULL); - invariant(_txn != NULL); + _func = 0; + } - _func = 0; - } + virtual ~WhereMatchExpression() {} - virtual ~WhereMatchExpression(){} + Status init(StringData dbName, StringData theCode, const BSONObj& scope); - Status init(StringData dbName, StringData theCode, const BSONObj& scope); + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const; - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const; + virtual bool matchesSingleElement(const BSONElement& e) const { + return false; + } - virtual bool matchesSingleElement( const BSONElement& e ) const { - return false; + virtual MatchExpression* shallowClone() const { + WhereMatchExpression* e = new WhereMatchExpression(_txn); + e->init(_dbName, _code, _userScope); + if (getTag()) { + e->setTag(getTag()->clone()); } + return e; + } - virtual MatchExpression* shallowClone() const { - WhereMatchExpression* e = new WhereMatchExpression(_txn); - e->init(_dbName, _code, _userScope); - if (getTag()) { - e->setTag(getTag()->clone()); - } - return e; - } + virtual void debugString(StringBuilder& debug, int level = 0) const; - virtual void debugString( StringBuilder& debug, int level = 0 ) const; + virtual void toBSON(BSONObjBuilder* out) const; - virtual void toBSON(BSONObjBuilder* out) const; + virtual bool equivalent(const MatchExpression* other) const; - virtual bool equivalent( const MatchExpression* other ) const ; + virtual void resetTag() { + setTag(NULL); + } - virtual void resetTag() { setTag(NULL); } +private: + string _dbName; + string _code; + BSONObj _userScope; - private: + unique_ptr<Scope> _scope; + ScriptingFunction _func; - string _dbName; - string _code; - BSONObj _userScope; + // Not owned. See comments insde WhereCallbackReal for the lifetime of this pointer. + OperationContext* _txn; +}; - unique_ptr<Scope> _scope; - ScriptingFunction _func; +Status WhereMatchExpression::init(StringData dbName, StringData theCode, const BSONObj& scope) { + if (dbName.size() == 0) { + return Status(ErrorCodes::BadValue, "ns for $where cannot be empty"); + } - // Not owned. See comments insde WhereCallbackReal for the lifetime of this pointer. - OperationContext* _txn; - }; + if (theCode.size() == 0) { + return Status(ErrorCodes::BadValue, "code for $where cannot be empty"); + } - Status WhereMatchExpression::init( StringData dbName, - StringData theCode, - const BSONObj& scope ) { + _dbName = dbName.toString(); + _code = theCode.toString(); + _userScope = scope.getOwned(); - if (dbName.size() == 0) { - return Status(ErrorCodes::BadValue, "ns for $where cannot be empty"); - } + const string userToken = + AuthorizationSession::get(ClientBasic::getCurrent())->getAuthenticatedUserNamesToken(); - if (theCode.size() == 0) { - return Status(ErrorCodes::BadValue, "code for $where cannot be empty"); - } - - _dbName = dbName.toString(); - _code = theCode.toString(); - _userScope = scope.getOwned(); + try { + _scope = globalScriptEngine->getPooledScope(_txn, _dbName, "where" + userToken); + _func = _scope->createFunction(_code.c_str()); + } catch (...) { + return exceptionToStatus(); + } - const string userToken = AuthorizationSession::get(ClientBasic::getCurrent()) - ->getAuthenticatedUserNamesToken(); + if (!_func) + return Status(ErrorCodes::BadValue, "$where compile error"); - try { - _scope = globalScriptEngine->getPooledScope(_txn, _dbName, "where" + userToken); - _func = _scope->createFunction(_code.c_str()); - } catch (...) { - return exceptionToStatus(); - } + return Status::OK(); +} - if ( !_func ) - return Status( ErrorCodes::BadValue, "$where compile error" ); +bool WhereMatchExpression::matches(const MatchableDocument* doc, MatchDetails* details) const { + uassert(28692, "$where compile error", _func); + BSONObj obj = doc->toBSON(); - return Status::OK(); + if (!_userScope.isEmpty()) { + _scope->init(&_userScope); } - bool WhereMatchExpression::matches( const MatchableDocument* doc, MatchDetails* details ) const { - uassert(28692, "$where compile error", _func); - BSONObj obj = doc->toBSON(); - - 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 + _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; + 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); } - void WhereMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << "$where\n"; + return _scope->getBoolean("__returnValue") != 0; +} - _debugAddSpace( debug, level + 1 ); - debug << "dbName: " << _dbName << "\n"; +void WhereMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "$where\n"; - _debugAddSpace( debug, level + 1 ); - debug << "code: " << _code << "\n"; + _debugAddSpace(debug, level + 1); + debug << "dbName: " << _dbName << "\n"; - _debugAddSpace( debug, level + 1 ); - debug << "scope: " << _userScope << "\n"; - } + _debugAddSpace(debug, level + 1); + debug << "code: " << _code << "\n"; - void WhereMatchExpression::toBSON(BSONObjBuilder* out) const { - out->append("$where", _code); - } + _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 - _dbName == realOther->_dbName && - _code == realOther->_code && - _userScope == realOther->_userScope; - } +void WhereMatchExpression::toBSON(BSONObjBuilder* out) const { + out->append("$where", _code); +} - WhereCallbackReal::WhereCallbackReal(OperationContext* txn, StringData dbName) - : _txn(txn), - _dbName(dbName) { +bool WhereMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) + return false; + const WhereMatchExpression* realOther = static_cast<const WhereMatchExpression*>(other); + return _dbName == realOther->_dbName && _code == realOther->_code && + _userScope == realOther->_userScope; +} - } +WhereCallbackReal::WhereCallbackReal(OperationContext* txn, StringData dbName) + : _txn(txn), _dbName(dbName) {} - StatusWithMatchExpression WhereCallbackReal::parseWhere(const BSONElement& where) const { - if (!globalScriptEngine) - return StatusWithMatchExpression(ErrorCodes::BadValue, - "no globalScriptEngine in $where parsing"); - - unique_ptr<WhereMatchExpression> exp(new WhereMatchExpression(_txn)); - if (where.type() == String || where.type() == Code) { - Status s = exp->init(_dbName, where.valuestr(), BSONObj()); - if (!s.isOK()) - return StatusWithMatchExpression(s); - return StatusWithMatchExpression(exp.release()); - } +StatusWithMatchExpression WhereCallbackReal::parseWhere(const BSONElement& where) const { + if (!globalScriptEngine) + return StatusWithMatchExpression(ErrorCodes::BadValue, + "no globalScriptEngine in $where parsing"); - if (where.type() == CodeWScope) { - Status s = exp->init(_dbName, - where.codeWScopeCode(), - BSONObj(where.codeWScopeScopeDataUnsafe())); - if (!s.isOK()) - return StatusWithMatchExpression(s); - return StatusWithMatchExpression(exp.release()); - } + unique_ptr<WhereMatchExpression> exp(new WhereMatchExpression(_txn)); + if (where.type() == String || where.type() == Code) { + Status s = exp->init(_dbName, where.valuestr(), BSONObj()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(exp.release()); + } - return StatusWithMatchExpression(ErrorCodes::BadValue, "$where got bad type"); + if (where.type() == CodeWScope) { + Status s = + exp->init(_dbName, where.codeWScopeCode(), BSONObj(where.codeWScopeScopeDataUnsafe())); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(exp.release()); } + + return StatusWithMatchExpression(ErrorCodes::BadValue, "$where got bad type"); +} } diff --git a/src/mongo/db/matcher/expression_where_noop.cpp b/src/mongo/db/matcher/expression_where_noop.cpp index d7f5a3c74e1..d505542e818 100644 --- a/src/mongo/db/matcher/expression_where_noop.cpp +++ b/src/mongo/db/matcher/expression_where_noop.cpp @@ -35,104 +35,102 @@ namespace mongo { - using std::unique_ptr; - using std::string; - - /** - * Bogus no-op $where match expression to parse $where in mongos, - * since mongos doesn't have script engine to compile JS functions. - * - * Linked into mongos, instead of the real WhereMatchExpression. - */ - class WhereNoOpMatchExpression : public MatchExpression { - public: - WhereNoOpMatchExpression() : MatchExpression( WHERE ){ } - virtual ~WhereNoOpMatchExpression(){} - - Status init( StringData theCode ); - - virtual bool matches( const MatchableDocument* doc, MatchDetails* details = 0 ) const { - return false; - } - - virtual bool matchesSingleElement( const BSONElement& e ) const { - return false; - } +using std::unique_ptr; +using std::string; - virtual MatchExpression* shallowClone() const { - WhereNoOpMatchExpression* e = new WhereNoOpMatchExpression(); - e->init(_code); - if ( getTag() ) { - e->setTag(getTag()->clone()); - } - return e; - } +/** + * Bogus no-op $where match expression to parse $where in mongos, + * since mongos doesn't have script engine to compile JS functions. + * + * Linked into mongos, instead of the real WhereMatchExpression. + */ +class WhereNoOpMatchExpression : public MatchExpression { +public: + WhereNoOpMatchExpression() : MatchExpression(WHERE) {} + virtual ~WhereNoOpMatchExpression() {} - virtual void debugString( StringBuilder& debug, int level = 0 ) const; + Status init(StringData theCode); - virtual void toBSON(BSONObjBuilder* out) const; + virtual bool matches(const MatchableDocument* doc, MatchDetails* details = 0) const { + return false; + } - virtual bool equivalent( const MatchExpression* other ) const ; + virtual bool matchesSingleElement(const BSONElement& e) const { + return false; + } - virtual void resetTag() { setTag(NULL); } + virtual MatchExpression* shallowClone() const { + WhereNoOpMatchExpression* e = new WhereNoOpMatchExpression(); + e->init(_code); + if (getTag()) { + e->setTag(getTag()->clone()); + } + return e; + } - private: - string _code; - }; + virtual void debugString(StringBuilder& debug, int level = 0) const; - Status WhereNoOpMatchExpression::init(StringData theCode ) { - if ( theCode.size() == 0 ) - return Status( ErrorCodes::BadValue, "code for $where cannot be empty" ); + virtual void toBSON(BSONObjBuilder* out) const; - _code = theCode.toString(); + virtual bool equivalent(const MatchExpression* other) const; - return Status::OK(); + virtual void resetTag() { + setTag(NULL); } - void WhereNoOpMatchExpression::debugString( StringBuilder& debug, int level ) const { - _debugAddSpace( debug, level ); - debug << "$where (only in mongos)\n"; +private: + string _code; +}; - _debugAddSpace( debug, level + 1 ); - debug << "code: " << _code << "\n"; - } +Status WhereNoOpMatchExpression::init(StringData theCode) { + if (theCode.size() == 0) + return Status(ErrorCodes::BadValue, "code for $where cannot be empty"); - void WhereNoOpMatchExpression::toBSON(BSONObjBuilder* out) const { - out->append("$where", _code); - } + _code = theCode.toString(); - bool WhereNoOpMatchExpression::equivalent( const MatchExpression* other ) const { - if ( matchType() != other->matchType() ) - return false; - const WhereNoOpMatchExpression* noopOther = static_cast<const WhereNoOpMatchExpression*>(other); - return _code == noopOther->_code; - } + return Status::OK(); +} +void WhereNoOpMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + debug << "$where (only in mongos)\n"; - // ----------------- + _debugAddSpace(debug, level + 1); + debug << "code: " << _code << "\n"; +} - WhereCallbackNoop::WhereCallbackNoop() { +void WhereNoOpMatchExpression::toBSON(BSONObjBuilder* out) const { + out->append("$where", _code); +} - } +bool WhereNoOpMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) + return false; + const WhereNoOpMatchExpression* noopOther = static_cast<const WhereNoOpMatchExpression*>(other); + return _code == noopOther->_code; +} - StatusWithMatchExpression WhereCallbackNoop::parseWhere(const BSONElement& where) const { +// ----------------- - unique_ptr<WhereNoOpMatchExpression> exp( new WhereNoOpMatchExpression() ); - if ( where.type() == String || where.type() == Code ) { - Status s = exp->init( where.valuestr() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( exp.release() ); - } +WhereCallbackNoop::WhereCallbackNoop() {} - if ( where.type() == CodeWScope ) { - Status s = exp->init( where.codeWScopeCode() ); - if ( !s.isOK() ) - return StatusWithMatchExpression( s ); - return StatusWithMatchExpression( exp.release() ); - } +StatusWithMatchExpression WhereCallbackNoop::parseWhere(const BSONElement& where) const { + unique_ptr<WhereNoOpMatchExpression> exp(new WhereNoOpMatchExpression()); + if (where.type() == String || where.type() == Code) { + Status s = exp->init(where.valuestr()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(exp.release()); + } - return StatusWithMatchExpression( ErrorCodes::BadValue, "$where got bad type" ); + if (where.type() == CodeWScope) { + Status s = exp->init(where.codeWScopeCode()); + if (!s.isOK()) + return StatusWithMatchExpression(s); + return StatusWithMatchExpression(exp.release()); } + + return StatusWithMatchExpression(ErrorCodes::BadValue, "$where got bad type"); +} } diff --git a/src/mongo/db/matcher/match_details.cpp b/src/mongo/db/matcher/match_details.cpp index 6adcfcdd910..6675bf08e15 100644 --- a/src/mongo/db/matcher/match_details.cpp +++ b/src/mongo/db/matcher/match_details.cpp @@ -36,39 +36,37 @@ namespace mongo { - using std::string; +using std::string; - MatchDetails::MatchDetails() : - _elemMatchKeyRequested() { - resetOutput(); - } - - void MatchDetails::resetOutput() { - _loadedRecord = false; - _elemMatchKey.reset(); - } +MatchDetails::MatchDetails() : _elemMatchKeyRequested() { + resetOutput(); +} - bool MatchDetails::hasElemMatchKey() const { - return _elemMatchKey.get(); - } +void MatchDetails::resetOutput() { + _loadedRecord = false; + _elemMatchKey.reset(); +} - std::string MatchDetails::elemMatchKey() const { - verify( hasElemMatchKey() ); - return *(_elemMatchKey.get()); - } +bool MatchDetails::hasElemMatchKey() const { + return _elemMatchKey.get(); +} - void MatchDetails::setElemMatchKey( const std::string &elemMatchKey ) { - if ( _elemMatchKeyRequested ) { - _elemMatchKey.reset( new std::string( elemMatchKey ) ); - } - } +std::string MatchDetails::elemMatchKey() const { + verify(hasElemMatchKey()); + return *(_elemMatchKey.get()); +} - string MatchDetails::toString() const { - std::stringstream ss; - ss << "loadedRecord: " << _loadedRecord << " "; - ss << "elemMatchKeyRequested: " << _elemMatchKeyRequested << " "; - ss << "elemMatchKey: " << ( _elemMatchKey ? _elemMatchKey->c_str() : "NONE" ) << " "; - return ss.str(); +void MatchDetails::setElemMatchKey(const std::string& elemMatchKey) { + if (_elemMatchKeyRequested) { + _elemMatchKey.reset(new std::string(elemMatchKey)); } +} +string MatchDetails::toString() const { + std::stringstream ss; + ss << "loadedRecord: " << _loadedRecord << " "; + ss << "elemMatchKeyRequested: " << _elemMatchKeyRequested << " "; + ss << "elemMatchKey: " << (_elemMatchKey ? _elemMatchKey->c_str() : "NONE") << " "; + return ss.str(); +} } diff --git a/src/mongo/db/matcher/match_details.h b/src/mongo/db/matcher/match_details.h index 8d5f016747f..15a0a606678 100644 --- a/src/mongo/db/matcher/match_details.h +++ b/src/mongo/db/matcher/match_details.h @@ -35,39 +35,47 @@ namespace mongo { - /** Reports information about a match request. */ - class MatchDetails { - public: - MatchDetails(); +/** Reports information about a match request. */ +class MatchDetails { +public: + MatchDetails(); - void resetOutput(); + void resetOutput(); - // for debugging only - std::string toString() const; + // for debugging only + std::string toString() const; - // relating to whether or not we had to load the full record + // relating to whether or not we had to load the full record - void setLoadedRecord( bool loadedRecord ) { _loadedRecord = loadedRecord; } + void setLoadedRecord(bool loadedRecord) { + _loadedRecord = loadedRecord; + } - bool hasLoadedRecord() const { return _loadedRecord; } + bool hasLoadedRecord() const { + return _loadedRecord; + } - // this name is wrong + // this name is wrong - bool needRecord() const { return _elemMatchKeyRequested; } + bool needRecord() const { + return _elemMatchKeyRequested; + } - // if we need to store the offset into an array where we found the match + // if we need to store the offset into an array where we found the match - /** Request that an elemMatchKey be recorded. */ - void requestElemMatchKey() { _elemMatchKeyRequested = true; } + /** Request that an elemMatchKey be recorded. */ + void requestElemMatchKey() { + _elemMatchKeyRequested = true; + } - bool hasElemMatchKey() const; - std::string elemMatchKey() const; + bool hasElemMatchKey() const; + std::string elemMatchKey() const; - void setElemMatchKey( const std::string &elemMatchKey ); + void setElemMatchKey(const std::string& elemMatchKey); - private: - bool _loadedRecord; - bool _elemMatchKeyRequested; - std::unique_ptr<std::string> _elemMatchKey; - }; +private: + bool _loadedRecord; + bool _elemMatchKeyRequested; + std::unique_ptr<std::string> _elemMatchKey; +}; } diff --git a/src/mongo/db/matcher/matchable.cpp b/src/mongo/db/matcher/matchable.cpp index b28f78f5ecf..bb5671ea801 100644 --- a/src/mongo/db/matcher/matchable.cpp +++ b/src/mongo/db/matcher/matchable.cpp @@ -34,12 +34,9 @@ namespace mongo { - BSONMatchableDocument::BSONMatchableDocument( const BSONObj& obj ) - : _obj( obj ) { - _iteratorUsed = false; - } - - BSONMatchableDocument::~BSONMatchableDocument() { - } +BSONMatchableDocument::BSONMatchableDocument(const BSONObj& obj) : _obj(obj) { + _iteratorUsed = false; +} +BSONMatchableDocument::~BSONMatchableDocument() {} } diff --git a/src/mongo/db/matcher/matchable.h b/src/mongo/db/matcher/matchable.h index a3506005901..66d1a417af0 100644 --- a/src/mongo/db/matcher/matchable.h +++ b/src/mongo/db/matcher/matchable.h @@ -36,70 +36,72 @@ namespace mongo { - class MatchableDocument { - public: - // Inlining to allow subclasses to see that this is a no-op and avoid a function call. - // Speeds up query execution measurably. - virtual ~MatchableDocument() {} - - virtual BSONObj toBSON() const = 0; - - /** - * The neewly returned ElementIterator is allowed to keep a pointer to path. - * So the caller of this function should make sure path is in scope until - * the ElementIterator is deallocated - */ - virtual ElementIterator* allocateIterator( const ElementPath* path ) const = 0; - - virtual void releaseIterator( ElementIterator* iterator ) const = 0; - - class IteratorHolder { - public: - IteratorHolder( const MatchableDocument* doc, const ElementPath* path ) { - _doc = doc; - _iterator = _doc->allocateIterator( path ); - } - - ~IteratorHolder() { - _doc->releaseIterator( _iterator ); - } - - ElementIterator* operator->() const { - return _iterator; - } - private: - const MatchableDocument* _doc; - ElementIterator* _iterator; - }; - }; +class MatchableDocument { +public: + // Inlining to allow subclasses to see that this is a no-op and avoid a function call. + // Speeds up query execution measurably. + virtual ~MatchableDocument() {} - class BSONMatchableDocument : public MatchableDocument { - public: - BSONMatchableDocument( const BSONObj& obj ); - virtual ~BSONMatchableDocument(); + virtual BSONObj toBSON() const = 0; - virtual BSONObj toBSON() const { return _obj; } + /** + * The neewly returned ElementIterator is allowed to keep a pointer to path. + * So the caller of this function should make sure path is in scope until + * the ElementIterator is deallocated + */ + virtual ElementIterator* allocateIterator(const ElementPath* path) const = 0; - virtual ElementIterator* allocateIterator( const ElementPath* path ) const { - if ( _iteratorUsed ) - return new BSONElementIterator( path, _obj ); - _iteratorUsed = true; - _iterator.reset( path, _obj ); - return &_iterator; + virtual void releaseIterator(ElementIterator* iterator) const = 0; + + class IteratorHolder { + public: + IteratorHolder(const MatchableDocument* doc, const ElementPath* path) { + _doc = doc; + _iterator = _doc->allocateIterator(path); } - virtual void releaseIterator( ElementIterator* iterator ) const { - if ( iterator == &_iterator ) { - _iteratorUsed = false; - } - else { - delete iterator; - } + ~IteratorHolder() { + _doc->releaseIterator(_iterator); + } + + ElementIterator* operator->() const { + return _iterator; } private: - BSONObj _obj; - mutable BSONElementIterator _iterator; - mutable bool _iteratorUsed; + const MatchableDocument* _doc; + ElementIterator* _iterator; }; +}; + +class BSONMatchableDocument : public MatchableDocument { +public: + BSONMatchableDocument(const BSONObj& obj); + virtual ~BSONMatchableDocument(); + + virtual BSONObj toBSON() const { + return _obj; + } + + virtual ElementIterator* allocateIterator(const ElementPath* path) const { + if (_iteratorUsed) + return new BSONElementIterator(path, _obj); + _iteratorUsed = true; + _iterator.reset(path, _obj); + return &_iterator; + } + + virtual void releaseIterator(ElementIterator* iterator) const { + if (iterator == &_iterator) { + _iteratorUsed = false; + } else { + delete iterator; + } + } + +private: + BSONObj _obj; + mutable BSONElementIterator _iterator; + mutable bool _iteratorUsed; +}; } diff --git a/src/mongo/db/matcher/matcher.cpp b/src/mongo/db/matcher/matcher.cpp index e57bae6fff7..da7e68e2f09 100644 --- a/src/mongo/db/matcher/matcher.cpp +++ b/src/mongo/db/matcher/matcher.cpp @@ -41,23 +41,21 @@ namespace mongo { - Matcher::Matcher(const BSONObj& pattern, - const MatchExpressionParser::WhereCallback& whereCallback) - : _pattern(pattern) { - - StatusWithMatchExpression result = MatchExpressionParser::parse(pattern, whereCallback); - uassert( 16810, - mongoutils::str::stream() << "bad query: " << result.getStatus().toString(), - result.isOK() ); - - _expression.reset( result.getValue() ); - } - - bool Matcher::matches(const BSONObj& doc, MatchDetails* details ) const { - if ( !_expression ) - return true; - - return _expression->matchesBSON( doc, details ); - } +Matcher::Matcher(const BSONObj& pattern, const MatchExpressionParser::WhereCallback& whereCallback) + : _pattern(pattern) { + StatusWithMatchExpression result = MatchExpressionParser::parse(pattern, whereCallback); + uassert(16810, + mongoutils::str::stream() << "bad query: " << result.getStatus().toString(), + result.isOK()); + + _expression.reset(result.getValue()); +} + +bool Matcher::matches(const BSONObj& doc, MatchDetails* details) const { + if (!_expression) + return true; + + return _expression->matchesBSON(doc, details); +} } // namespace mongo diff --git a/src/mongo/db/matcher/matcher.h b/src/mongo/db/matcher/matcher.h index 629f08facd5..e7ea9d93f1a 100644 --- a/src/mongo/db/matcher/matcher.h +++ b/src/mongo/db/matcher/matcher.h @@ -41,27 +41,31 @@ namespace mongo { - /** - * Matcher is a simple wrapper around a BSONObj and the MatchExpression created from it. - */ - class Matcher { - MONGO_DISALLOW_COPYING(Matcher); +/** + * Matcher is a simple wrapper around a BSONObj and the MatchExpression created from it. + */ +class Matcher { + MONGO_DISALLOW_COPYING(Matcher); - public: - explicit Matcher(const BSONObj& pattern, - const MatchExpressionParser::WhereCallback& whereCallback = - MatchExpressionParser::WhereCallback()); +public: + explicit Matcher(const BSONObj& pattern, + const MatchExpressionParser::WhereCallback& whereCallback = + MatchExpressionParser::WhereCallback()); - bool matches(const BSONObj& doc, MatchDetails* details = NULL ) const; + bool matches(const BSONObj& doc, MatchDetails* details = NULL) const; - const BSONObj* getQuery() const { return &_pattern; }; + const BSONObj* getQuery() const { + return &_pattern; + }; - std::string toString() const { return _pattern.toString(); } + std::string toString() const { + return _pattern.toString(); + } - private: - BSONObj _pattern; +private: + BSONObj _pattern; - std::unique_ptr<MatchExpression> _expression; - }; + std::unique_ptr<MatchExpression> _expression; +}; } // namespace mongo diff --git a/src/mongo/db/matcher/path.cpp b/src/mongo/db/matcher/path.cpp index 80fdd3e8ec8..3dd9374faa7 100644 --- a/src/mongo/db/matcher/path.cpp +++ b/src/mongo/db/matcher/path.cpp @@ -35,295 +35,280 @@ namespace mongo { - Status ElementPath::init( StringData path ) { - _shouldTraverseNonleafArrays = true; - _shouldTraverseLeafArray = true; - _fieldRef.parse( path ); - return Status::OK(); - } - - // ----- +Status ElementPath::init(StringData path) { + _shouldTraverseNonleafArrays = true; + _shouldTraverseLeafArray = true; + _fieldRef.parse(path); + return Status::OK(); +} - ElementIterator::~ElementIterator(){ - } +// ----- - void ElementIterator::Context::reset() { - _element = BSONElement(); - } +ElementIterator::~ElementIterator() {} - void ElementIterator::Context::reset( BSONElement element, - BSONElement arrayOffset, - bool outerArray ) { - _element = element; - _arrayOffset = arrayOffset; - _outerArray = outerArray; - } +void ElementIterator::Context::reset() { + _element = BSONElement(); +} +void ElementIterator::Context::reset(BSONElement element, + BSONElement arrayOffset, + bool outerArray) { + _element = element; + _arrayOffset = arrayOffset; + _outerArray = outerArray; +} - // ------ - SimpleArrayElementIterator::SimpleArrayElementIterator( const BSONElement& theArray, bool returnArrayLast ) - : _theArray( theArray ), _returnArrayLast( returnArrayLast ), _iterator( theArray.Obj() ) { +// ------ - } +SimpleArrayElementIterator::SimpleArrayElementIterator(const BSONElement& theArray, + bool returnArrayLast) + : _theArray(theArray), _returnArrayLast(returnArrayLast), _iterator(theArray.Obj()) {} - bool SimpleArrayElementIterator::more() { - return _iterator.more() || _returnArrayLast; - } +bool SimpleArrayElementIterator::more() { + return _iterator.more() || _returnArrayLast; +} - ElementIterator::Context SimpleArrayElementIterator::next() { - if ( _iterator.more() ) { - Context e; - e.reset( _iterator.next(), BSONElement(), false ); - return e; - } - _returnArrayLast = false; +ElementIterator::Context SimpleArrayElementIterator::next() { + if (_iterator.more()) { Context e; - e.reset( _theArray, BSONElement(), true ); + e.reset(_iterator.next(), BSONElement(), false); return e; } + _returnArrayLast = false; + Context e; + e.reset(_theArray, BSONElement(), true); + return e; +} +// ------ +BSONElementIterator::BSONElementIterator() { + _path = NULL; +} - // ------ - BSONElementIterator::BSONElementIterator() { - _path = NULL; - } - - BSONElementIterator::BSONElementIterator( const ElementPath* path, const BSONObj& context ) - : _path( path ), _context( context ) { - _state = BEGIN; - //log() << "path: " << path.fieldRef().dottedField() << " context: " << context << endl; - } +BSONElementIterator::BSONElementIterator(const ElementPath* path, const BSONObj& context) + : _path(path), _context(context) { + _state = BEGIN; + // log() << "path: " << path.fieldRef().dottedField() << " context: " << context << endl; +} - BSONElementIterator::~BSONElementIterator() { - } +BSONElementIterator::~BSONElementIterator() {} - void BSONElementIterator::reset( const ElementPath* path, const BSONObj& context ) { - _path = path; - _context = context; - _state = BEGIN; - _next.reset(); +void BSONElementIterator::reset(const ElementPath* path, const BSONObj& context) { + _path = path; + _context = context; + _state = BEGIN; + _next.reset(); - _subCursor.reset(); - _subCursorPath.reset(); - } + _subCursor.reset(); + _subCursorPath.reset(); +} - void BSONElementIterator::ArrayIterationState::reset( const FieldRef& ref, int start ) { - restOfPath = ref.dottedField( start ).toString(); - hasMore = restOfPath.size() > 0; - if ( hasMore ) { - nextPieceOfPath = ref.getPart( start ); - nextPieceOfPathIsNumber = isAllDigits( nextPieceOfPath ); - } - else { - nextPieceOfPathIsNumber = false; - } +void BSONElementIterator::ArrayIterationState::reset(const FieldRef& ref, int start) { + restOfPath = ref.dottedField(start).toString(); + hasMore = restOfPath.size() > 0; + if (hasMore) { + nextPieceOfPath = ref.getPart(start); + nextPieceOfPathIsNumber = isAllDigits(nextPieceOfPath); + } else { + nextPieceOfPathIsNumber = false; } +} - bool BSONElementIterator::ArrayIterationState::isArrayOffsetMatch( StringData fieldName ) const { - if ( !nextPieceOfPathIsNumber ) - return false; - return nextPieceOfPath == fieldName; - } +bool BSONElementIterator::ArrayIterationState::isArrayOffsetMatch(StringData fieldName) const { + if (!nextPieceOfPathIsNumber) + return false; + return nextPieceOfPath == fieldName; +} - void BSONElementIterator::ArrayIterationState::startIterator( BSONElement e ) { - _theArray = e; - _iterator.reset( new BSONObjIterator( _theArray.Obj() ) ); - } +void BSONElementIterator::ArrayIterationState::startIterator(BSONElement e) { + _theArray = e; + _iterator.reset(new BSONObjIterator(_theArray.Obj())); +} - bool BSONElementIterator::ArrayIterationState::more() { - return _iterator && _iterator->more(); - } +bool BSONElementIterator::ArrayIterationState::more() { + return _iterator && _iterator->more(); +} - BSONElement BSONElementIterator::ArrayIterationState::next() { - _current = _iterator->next(); - return _current; - } +BSONElement BSONElementIterator::ArrayIterationState::next() { + _current = _iterator->next(); + return _current; +} - bool BSONElementIterator::subCursorHasMore() { - // While we still are still finding arrays along the path, keep traversing deeper. - while ( _subCursor ) { +bool BSONElementIterator::subCursorHasMore() { + // While we still are still finding arrays along the path, keep traversing deeper. + while (_subCursor) { + if (_subCursor->more()) { + return true; + } + _subCursor.reset(); - if ( _subCursor->more() ) { + // If the subcursor doesn't have more, see if the current element is an array offset + // match (see comment in BSONElementIterator::more() for an example). If it is indeed + // an array offset match, create a new subcursor and examine it. + if (_arrayIterationState.isArrayOffsetMatch(_arrayIterationState._current.fieldName())) { + if (_arrayIterationState.nextEntireRest()) { + // Our path terminates at the array offset. _next should point at the current + // array element. + _next.reset(_arrayIterationState._current, _arrayIterationState._current, true); + _arrayIterationState._current = BSONElement(); return true; } - _subCursor.reset(); - - // If the subcursor doesn't have more, see if the current element is an array offset - // match (see comment in BSONElementIterator::more() for an example). If it is indeed - // an array offset match, create a new subcursor and examine it. - if ( _arrayIterationState.isArrayOffsetMatch( _arrayIterationState._current.fieldName() ) ) { - if ( _arrayIterationState.nextEntireRest() ) { - // Our path terminates at the array offset. _next should point at the current - // array element. - _next.reset( _arrayIterationState._current, - _arrayIterationState._current, - true ); - _arrayIterationState._current = BSONElement(); - return true; - } - _subCursorPath.reset( new ElementPath() ); - _subCursorPath->init( _arrayIterationState.restOfPath.substr( _arrayIterationState.nextPieceOfPath.size() + 1 ) ); - _subCursorPath->setTraverseLeafArray( _path->shouldTraverseLeafArray() ); + _subCursorPath.reset(new ElementPath()); + _subCursorPath->init(_arrayIterationState.restOfPath.substr( + _arrayIterationState.nextPieceOfPath.size() + 1)); + _subCursorPath->setTraverseLeafArray(_path->shouldTraverseLeafArray()); - // If we're here, we must be able to traverse nonleaf arrays. - dassert(_path->shouldTraverseNonleafArrays()); - dassert(_subCursorPath->shouldTraverseNonleafArrays()); - - _subCursor.reset( new BSONElementIterator( _subCursorPath.get(), - _arrayIterationState._current.Obj() ) ); - _arrayIterationState._current = BSONElement(); - } + // If we're here, we must be able to traverse nonleaf arrays. + dassert(_path->shouldTraverseNonleafArrays()); + dassert(_subCursorPath->shouldTraverseNonleafArrays()); + _subCursor.reset( + new BSONElementIterator(_subCursorPath.get(), _arrayIterationState._current.Obj())); + _arrayIterationState._current = BSONElement(); } + } + return false; +} + +bool BSONElementIterator::more() { + if (subCursorHasMore()) { + return true; + } + + if (!_next.element().eoo()) { + return true; + } + + if (_state == DONE) { return false; } - bool BSONElementIterator::more() { - if ( subCursorHasMore() ) { - return true; - } + if (_state == BEGIN) { + size_t idxPath = 0; + BSONElement e = getFieldDottedOrArray(_context, _path->fieldRef(), &idxPath); - if ( !_next.element().eoo() ) { + if (e.type() != Array) { + _next.reset(e, BSONElement(), false); + _state = DONE; return true; } - if ( _state == DONE ){ + // It's an array. + + _arrayIterationState.reset(_path->fieldRef(), idxPath + 1); + + if (_arrayIterationState.hasMore && !_path->shouldTraverseNonleafArrays()) { + // Don't allow traversing the array. + _state = DONE; return false; + } else if (!_arrayIterationState.hasMore && !_path->shouldTraverseLeafArray()) { + // Return the leaf array. + _next.reset(e, BSONElement(), true); + _state = DONE; + return true; } - if ( _state == BEGIN ) { - size_t idxPath = 0; - BSONElement e = getFieldDottedOrArray( _context, _path->fieldRef(), &idxPath ); - - if ( e.type() != Array ) { - _next.reset( e, BSONElement(), false ); - _state = DONE; - return true; - } + _arrayIterationState.startIterator(e); + _state = IN_ARRAY; - // It's an array. + invariant(_next.element().eoo()); + } - _arrayIterationState.reset( _path->fieldRef(), idxPath + 1 ); + if (_state == IN_ARRAY) { + // We're traversing an array. Look at each array element. - if (_arrayIterationState.hasMore && !_path->shouldTraverseNonleafArrays()) { - // Don't allow traversing the array. - _state = DONE; - return false; - } - else if (!_arrayIterationState.hasMore && !_path->shouldTraverseLeafArray()) { - // Return the leaf array. - _next.reset(e, BSONElement(), true); - _state = DONE; + while (_arrayIterationState.more()) { + BSONElement eltInArray = _arrayIterationState.next(); + if (!_arrayIterationState.hasMore) { + // Our path terminates at this array. _next should point at the current array + // element. + _next.reset(eltInArray, eltInArray, false); return true; } - _arrayIterationState.startIterator( e ); - _state = IN_ARRAY; + // Our path does not terminate at this array; there's a subpath left over. Inspect + // the current array element to see if it could match the subpath. - invariant( _next.element().eoo() ); - } - - if ( _state == IN_ARRAY ) { - // We're traversing an array. Look at each array element. - - while ( _arrayIterationState.more() ) { + if (eltInArray.type() == Object) { + // The current array element is a subdocument. See if the subdocument generates + // any elements matching the remaining subpath. + _subCursorPath.reset(new ElementPath()); + _subCursorPath->init(_arrayIterationState.restOfPath); + _subCursorPath->setTraverseLeafArray(_path->shouldTraverseLeafArray()); - BSONElement eltInArray = _arrayIterationState.next(); - if ( !_arrayIterationState.hasMore ) { - // Our path terminates at this array. _next should point at the current array - // element. - _next.reset( eltInArray, eltInArray, false ); + _subCursor.reset(new BSONElementIterator(_subCursorPath.get(), eltInArray.Obj())); + if (subCursorHasMore()) { return true; } - - // Our path does not terminate at this array; there's a subpath left over. Inspect - // the current array element to see if it could match the subpath. - - if ( eltInArray.type() == Object ) { - // The current array element is a subdocument. See if the subdocument generates - // any elements matching the remaining subpath. - _subCursorPath.reset( new ElementPath() ); - _subCursorPath->init( _arrayIterationState.restOfPath ); - _subCursorPath->setTraverseLeafArray( _path->shouldTraverseLeafArray() ); - - _subCursor.reset( new BSONElementIterator( _subCursorPath.get(), - eltInArray.Obj() ) ); - if ( subCursorHasMore() ) { - return true; - } + } else if (_arrayIterationState.isArrayOffsetMatch(eltInArray.fieldName())) { + // The path we're traversing has an array offset component, and the current + // array element corresponds to the offset we're looking for (for example: our + // path has a ".0" component, and we're looking at the first element of the + // array, so we should look inside this element). + + if (_arrayIterationState.nextEntireRest()) { + // Our path terminates at the array offset. _next should point at the + // current array element. + _next.reset(eltInArray, eltInArray, false); + return true; } - else if ( _arrayIterationState.isArrayOffsetMatch( eltInArray.fieldName() ) ) { - // The path we're traversing has an array offset component, and the current - // array element corresponds to the offset we're looking for (for example: our - // path has a ".0" component, and we're looking at the first element of the - // array, so we should look inside this element). - - if ( _arrayIterationState.nextEntireRest() ) { - // Our path terminates at the array offset. _next should point at the - // current array element. - _next.reset( eltInArray, eltInArray, false ); - return true; - } - invariant( eltInArray.type() != Object ); // Handled above. - if ( eltInArray.type() == Array ) { - // The current array element is itself an array. See if the nested array - // has any elements matching the remainihng. - _subCursorPath.reset( new ElementPath() ); - _subCursorPath->init( _arrayIterationState.restOfPath.substr( _arrayIterationState.nextPieceOfPath.size() + 1 ) ); - _subCursorPath->setTraverseLeafArray( _path->shouldTraverseLeafArray() ); - BSONElementIterator* real = - new BSONElementIterator( _subCursorPath.get(), - _arrayIterationState._current.Obj() ); - _subCursor.reset( real ); - real->_arrayIterationState.reset( _subCursorPath->fieldRef(), 0 ); - real->_arrayIterationState.startIterator( eltInArray ); - real->_state = IN_ARRAY; - _arrayIterationState._current = BSONElement(); - if ( subCursorHasMore() ) { - return true; - } + invariant(eltInArray.type() != Object); // Handled above. + if (eltInArray.type() == Array) { + // The current array element is itself an array. See if the nested array + // has any elements matching the remainihng. + _subCursorPath.reset(new ElementPath()); + _subCursorPath->init(_arrayIterationState.restOfPath.substr( + _arrayIterationState.nextPieceOfPath.size() + 1)); + _subCursorPath->setTraverseLeafArray(_path->shouldTraverseLeafArray()); + BSONElementIterator* real = new BSONElementIterator( + _subCursorPath.get(), _arrayIterationState._current.Obj()); + _subCursor.reset(real); + real->_arrayIterationState.reset(_subCursorPath->fieldRef(), 0); + real->_arrayIterationState.startIterator(eltInArray); + real->_state = IN_ARRAY; + _arrayIterationState._current = BSONElement(); + if (subCursorHasMore()) { + return true; } } - - } - - if ( _arrayIterationState.hasMore ) { - return false; } + } - _next.reset( _arrayIterationState._theArray, BSONElement(), true ); - _state = DONE; - return true; + if (_arrayIterationState.hasMore) { + return false; } - return false; + _next.reset(_arrayIterationState._theArray, BSONElement(), true); + _state = DONE; + return true; } - ElementIterator::Context BSONElementIterator::next() { - if ( _subCursor ) { - Context e = _subCursor->next(); - // Use our array offset if we have one, otherwise copy our subcursor's. This has the - // effect of preferring the outermost array offset, in the case where we are implicitly - // traversing nested arrays and have multiple candidate array offsets. For example, - // when we use the path "a.b" to generate elements from the document {a: [{b: [1, 2]}]}, - // the element with a value of 2 should be returned with an array offset of 0. - if ( !_arrayIterationState._current.eoo() ) { - e.setArrayOffset( _arrayIterationState._current ); - } - return e; + return false; +} + +ElementIterator::Context BSONElementIterator::next() { + if (_subCursor) { + Context e = _subCursor->next(); + // Use our array offset if we have one, otherwise copy our subcursor's. This has the + // effect of preferring the outermost array offset, in the case where we are implicitly + // traversing nested arrays and have multiple candidate array offsets. For example, + // when we use the path "a.b" to generate elements from the document {a: [{b: [1, 2]}]}, + // the element with a value of 2 should be returned with an array offset of 0. + if (!_arrayIterationState._current.eoo()) { + e.setArrayOffset(_arrayIterationState._current); } - Context x = _next; - _next.reset(); - return x; + return e; } - - + Context x = _next; + _next.reset(); + return x; +} } diff --git a/src/mongo/db/matcher/path.h b/src/mongo/db/matcher/path.h index 11fec9ff673..471a1af47e6 100644 --- a/src/mongo/db/matcher/path.h +++ b/src/mongo/db/matcher/path.h @@ -38,132 +38,152 @@ namespace mongo { - class ElementPath { +class ElementPath { +public: + Status init(StringData path); + + void setTraverseNonleafArrays(bool b) { + _shouldTraverseNonleafArrays = b; + } + void setTraverseLeafArray(bool b) { + _shouldTraverseLeafArray = b; + } + + const FieldRef& fieldRef() const { + return _fieldRef; + } + bool shouldTraverseNonleafArrays() const { + return _shouldTraverseNonleafArrays; + } + bool shouldTraverseLeafArray() const { + return _shouldTraverseLeafArray; + } + +private: + FieldRef _fieldRef; + bool _shouldTraverseNonleafArrays; + bool _shouldTraverseLeafArray; +}; + +class ElementIterator { +public: + class Context { public: - Status init( StringData path ); + void reset(); - void setTraverseNonleafArrays( bool b ) { _shouldTraverseNonleafArrays = b; } - void setTraverseLeafArray( bool b ) { _shouldTraverseLeafArray = b; } + void reset(BSONElement element, BSONElement arrayOffset, bool outerArray); - const FieldRef& fieldRef() const { return _fieldRef; } - bool shouldTraverseNonleafArrays() const { return _shouldTraverseNonleafArrays; } - bool shouldTraverseLeafArray() const { return _shouldTraverseLeafArray; } + void setArrayOffset(BSONElement e) { + _arrayOffset = e; + } + + BSONElement element() const { + return _element; + } + BSONElement arrayOffset() const { + return _arrayOffset; + } + bool outerArray() const { + return _outerArray; + } private: - FieldRef _fieldRef; - bool _shouldTraverseNonleafArrays; - bool _shouldTraverseLeafArray; + BSONElement _element; + BSONElement _arrayOffset; + bool _outerArray; }; - class ElementIterator { - public: - class Context { - public: + virtual ~ElementIterator(); - void reset(); + virtual bool more() = 0; + virtual Context next() = 0; +}; - void reset( BSONElement element, BSONElement arrayOffset, bool outerArray ); +// --------------------------------------------------------------- - void setArrayOffset( BSONElement e ) { _arrayOffset = e; } +class SingleElementElementIterator : public ElementIterator { +public: + explicit SingleElementElementIterator(BSONElement e) : _seen(false) { + _element.reset(e, BSONElement(), false); + } + virtual ~SingleElementElementIterator() {} - BSONElement element() const { return _element; } - BSONElement arrayOffset() const { return _arrayOffset; } - bool outerArray() const { return _outerArray; } + virtual bool more() { + return !_seen; + } + virtual Context next() { + _seen = true; + return _element; + } - private: - BSONElement _element; - BSONElement _arrayOffset; - bool _outerArray; - }; +private: + bool _seen; + ElementIterator::Context _element; +}; - virtual ~ElementIterator(); +class SimpleArrayElementIterator : public ElementIterator { +public: + SimpleArrayElementIterator(const BSONElement& theArray, bool returnArrayLast); - virtual bool more() = 0; - virtual Context next() = 0; + virtual bool more(); + virtual Context next(); - }; +private: + BSONElement _theArray; + bool _returnArrayLast; + BSONObjIterator _iterator; +}; - // --------------------------------------------------------------- +class BSONElementIterator : public ElementIterator { +public: + BSONElementIterator(); + BSONElementIterator(const ElementPath* path, const BSONObj& context); - class SingleElementElementIterator : public ElementIterator { - public: - explicit SingleElementElementIterator( BSONElement e ) - : _seen( false ) { - _element.reset( e, BSONElement(), false ); - } - virtual ~SingleElementElementIterator(){} + virtual ~BSONElementIterator(); - virtual bool more() { return !_seen; } - virtual Context next() { _seen = true; return _element; } + void reset(const ElementPath* path, const BSONObj& context); - private: - bool _seen; - ElementIterator::Context _element; - }; + bool more(); + Context next(); - class SimpleArrayElementIterator : public ElementIterator { - public: - SimpleArrayElementIterator( const BSONElement& theArray, bool returnArrayLast ); - - virtual bool more(); - virtual Context next(); - - private: - BSONElement _theArray; - bool _returnArrayLast; - BSONObjIterator _iterator; - }; +private: + /** + * Helper for more(). Recurs on _subCursor (which traverses the remainder of a path through + * subdocuments of an array). + */ + bool subCursorHasMore(); - class BSONElementIterator : public ElementIterator { - public: - BSONElementIterator(); - BSONElementIterator( const ElementPath* path, const BSONObj& context ); + const ElementPath* _path; + BSONObj _context; - virtual ~BSONElementIterator(); + enum State { BEGIN, IN_ARRAY, DONE } _state; + Context _next; - void reset( const ElementPath* path, const BSONObj& context ); + struct ArrayIterationState { + void reset(const FieldRef& ref, int start); + void startIterator(BSONElement theArray); bool more(); - Context next(); - - private: - /** - * Helper for more(). Recurs on _subCursor (which traverses the remainder of a path through - * subdocuments of an array). - */ - bool subCursorHasMore(); - - const ElementPath* _path; - BSONObj _context; - - enum State { BEGIN, IN_ARRAY, DONE } _state; - Context _next; - - struct ArrayIterationState { + BSONElement next(); - void reset( const FieldRef& ref, int start ); - void startIterator( BSONElement theArray ); - - bool more(); - BSONElement next(); - - bool isArrayOffsetMatch( StringData fieldName ) const; - bool nextEntireRest() const { return nextPieceOfPath.size() == restOfPath.size(); } - - std::string restOfPath; - bool hasMore; - StringData nextPieceOfPath; - bool nextPieceOfPathIsNumber; - - BSONElement _theArray; - BSONElement _current; - std::unique_ptr<BSONObjIterator> _iterator; - }; + bool isArrayOffsetMatch(StringData fieldName) const; + bool nextEntireRest() const { + return nextPieceOfPath.size() == restOfPath.size(); + } - ArrayIterationState _arrayIterationState; + std::string restOfPath; + bool hasMore; + StringData nextPieceOfPath; + bool nextPieceOfPathIsNumber; - std::unique_ptr<ElementIterator> _subCursor; - std::unique_ptr<ElementPath> _subCursorPath; + BSONElement _theArray; + BSONElement _current; + std::unique_ptr<BSONObjIterator> _iterator; }; + ArrayIterationState _arrayIterationState; + + std::unique_ptr<ElementIterator> _subCursor; + std::unique_ptr<ElementPath> _subCursorPath; +}; } diff --git a/src/mongo/db/matcher/path_internal.cpp b/src/mongo/db/matcher/path_internal.cpp index d8ccfe0ea9b..31b850e6834 100644 --- a/src/mongo/db/matcher/path_internal.cpp +++ b/src/mongo/db/matcher/path_internal.cpp @@ -32,31 +32,27 @@ namespace mongo { - bool isAllDigits( StringData str ) { - for ( unsigned i = 0; i < str.size(); i++ ) { - if ( !isdigit( str[i] ) ) - return false; - } - return true; +bool isAllDigits(StringData str) { + for (unsigned i = 0; i < str.size(); i++) { + if (!isdigit(str[i])) + return false; } + return true; +} - BSONElement getFieldDottedOrArray( const BSONObj& doc, - const FieldRef& path, - size_t* idxPath ) { - if ( path.numParts() == 0 ) - return doc.getField( "" ); - - BSONElement res; - - BSONObj curr = doc; - bool stop = false; - size_t partNum = 0; - while ( partNum < path.numParts() && !stop ) { +BSONElement getFieldDottedOrArray(const BSONObj& doc, const FieldRef& path, size_t* idxPath) { + if (path.numParts() == 0) + return doc.getField(""); - res = curr.getField( path.getPart( partNum ) ); + BSONElement res; - switch ( res.type() ) { + BSONObj curr = doc; + bool stop = false; + size_t partNum = 0; + while (partNum < path.numParts() && !stop) { + res = curr.getField(path.getPart(partNum)); + switch (res.type()) { case EOO: stop = true; break; @@ -71,17 +67,16 @@ namespace mongo { break; default: - if ( partNum+1 < path.numParts() ) { + if (partNum + 1 < path.numParts()) { res = BSONElement(); } stop = true; - - } } - - *idxPath = partNum; - return res; } + *idxPath = partNum; + return res; +} + } // namespace mongo diff --git a/src/mongo/db/matcher/path_internal.h b/src/mongo/db/matcher/path_internal.h index 82d94bc6f32..9d44d1877f1 100644 --- a/src/mongo/db/matcher/path_internal.h +++ b/src/mongo/db/matcher/path_internal.h @@ -37,12 +37,10 @@ namespace mongo { - bool isAllDigits( StringData str ); +bool isAllDigits(StringData str); - // XXX document me - // Replaces getFieldDottedOrArray without recursion nor std::string manipulation - BSONElement getFieldDottedOrArray( const BSONObj& doc, - const FieldRef& path, - size_t* idxPath ); +// XXX document me +// Replaces getFieldDottedOrArray without recursion nor std::string manipulation +BSONElement getFieldDottedOrArray(const BSONObj& doc, const FieldRef& path, size_t* idxPath); } // namespace mongo diff --git a/src/mongo/db/matcher/path_test.cpp b/src/mongo/db/matcher/path_test.cpp index 12091c4749c..73bbba4961f 100644 --- a/src/mongo/db/matcher/path_test.cpp +++ b/src/mongo/db/matcher/path_test.cpp @@ -37,451 +37,445 @@ namespace mongo { - using std::string; +using std::string; - TEST( Path, Root1 ) { - ElementPath p; - ASSERT( p.init( "a" ).isOK() ); +TEST(Path, Root1) { + ElementPath p; + ASSERT(p.init("a").isOK()); - BSONObj doc = BSON( "x" << 4 << "a" << 5 ); + BSONObj doc = BSON("x" << 4 << "a" << 5); - BSONElementIterator cursor( &p, doc ); - ASSERT( cursor.more() ); - ElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( (string)"a", e.element().fieldName() ); - ASSERT_EQUALS( 5, e.element().numberInt() ); - ASSERT( !cursor.more() ); - } + BSONElementIterator cursor(&p, doc); + ASSERT(cursor.more()); + ElementIterator::Context e = cursor.next(); + ASSERT_EQUALS((string) "a", e.element().fieldName()); + ASSERT_EQUALS(5, e.element().numberInt()); + ASSERT(!cursor.more()); +} - TEST( Path, RootArray1 ) { - ElementPath p; - ASSERT( p.init( "a" ).isOK() ); +TEST(Path, RootArray1) { + ElementPath p; + ASSERT(p.init("a").isOK()); - BSONObj doc = BSON( "x" << 4 << "a" << BSON_ARRAY( 5 << 6 ) ); + BSONObj doc = BSON("x" << 4 << "a" << BSON_ARRAY(5 << 6)); - BSONElementIterator cursor( &p, doc ); + BSONElementIterator cursor(&p, doc); - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( 5, e.element().numberInt() ); + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT_EQUALS(5, e.element().numberInt()); - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( 6, e.element().numberInt() ); + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(6, e.element().numberInt()); - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( Array, e.element().type() ); + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(Array, e.element().type()); - ASSERT( !cursor.more() ); - } + ASSERT(!cursor.more()); +} - TEST( Path, RootArray2 ) { - ElementPath p; - ASSERT( p.init( "a" ).isOK() ); - p.setTraverseLeafArray( false ); +TEST(Path, RootArray2) { + ElementPath p; + ASSERT(p.init("a").isOK()); + p.setTraverseLeafArray(false); - BSONObj doc = BSON( "x" << 4 << "a" << BSON_ARRAY( 5 << 6 ) ); + BSONObj doc = BSON("x" << 4 << "a" << BSON_ARRAY(5 << 6)); - BSONElementIterator cursor( &p, doc ); + BSONElementIterator cursor(&p, doc); - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT( e.element().type() == Array ); + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT(e.element().type() == Array); - ASSERT( !cursor.more() ); - } + ASSERT(!cursor.more()); +} - TEST( Path, Nested1 ) { - ElementPath p; - ASSERT( p.init( "a.b" ).isOK() ); - - BSONObj doc = BSON( "a" << BSON_ARRAY( BSON( "b" << 5 ) << - 3 << - BSONObj() << - BSON( "b" << BSON_ARRAY( 9 << 11 ) ) << - BSON( "b" << 7 ) ) ); - - BSONElementIterator cursor( &p, doc ); - - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( 5, e.element().numberInt() ); - ASSERT( !e.outerArray() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT( e.element().eoo() ); - ASSERT_EQUALS( (string)"2", e.arrayOffset().fieldName() ); - ASSERT( !e.outerArray() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( 9, e.element().numberInt() ); - ASSERT( !e.outerArray() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( 11, e.element().numberInt() ); - ASSERT( !e.outerArray() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( Array, e.element().type() ); - ASSERT_EQUALS( 2, e.element().Obj().nFields() ); - ASSERT( e.outerArray() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( 7, e.element().numberInt() ); - ASSERT( !e.outerArray() ); - - ASSERT( !cursor.more() ); - } +TEST(Path, Nested1) { + ElementPath p; + ASSERT(p.init("a.b").isOK()); + + BSONObj doc = + BSON("a" << BSON_ARRAY(BSON("b" << 5) << 3 << BSONObj() << BSON("b" << BSON_ARRAY(9 << 11)) + << BSON("b" << 7))); + + BSONElementIterator cursor(&p, doc); + + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT_EQUALS(5, e.element().numberInt()); + ASSERT(!e.outerArray()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT(e.element().eoo()); + ASSERT_EQUALS((string) "2", e.arrayOffset().fieldName()); + ASSERT(!e.outerArray()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(9, e.element().numberInt()); + ASSERT(!e.outerArray()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(11, e.element().numberInt()); + ASSERT(!e.outerArray()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(Array, e.element().type()); + ASSERT_EQUALS(2, e.element().Obj().nFields()); + ASSERT(e.outerArray()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(7, e.element().numberInt()); + ASSERT(!e.outerArray()); + + ASSERT(!cursor.more()); +} - TEST( Path, NestedPartialMatchScalar ) { - ElementPath p; - ASSERT( p.init( "a.b" ).isOK() ); +TEST(Path, NestedPartialMatchScalar) { + ElementPath p; + ASSERT(p.init("a.b").isOK()); - BSONObj doc = BSON( "a" << 4 ); + BSONObj doc = BSON("a" << 4); - BSONElementIterator cursor( &p, doc ); + BSONElementIterator cursor(&p, doc); - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT( e.element().eoo() ); - ASSERT( e.arrayOffset().eoo() ); - ASSERT( !e.outerArray() ); + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT(e.element().eoo()); + ASSERT(e.arrayOffset().eoo()); + ASSERT(!e.outerArray()); - ASSERT( !cursor.more() ); - } + ASSERT(!cursor.more()); +} - // When the path (partially or in its entirety) refers to an array, - // the iteration logic does not return an EOO. - // what we want ideally. - TEST( Path, NestedPartialMatchArray ) { - ElementPath p; - ASSERT( p.init( "a.b" ).isOK() ); +// When the path (partially or in its entirety) refers to an array, +// the iteration logic does not return an EOO. +// what we want ideally. +TEST(Path, NestedPartialMatchArray) { + ElementPath p; + ASSERT(p.init("a.b").isOK()); - BSONObj doc = BSON( "a" << BSON_ARRAY( 4 ) ); + BSONObj doc = BSON("a" << BSON_ARRAY(4)); - BSONElementIterator cursor( &p, doc ); + BSONElementIterator cursor(&p, doc); - ASSERT( !cursor.more() ); - } + ASSERT(!cursor.more()); +} - // Note that this describes existing behavior and not necessarily - TEST( Path, NestedEmptyArray ) { - ElementPath p; - ASSERT( p.init( "a.b" ).isOK() ); +// Note that this describes existing behavior and not necessarily +TEST(Path, NestedEmptyArray) { + ElementPath p; + ASSERT(p.init("a.b").isOK()); - BSONObj doc = BSON( "a" << BSON( "b" << BSONArray() ) ); + BSONObj doc = BSON("a" << BSON("b" << BSONArray())); - BSONElementIterator cursor( &p, doc ); + BSONElementIterator cursor(&p, doc); - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( Array, e.element().type() ); - ASSERT_EQUALS( 0, e.element().Obj().nFields() ); - ASSERT( e.outerArray() ); + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT_EQUALS(Array, e.element().type()); + ASSERT_EQUALS(0, e.element().Obj().nFields()); + ASSERT(e.outerArray()); - ASSERT( !cursor.more() ); - } + ASSERT(!cursor.more()); +} - TEST( Path, NestedNoLeaf1 ) { - ElementPath p; - ASSERT( p.init( "a.b" ).isOK() ); - p.setTraverseLeafArray( false ); - - BSONObj doc = BSON( "a" << BSON_ARRAY( BSON( "b" << 5 ) << - 3 << - BSONObj() << - BSON( "b" << BSON_ARRAY( 9 << 11 ) ) << - BSON( "b" << 7 ) ) ); - - BSONElementIterator cursor( &p, doc ); - - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( 5, e.element().numberInt() ); - ASSERT( !e.outerArray() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT( e.element().eoo() ); - ASSERT_EQUALS( (string)"2", e.arrayOffset().fieldName() ); - ASSERT( !e.outerArray() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( Array, e.element().type() ); - ASSERT_EQUALS( 2, e.element().Obj().nFields() ); - ASSERT( e.outerArray() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( 7, e.element().numberInt() ); - ASSERT( !e.outerArray() ); - - ASSERT( !cursor.more() ); - } +TEST(Path, NestedNoLeaf1) { + ElementPath p; + ASSERT(p.init("a.b").isOK()); + p.setTraverseLeafArray(false); + BSONObj doc = + BSON("a" << BSON_ARRAY(BSON("b" << 5) << 3 << BSONObj() << BSON("b" << BSON_ARRAY(9 << 11)) + << BSON("b" << 7))); - TEST( Path, ArrayIndex1 ) { - ElementPath p; - ASSERT( p.init( "a.1" ).isOK() ); + BSONElementIterator cursor(&p, doc); - BSONObj doc = BSON( "a" << BSON_ARRAY( 5 << 7 << 3 ) ); + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT_EQUALS(5, e.element().numberInt()); + ASSERT(!e.outerArray()); - BSONElementIterator cursor( &p, doc ); + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT(e.element().eoo()); + ASSERT_EQUALS((string) "2", e.arrayOffset().fieldName()); + ASSERT(!e.outerArray()); - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( 7, e.element().numberInt() ); + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(Array, e.element().type()); + ASSERT_EQUALS(2, e.element().Obj().nFields()); + ASSERT(e.outerArray()); - ASSERT( !cursor.more() ); - } + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(7, e.element().numberInt()); + ASSERT(!e.outerArray()); - TEST( Path, ArrayIndex2 ) { - ElementPath p; - ASSERT( p.init( "a.1" ).isOK() ); + ASSERT(!cursor.more()); +} - BSONObj doc = BSON( "a" << BSON_ARRAY( 5 << BSON_ARRAY( 2 << 4 ) << 3 ) ); - BSONElementIterator cursor( &p, doc ); +TEST(Path, ArrayIndex1) { + ElementPath p; + ASSERT(p.init("a.1").isOK()); - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( Array, e.element().type() ); + BSONObj doc = BSON("a" << BSON_ARRAY(5 << 7 << 3)); - ASSERT( !cursor.more() ); - } + BSONElementIterator cursor(&p, doc); - TEST( Path, ArrayIndex3 ) { - ElementPath p; - ASSERT( p.init( "a.1" ).isOK() ); + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT_EQUALS(7, e.element().numberInt()); - BSONObj doc = BSON( "a" << BSON_ARRAY( 5 << BSON( "1" << 4 ) << 3 ) ); + ASSERT(!cursor.more()); +} - BSONElementIterator cursor( &p, doc ); +TEST(Path, ArrayIndex2) { + ElementPath p; + ASSERT(p.init("a.1").isOK()); - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( 4, e.element().numberInt() ); - ASSERT( !e.outerArray() ); + BSONObj doc = BSON("a" << BSON_ARRAY(5 << BSON_ARRAY(2 << 4) << 3)); - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( BSON( "1" << 4 ), e.element().Obj() ); - ASSERT( e.outerArray() ); + BSONElementIterator cursor(&p, doc); - ASSERT( !cursor.more() ); - } + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT_EQUALS(Array, e.element().type()); - TEST( Path, ArrayIndexNested1 ) { - ElementPath p; - ASSERT( p.init( "a.1.b" ).isOK() ); + ASSERT(!cursor.more()); +} - BSONObj doc = BSON( "a" << BSON_ARRAY( 5 << BSON( "b" << 4 ) << 3 ) ); +TEST(Path, ArrayIndex3) { + ElementPath p; + ASSERT(p.init("a.1").isOK()); - BSONElementIterator cursor( &p, doc ); + BSONObj doc = BSON("a" << BSON_ARRAY(5 << BSON("1" << 4) << 3)); - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT( e.element().eoo() ); + BSONElementIterator cursor(&p, doc); - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( 4, e.element().numberInt() ); + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT_EQUALS(4, e.element().numberInt()); + ASSERT(!e.outerArray()); + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(BSON("1" << 4), e.element().Obj()); + ASSERT(e.outerArray()); - ASSERT( !cursor.more() ); - } + ASSERT(!cursor.more()); +} - TEST( Path, ArrayIndexNested2 ) { - ElementPath p; - ASSERT( p.init( "a.1.b" ).isOK() ); +TEST(Path, ArrayIndexNested1) { + ElementPath p; + ASSERT(p.init("a.1.b").isOK()); - BSONObj doc = BSON( "a" << BSON_ARRAY( 5 << BSON_ARRAY( BSON( "b" << 4 ) ) << 3 ) ); + BSONObj doc = BSON("a" << BSON_ARRAY(5 << BSON("b" << 4) << 3)); - BSONElementIterator cursor( &p, doc ); + BSONElementIterator cursor(&p, doc); - ASSERT( cursor.more() ); - BSONElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( 4, e.element().numberInt() ); + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT(e.element().eoo()); + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(4, e.element().numberInt()); - ASSERT( !cursor.more() ); - } - // SERVER-15899: test iteration using a path that generates no elements, but traverses a long - // array containing subdocuments with nested arrays. - TEST( Path, NonMatchingLongArrayOfSubdocumentsWithNestedArrays ) { - ElementPath p; - ASSERT( p.init( "a.b.x" ).isOK() ); + ASSERT(!cursor.more()); +} - // Build the document {a: [{b: []}, {b: []}, {b: []}, ...]}. - BSONObj subdoc = BSON( "b" << BSONArray() ); - BSONArrayBuilder builder; - for ( int i = 0; i < 100 * 1000; ++i ) { - builder.append( subdoc ); - } - BSONObj doc = BSON( "a" << builder.arr() ); +TEST(Path, ArrayIndexNested2) { + ElementPath p; + ASSERT(p.init("a.1.b").isOK()); - BSONElementIterator cursor( &p, doc ); + BSONObj doc = BSON("a" << BSON_ARRAY(5 << BSON_ARRAY(BSON("b" << 4)) << 3)); - // The path "a.b.x" matches no elements. - ASSERT( !cursor.more() ); - } + BSONElementIterator cursor(&p, doc); + + ASSERT(cursor.more()); + BSONElementIterator::Context e = cursor.next(); + ASSERT_EQUALS(4, e.element().numberInt()); - // When multiple arrays are traversed implicitly in the same path, - // ElementIterator::Context::arrayOffset() should always refer to the current offset of the - // outermost array that is implicitly traversed. - TEST( Path, NestedArrayImplicitTraversal ) { - ElementPath p; - ASSERT( p.init( "a.b" ).isOK() ); - BSONObj doc = fromjson("{a: [{b: [2, 3]}, {b: [4, 5]}]}"); - BSONElementIterator cursor( &p, doc ); - - ASSERT( cursor.more() ); - ElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( NumberInt, e.element().type() ); - ASSERT_EQUALS( 2, e.element().numberInt() ); - ASSERT_EQUALS( "0", e.arrayOffset().fieldNameStringData() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( NumberInt, e.element().type() ); - ASSERT_EQUALS( 3, e.element().numberInt() ); - ASSERT_EQUALS( "0", e.arrayOffset().fieldNameStringData() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( Array, e.element().type() ); - ASSERT_EQUALS( BSON( "0" << 2 << "1" << 3 ), e.element().Obj() ); - ASSERT_EQUALS( "0", e.arrayOffset().fieldNameStringData() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( NumberInt, e.element().type() ); - ASSERT_EQUALS( 4, e.element().numberInt() ); - ASSERT_EQUALS( "1", e.arrayOffset().fieldNameStringData() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( NumberInt, e.element().type() ); - ASSERT_EQUALS( 5, e.element().numberInt() ); - ASSERT_EQUALS( "1", e.arrayOffset().fieldNameStringData() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( Array, e.element().type() ); - ASSERT_EQUALS( BSON( "0" << 4 << "1" << 5 ), e.element().Obj() ); - ASSERT_EQUALS( "1", e.arrayOffset().fieldNameStringData() ); - - ASSERT( !cursor.more() ); - } - // SERVER-14886: when an array is being traversed explictly at the same time that a nested array - // is being traversed implicitly, ElementIterator::Context::arrayOffset() should return the - // current offset of the array being implicitly traversed. - TEST( Path, ArrayOffsetWithImplicitAndExplicitTraversal ) { - ElementPath p; - ASSERT( p.init( "a.0.b" ).isOK() ); - BSONObj doc = fromjson("{a: [{b: [2, 3]}, {b: [4, 5]}]}"); - BSONElementIterator cursor( &p, doc ); - - ASSERT( cursor.more() ); - ElementIterator::Context e = cursor.next(); - ASSERT_EQUALS( EOO, e.element().type() ); - ASSERT_EQUALS( "0", e.arrayOffset().fieldNameStringData() ); // First elt of outer array. - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( NumberInt, e.element().type() ); - ASSERT_EQUALS( 2, e.element().numberInt() ); - ASSERT_EQUALS( "0", e.arrayOffset().fieldNameStringData() ); // First elt of inner array. - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( NumberInt, e.element().type() ); - ASSERT_EQUALS( 3, e.element().numberInt() ); - ASSERT_EQUALS( "1", e.arrayOffset().fieldNameStringData() ); // Second elt of inner array. - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( Array, e.element().type() ); - ASSERT_EQUALS( BSON( "0" << 2 << "1" << 3 ), e.element().Obj() ); - ASSERT( e.arrayOffset().eoo() ); - - ASSERT( cursor.more() ); - e = cursor.next(); - ASSERT_EQUALS( EOO, e.element().type() ); - ASSERT_EQUALS( "1", e.arrayOffset().fieldNameStringData() ); // Second elt of outer array. - - ASSERT( !cursor.more() ); + ASSERT(!cursor.more()); +} + +// SERVER-15899: test iteration using a path that generates no elements, but traverses a long +// array containing subdocuments with nested arrays. +TEST(Path, NonMatchingLongArrayOfSubdocumentsWithNestedArrays) { + ElementPath p; + ASSERT(p.init("a.b.x").isOK()); + + // Build the document {a: [{b: []}, {b: []}, {b: []}, ...]}. + BSONObj subdoc = BSON("b" << BSONArray()); + BSONArrayBuilder builder; + for (int i = 0; i < 100 * 1000; ++i) { + builder.append(subdoc); } + BSONObj doc = BSON("a" << builder.arr()); + + BSONElementIterator cursor(&p, doc); - TEST( SimpleArrayElementIterator, SimpleNoArrayLast1 ) { - BSONObj obj = BSON( "a" << BSON_ARRAY( 5 << BSON( "x" << 6 ) << BSON_ARRAY( 7 << 9 ) << 11 ) ); - SimpleArrayElementIterator i( obj["a"], false ); + // The path "a.b.x" matches no elements. + ASSERT(!cursor.more()); +} - ASSERT( i.more() ); - ElementIterator::Context e = i.next(); - ASSERT_EQUALS( 5, e.element().numberInt() ); +// When multiple arrays are traversed implicitly in the same path, +// ElementIterator::Context::arrayOffset() should always refer to the current offset of the +// outermost array that is implicitly traversed. +TEST(Path, NestedArrayImplicitTraversal) { + ElementPath p; + ASSERT(p.init("a.b").isOK()); + BSONObj doc = fromjson("{a: [{b: [2, 3]}, {b: [4, 5]}]}"); + BSONElementIterator cursor(&p, doc); + + ASSERT(cursor.more()); + ElementIterator::Context e = cursor.next(); + ASSERT_EQUALS(NumberInt, e.element().type()); + ASSERT_EQUALS(2, e.element().numberInt()); + ASSERT_EQUALS("0", e.arrayOffset().fieldNameStringData()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(NumberInt, e.element().type()); + ASSERT_EQUALS(3, e.element().numberInt()); + ASSERT_EQUALS("0", e.arrayOffset().fieldNameStringData()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(Array, e.element().type()); + ASSERT_EQUALS(BSON("0" << 2 << "1" << 3), e.element().Obj()); + ASSERT_EQUALS("0", e.arrayOffset().fieldNameStringData()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(NumberInt, e.element().type()); + ASSERT_EQUALS(4, e.element().numberInt()); + ASSERT_EQUALS("1", e.arrayOffset().fieldNameStringData()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(NumberInt, e.element().type()); + ASSERT_EQUALS(5, e.element().numberInt()); + ASSERT_EQUALS("1", e.arrayOffset().fieldNameStringData()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(Array, e.element().type()); + ASSERT_EQUALS(BSON("0" << 4 << "1" << 5), e.element().Obj()); + ASSERT_EQUALS("1", e.arrayOffset().fieldNameStringData()); + + ASSERT(!cursor.more()); +} - ASSERT( i.more() ); - e = i.next(); - ASSERT_EQUALS( 6, e.element().Obj()["x"].numberInt() ); +// SERVER-14886: when an array is being traversed explictly at the same time that a nested array +// is being traversed implicitly, ElementIterator::Context::arrayOffset() should return the +// current offset of the array being implicitly traversed. +TEST(Path, ArrayOffsetWithImplicitAndExplicitTraversal) { + ElementPath p; + ASSERT(p.init("a.0.b").isOK()); + BSONObj doc = fromjson("{a: [{b: [2, 3]}, {b: [4, 5]}]}"); + BSONElementIterator cursor(&p, doc); + + ASSERT(cursor.more()); + ElementIterator::Context e = cursor.next(); + ASSERT_EQUALS(EOO, e.element().type()); + ASSERT_EQUALS("0", e.arrayOffset().fieldNameStringData()); // First elt of outer array. + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(NumberInt, e.element().type()); + ASSERT_EQUALS(2, e.element().numberInt()); + ASSERT_EQUALS("0", e.arrayOffset().fieldNameStringData()); // First elt of inner array. + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(NumberInt, e.element().type()); + ASSERT_EQUALS(3, e.element().numberInt()); + ASSERT_EQUALS("1", e.arrayOffset().fieldNameStringData()); // Second elt of inner array. + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(Array, e.element().type()); + ASSERT_EQUALS(BSON("0" << 2 << "1" << 3), e.element().Obj()); + ASSERT(e.arrayOffset().eoo()); + + ASSERT(cursor.more()); + e = cursor.next(); + ASSERT_EQUALS(EOO, e.element().type()); + ASSERT_EQUALS("1", e.arrayOffset().fieldNameStringData()); // Second elt of outer array. + + ASSERT(!cursor.more()); +} - ASSERT( i.more() ); - e = i.next(); - ASSERT_EQUALS( 7, e.element().Obj().firstElement().numberInt() ); +TEST(SimpleArrayElementIterator, SimpleNoArrayLast1) { + BSONObj obj = BSON("a" << BSON_ARRAY(5 << BSON("x" << 6) << BSON_ARRAY(7 << 9) << 11)); + SimpleArrayElementIterator i(obj["a"], false); - ASSERT( i.more() ); - e = i.next(); - ASSERT_EQUALS( 11, e.element().numberInt() ); + ASSERT(i.more()); + ElementIterator::Context e = i.next(); + ASSERT_EQUALS(5, e.element().numberInt()); - ASSERT( !i.more() ); - } + ASSERT(i.more()); + e = i.next(); + ASSERT_EQUALS(6, e.element().Obj()["x"].numberInt()); + + ASSERT(i.more()); + e = i.next(); + ASSERT_EQUALS(7, e.element().Obj().firstElement().numberInt()); - TEST( SimpleArrayElementIterator, SimpleArrayLast1 ) { - BSONObj obj = BSON( "a" << BSON_ARRAY( 5 << BSON( "x" << 6 ) << BSON_ARRAY( 7 << 9 ) << 11 ) ); - SimpleArrayElementIterator i( obj["a"], true ); + ASSERT(i.more()); + e = i.next(); + ASSERT_EQUALS(11, e.element().numberInt()); - ASSERT( i.more() ); - ElementIterator::Context e = i.next(); - ASSERT_EQUALS( 5, e.element().numberInt() ); + ASSERT(!i.more()); +} - ASSERT( i.more() ); - e = i.next(); - ASSERT_EQUALS( 6, e.element().Obj()["x"].numberInt() ); +TEST(SimpleArrayElementIterator, SimpleArrayLast1) { + BSONObj obj = BSON("a" << BSON_ARRAY(5 << BSON("x" << 6) << BSON_ARRAY(7 << 9) << 11)); + SimpleArrayElementIterator i(obj["a"], true); - ASSERT( i.more() ); - e = i.next(); - ASSERT_EQUALS( 7, e.element().Obj().firstElement().numberInt() ); + ASSERT(i.more()); + ElementIterator::Context e = i.next(); + ASSERT_EQUALS(5, e.element().numberInt()); - ASSERT( i.more() ); - e = i.next(); - ASSERT_EQUALS( 11, e.element().numberInt() ); + ASSERT(i.more()); + e = i.next(); + ASSERT_EQUALS(6, e.element().Obj()["x"].numberInt()); - ASSERT( i.more() ); - e = i.next(); - ASSERT_EQUALS( Array, e.element().type() ); + ASSERT(i.more()); + e = i.next(); + ASSERT_EQUALS(7, e.element().Obj().firstElement().numberInt()); - ASSERT( !i.more() ); - } + ASSERT(i.more()); + e = i.next(); + ASSERT_EQUALS(11, e.element().numberInt()); - TEST( SingleElementElementIterator, Simple1 ) { - BSONObj obj = BSON( "x" << 3 << "y" << 5 ); - SingleElementElementIterator i( obj["y"] ); + ASSERT(i.more()); + e = i.next(); + ASSERT_EQUALS(Array, e.element().type()); - ASSERT( i.more() ); - ElementIterator::Context e = i.next(); - ASSERT_EQUALS( 5, e.element().numberInt() ); + ASSERT(!i.more()); +} - ASSERT( !i.more() ); +TEST(SingleElementElementIterator, Simple1) { + BSONObj obj = BSON("x" << 3 << "y" << 5); + SingleElementElementIterator i(obj["y"]); - } + ASSERT(i.more()); + ElementIterator::Context e = i.next(); + ASSERT_EQUALS(5, e.element().numberInt()); + ASSERT(!i.more()); +} } |