// matcher.h /* Matcher is our boolean expression evaluator for "where" clauses */ /** * Copyright (C) 2008 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #pragma once #include "jsobj.h" #include "pcrecpp.h" namespace mongo { class Cursor; class CoveredIndexMatcher; class Matcher; class FieldRangeVector; class RegexMatcher { public: const char *_fieldName; const char *_regex; const char *_flags; string _prefix; shared_ptr< pcrecpp::RE > _re; bool _isNot; RegexMatcher() : _isNot() {} }; struct element_lt { bool operator()(const BSONElement& l, const BSONElement& r) const { int x = (int) l.canonicalType() - (int) r.canonicalType(); if ( x < 0 ) return true; else if ( x > 0 ) return false; return compareElementValues(l,r) < 0; } }; class ElementMatcher { public: ElementMatcher() { } ElementMatcher( BSONElement e , int op, bool isNot ); ElementMatcher( BSONElement e , int op , const BSONObj& array, bool isNot ); ~ElementMatcher() { } bool negativeCompareOp() const { return _compareOp == BSONObj::NE || _compareOp == BSONObj::NIN; } int inverseOfNegativeCompareOp() const; bool negativeCompareOpContainsNull() const; BSONElement _toMatch; int _compareOp; bool _isNot; shared_ptr< set > _myset; shared_ptr< vector > _myregex; // these are for specific operators int _mod; int _modm; BSONType _type; shared_ptr _subMatcher; bool _subMatcherOnPrimitives ; vector< shared_ptr > _allMatchers; }; class Where; // used for $where javascript eval class DiskLoc; /** Reports information about a match request. */ class MatchDetails { public: MatchDetails(); void resetOutput(); string toString() const; /** Request that an elemMatchKey be recorded. */ void requestElemMatchKey() { _elemMatchKeyRequested = true; } bool needRecord() const { return _elemMatchKeyRequested; } bool hasLoadedRecord() const { return _loadedRecord; } bool hasElemMatchKey() const { return _elemMatchKeyFound; } string elemMatchKey() const { verify( 16108, hasElemMatchKey() ); return _elemMatchKey; } void setLoadedRecord( bool loadedRecord ) { _loadedRecord = loadedRecord; } void setElemMatchKey( const string &elemMatchKey ) { if ( _elemMatchKeyRequested ) { _elemMatchKeyFound = true; _elemMatchKey = elemMatchKey; } } private: bool _loadedRecord; bool _elemMatchKeyRequested; bool _elemMatchKeyFound; string _elemMatchKey; }; /* Match BSON objects against a query pattern. e.g. db.foo.find( { a : 3 } ); { a : 3 } is the pattern object. See wiki documentation for full info. GT/LT: { a : { $gt : 3 } } Not equal: { a : { $ne : 3 } } TODO: we should rewrite the matcher to be more an AST style. */ class Matcher : boost::noncopyable { int matchesDotted( const char *fieldName, const BSONElement& toMatch, const BSONObj& obj, int compareOp, const ElementMatcher& bm, bool isArr , MatchDetails * details ) const; /** * Perform a NE or NIN match by returning the inverse of the opposite matching operation. * Missing values are considered matches unless the match must not equal null. */ int inverseMatch( const char *fieldName, const BSONElement &toMatch, const BSONObj &obj, const ElementMatcher&bm, MatchDetails * details ) const; public: static int opDirection(int op) { return op <= BSONObj::LTE ? -1 : 1; } Matcher(const BSONObj &pattern, bool nested=false); ~Matcher(); bool matches(const BSONObj& j, MatchDetails * details = 0 ) const; #ifdef MONGO_LATER_SERVER_4644 class FieldSink { public: virtual ~FieldSink() {}; virtual void referenceField(const string &fieldPath) = 0; }; /** Visit all of the fields that are referenced by this Matcher (and any descendants). This can be used to gather a list of all the references made by this matcher. The implementation of this parallels that of matches() above. @param pSink a FieldSink that the caller will use to gather or process the references */ void visitReferences(FieldSink *pSink) const; #endif /* MONGO_LATER_SERVER_4644 */ bool atomic() const { return _atomic; } string toString() const { return _jsobj.toString(); } void addOrDedupConstraint( const shared_ptr< FieldRangeVector > &frv ) { _orDedupConstraints.push_back( frv ); } void popOrClause() { _orMatchers.pop_front(); } /** * @return true if this key matcher will return the same true/false * value as the provided doc matcher. */ bool keyMatch( const Matcher &docMatcher ) const; bool singleSimpleCriterion() const { return false; // TODO SERVER-958 // // TODO Really check, especially if all basics are ok. // // $all, etc // // _orConstraints? // return ( ( basics.size() + nRegex ) < 2 ) && !where && !_orMatchers.size() && !_norMatchers.size(); } const BSONObj *getQuery() const { return &_jsobj; }; private: /** * Generate a matcher for the provided index key format using the * provided full doc matcher. */ Matcher( const Matcher &docMatcher, const BSONObj &constrainIndexKey ); void addBasic(const BSONElement &e, int c, bool isNot) { // TODO May want to selectively ignore these element types based on op type. if ( e.type() == MinKey || e.type() == MaxKey ) return; _basics.push_back( ElementMatcher( e , c, isNot ) ); } void addRegex(const char *fieldName, const char *regex, const char *flags, bool isNot = false); bool addOp( const BSONElement &e, const BSONElement &fe, bool isNot, const char *& regex, const char *&flags ); int valuesMatch(const BSONElement& l, const BSONElement& r, int op, const ElementMatcher& bm) const; bool parseClause( const BSONElement &e ); void parseExtractedClause( const BSONElement &e, list< shared_ptr< Matcher > > &matchers ); void parseWhere( const BSONElement &e ); void parseMatchExpressionElement( const BSONElement &e, bool nested ); Where *_where; // set if query uses $where BSONObj _jsobj; // the query pattern. e.g., { name: "joe" } BSONObj _constrainIndexKey; vector _basics; bool _haveSize; bool _all; bool _hasArray; bool _haveNeg; /* $atomic - if true, a multi document operation (some removes, updates) should be done atomically. in that case, we do not yield - i.e. we stay locked the whole time. http://www.mongodb.org/display/DOCS/Removing[ */ bool _atomic; vector _regexs; // so we delete the mem when we're done: vector< shared_ptr< BSONObjBuilder > > _builders; list< shared_ptr< Matcher > > _andMatchers; list< shared_ptr< Matcher > > _orMatchers; list< shared_ptr< Matcher > > _norMatchers; vector< shared_ptr< FieldRangeVector > > _orDedupConstraints; friend class CoveredIndexMatcher; }; // If match succeeds on index key, then attempt to match full document. class CoveredIndexMatcher : boost::noncopyable { public: CoveredIndexMatcher(const BSONObj &pattern, const BSONObj &indexKeyPattern); bool matchesWithSingleKeyIndex(const BSONObj &key, const DiskLoc &recLoc , MatchDetails * details = 0 ) { return matches( key, recLoc, details, true ); } /** * This is the preferred method for matching against a cursor, as it * can handle both multi and single key cursors. */ bool matchesCurrent( Cursor * cursor , MatchDetails * details = 0 ); bool needRecord() { return _needRecord; } Matcher& docMatcher() { return *_docMatcher; } // once this is called, shouldn't use this matcher for matching any more void advanceOrClause( const shared_ptr< FieldRangeVector > &frv ) { _docMatcher->addOrDedupConstraint( frv ); // TODO this is not yet optimal. Since we could skip an entire // or clause (if a match is impossible) between calls to advanceOrClause() // we may not pop all the clauses we can. _docMatcher->popOrClause(); } CoveredIndexMatcher *nextClauseMatcher( const BSONObj &indexKeyPattern ) { return new CoveredIndexMatcher( _docMatcher, indexKeyPattern ); } string toString() const; private: bool matches(const BSONObj &key, const DiskLoc &recLoc , MatchDetails * details = 0 , bool keyUsable = true ); CoveredIndexMatcher(const shared_ptr< Matcher > &docMatcher, const BSONObj &indexKeyPattern); void init(); shared_ptr< Matcher > _docMatcher; Matcher _keyMatcher; bool _needRecord; // if the key itself isn't good enough to determine a positive match }; } // namespace mongo