summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/matcher')
-rw-r--r--src/mongo/db/matcher/expression.cpp62
-rw-r--r--src/mongo/db/matcher/expression.h417
-rw-r--r--src/mongo/db/matcher/expression_algo.cpp291
-rw-r--r--src/mongo/db/matcher/expression_algo.h50
-rw-r--r--src/mongo/db/matcher/expression_algo_test.cpp1173
-rw-r--r--src/mongo/db/matcher/expression_array.cpp322
-rw-r--r--src/mongo/db/matcher/expression_array.h179
-rw-r--r--src/mongo/db/matcher/expression_array_test.cpp864
-rw-r--r--src/mongo/db/matcher/expression_geo.cpp666
-rw-r--r--src/mongo/db/matcher/expression_geo.h255
-rw-r--r--src/mongo/db/matcher/expression_geo_test.cpp235
-rw-r--r--src/mongo/db/matcher/expression_leaf.cpp822
-rw-r--r--src/mongo/db/matcher/expression_leaf.h593
-rw-r--r--src/mongo/db/matcher/expression_leaf_test.cpp3271
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp1149
-rw-r--r--src/mongo/db/matcher/expression_parser.h286
-rw-r--r--src/mongo/db/matcher/expression_parser_array_test.cpp1292
-rw-r--r--src/mongo/db/matcher/expression_parser_geo.cpp81
-rw-r--r--src/mongo/db/matcher/expression_parser_geo_test.cpp61
-rw-r--r--src/mongo/db/matcher/expression_parser_leaf_test.cpp1141
-rw-r--r--src/mongo/db/matcher/expression_parser_test.cpp148
-rw-r--r--src/mongo/db/matcher/expression_parser_text.cpp92
-rw-r--r--src/mongo/db/matcher/expression_parser_text_test.cpp82
-rw-r--r--src/mongo/db/matcher/expression_parser_tree.cpp123
-rw-r--r--src/mongo/db/matcher/expression_parser_tree_test.cpp266
-rw-r--r--src/mongo/db/matcher/expression_test.cpp111
-rw-r--r--src/mongo/db/matcher/expression_text.cpp111
-rw-r--r--src/mongo/db/matcher/expression_text.h45
-rw-r--r--src/mongo/db/matcher/expression_tree.cpp231
-rw-r--r--src/mongo/db/matcher/expression_tree.h271
-rw-r--r--src/mongo/db/matcher/expression_tree_test.cpp1062
-rw-r--r--src/mongo/db/matcher/expression_where.cpp249
-rw-r--r--src/mongo/db/matcher/expression_where_noop.cpp150
-rw-r--r--src/mongo/db/matcher/match_details.cpp54
-rw-r--r--src/mongo/db/matcher/match_details.h54
-rw-r--r--src/mongo/db/matcher/matchable.cpp11
-rw-r--r--src/mongo/db/matcher/matchable.h116
-rw-r--r--src/mongo/db/matcher/matcher.cpp34
-rw-r--r--src/mongo/db/matcher/matcher.h36
-rw-r--r--src/mongo/db/matcher/path.cpp431
-rw-r--r--src/mongo/db/matcher/path.h216
-rw-r--r--src/mongo/db/matcher/path_internal.cpp47
-rw-r--r--src/mongo/db/matcher/path_internal.h10
-rw-r--r--src/mongo/db/matcher/path_test.cpp716
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(&regexBob);
- 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(&regexBob);
+ 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());
+}
}