// queryutil.cpp /** * 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 . */ #include "stdafx.h" #include "btree.h" #include "matcher.h" #include "pdfile.h" #include "queryoptimizer.h" namespace mongo { FieldRange::FieldRange( const BSONElement &e, bool optimize ) { if ( !e.eoo() && e.type() != RegEx && e.getGtLtOp() == BSONObj::opIN ) { set< BSONElement, element_lt > vals; BSONObjIterator i( e.embeddedObject() ); while( i.more() ) vals.insert( i.next() ); for( set< BSONElement, element_lt >::const_iterator i = vals.begin(); i != vals.end(); ++i ) intervals_.push_back( FieldInterval(*i) ); return; } if ( e.type() == Array && e.getGtLtOp() == BSONObj::Equality ){ intervals_.push_back( FieldInterval(e) ); const BSONElement& temp = e.embeddedObject().firstElement(); if ( ! temp.eoo() ){ if ( temp < e ) intervals_.insert( intervals_.begin() , temp ); else intervals_.push_back( FieldInterval(temp) ); } return; } intervals_.push_back( FieldInterval() ); FieldInterval &initial = intervals_[ 0 ]; BSONElement &lower = initial.lower_.bound_; bool &lowerInclusive = initial.lower_.inclusive_; BSONElement &upper = initial.upper_.bound_; bool &upperInclusive = initial.upper_.inclusive_; lower = minKey.firstElement(); lowerInclusive = true; upper = maxKey.firstElement(); upperInclusive = true; if ( e.eoo() ) return; if ( e.type() == RegEx ) { const string r = e.simpleRegex(); if ( r.size() ) { lower = addObj( BSON( "" << r ) ).firstElement(); upper = addObj( BSON( "" << simpleRegexEnd( r ) ) ).firstElement(); upperInclusive = false; } return; } switch( e.getGtLtOp() ) { case BSONObj::Equality: lower = upper = e; break; case BSONObj::LT: upperInclusive = false; case BSONObj::LTE: upper = e; break; case BSONObj::GT: lowerInclusive = false; case BSONObj::GTE: lower = e; break; case BSONObj::opALL: { massert( "$all requires array", e.type() == Array ); BSONObjIterator i( e.embeddedObject() ); if ( i.more() ) lower = upper = i.next(); break; } case BSONObj::opMOD: { break; } case BSONObj::opTYPE: { BSONType t = (BSONType)e.numberInt(); { BSONObjBuilder b; b.appendMinForType( "" , t ); lower = addObj( b.obj() ).firstElement(); } { BSONObjBuilder b; b.appendMaxForType( "" , t ); upper = addObj( b.obj() ).firstElement(); } break; } default: break; } if ( optimize ){ if ( lower.type() != MinKey && upper.type() == MaxKey && lower.isSimpleType() ){ // TODO: get rid of isSimpleType BSONObjBuilder b; b.appendMaxForType( lower.fieldName() , lower.type() ); upper = addObj( b.obj() ).firstElement(); } else if ( lower.type() == MinKey && upper.type() != MaxKey && upper.isSimpleType() ){ // TODO: get rid of isSimpleType BSONObjBuilder b; b.appendMinForType( upper.fieldName() , upper.type() ); lower = addObj( b.obj() ).firstElement(); } } } // as called, these functions find the max/min of a bound in the // opposite direction, so inclusive bounds are considered less // superlative FieldBound maxFieldBound( const FieldBound &a, const FieldBound &b ) { int cmp = a.bound_.woCompare( b.bound_, false ); if ( ( cmp == 0 && !b.inclusive_ ) || cmp < 0 ) return b; return a; } FieldBound minFieldBound( const FieldBound &a, const FieldBound &b ) { int cmp = a.bound_.woCompare( b.bound_, false ); if ( ( cmp == 0 && !b.inclusive_ ) || cmp > 0 ) return b; return a; } bool fieldIntervalOverlap( const FieldInterval &one, const FieldInterval &two, FieldInterval &result ) { result.lower_ = maxFieldBound( one.lower_, two.lower_ ); result.upper_ = minFieldBound( one.upper_, two.upper_ ); return result.valid(); } // NOTE Not yet tested for complex $or bounds, just for simple bounds generated by $in const FieldRange &FieldRange::operator&=( const FieldRange &other ) { vector< FieldInterval > newIntervals; vector< FieldInterval >::const_iterator i = intervals_.begin(); vector< FieldInterval >::const_iterator j = other.intervals_.begin(); while( i != intervals_.end() && j != other.intervals_.end() ) { FieldInterval overlap; if ( fieldIntervalOverlap( *i, *j, overlap ) ) newIntervals.push_back( overlap ); if ( i->upper_ == minFieldBound( i->upper_, j->upper_ ) ) ++i; else ++j; } intervals_ = newIntervals; for( vector< BSONObj >::const_iterator i = other.objData_.begin(); i != other.objData_.end(); ++i ) objData_.push_back( *i ); return *this; } string FieldRange::simpleRegexEnd( string regex ) { ++regex[ regex.length() - 1 ]; return regex; } BSONObj FieldRange::addObj( const BSONObj &o ) { objData_.push_back( o ); return o; } FieldRangeSet::FieldRangeSet( const char *ns, const BSONObj &query , bool optimize ) : ns_( ns ), query_( query.getOwned() ) { BSONObjIterator i( query_ ); while( i.moreWithEOO() ) { BSONElement e = i.next(); if ( e.eoo() ) break; if ( strcmp( e.fieldName(), "$where" ) == 0 ) continue; if ( getGtLtOp( e ) == BSONObj::Equality ) { ranges_[ e.fieldName() ] &= FieldRange( e , optimize ); } else { BSONObjIterator i( e.embeddedObject() ); while( i.moreWithEOO() ) { BSONElement f = i.next(); if ( f.eoo() ) break; ranges_[ e.fieldName() ] &= FieldRange( f , optimize ); } } } } FieldRange *FieldRangeSet::trivialRange_ = 0; FieldRange &FieldRangeSet::trivialRange() { if ( trivialRange_ == 0 ) trivialRange_ = new FieldRange(); return *trivialRange_; } BSONObj FieldRangeSet::simplifiedQuery( const BSONObj &_fields ) const { BSONObj fields = _fields; if ( fields.isEmpty() ) { BSONObjBuilder b; for( map< string, FieldRange >::const_iterator i = ranges_.begin(); i != ranges_.end(); ++i ) { b.append( i->first.c_str(), 1 ); } fields = b.obj(); } BSONObjBuilder b; BSONObjIterator i( fields ); while( i.moreWithEOO() ) { BSONElement e = i.next(); if ( e.eoo() ) break; const char *name = e.fieldName(); const FieldRange &range = ranges_[ name ]; assert( !range.empty() ); if ( range.equality() ) b.appendAs( range.min(), name ); else if ( range.nontrivial() ) { BSONObjBuilder c; if ( range.min().type() != MinKey ) c.appendAs( range.min(), range.minInclusive() ? "$gte" : "$gt" ); if ( range.max().type() != MaxKey ) c.appendAs( range.max(), range.maxInclusive() ? "$lte" : "$lt" ); b.append( name, c.done() ); } } return b.obj(); } QueryPattern FieldRangeSet::pattern( const BSONObj &sort ) const { QueryPattern qp; for( map< string, FieldRange >::const_iterator i = ranges_.begin(); i != ranges_.end(); ++i ) { assert( !i->second.empty() ); if ( i->second.equality() ) { qp.fieldTypes_[ i->first ] = QueryPattern::Equality; } else if ( i->second.nontrivial() ) { bool upper = i->second.max().type() != MaxKey; bool lower = i->second.min().type() != MinKey; if ( upper && lower ) qp.fieldTypes_[ i->first ] = QueryPattern::UpperAndLowerBound; else if ( upper ) qp.fieldTypes_[ i->first ] = QueryPattern::UpperBound; else if ( lower ) qp.fieldTypes_[ i->first ] = QueryPattern::LowerBound; } } qp.setSort( sort ); return qp; } BoundList FieldRangeSet::indexBounds( const BSONObj &keyPattern, int direction ) const { BSONObjBuilder equalityBuilder; typedef vector< pair< shared_ptr< BSONObjBuilder >, shared_ptr< BSONObjBuilder > > > BoundBuilders; BoundBuilders builders; BSONObjIterator i( keyPattern ); while( i.more() ) { BSONElement e = i.next(); const FieldRange &fr = range( e.fieldName() ); int number = (int) e.number(); // returns 0.0 if not numeric bool forward = ( ( number >= 0 ? 1 : -1 ) * ( direction >= 0 ? 1 : -1 ) > 0 ); if ( builders.empty() ) { if ( fr.equality() ) { equalityBuilder.appendAs( fr.min(), "" ); } else { BSONObj equalityObj = equalityBuilder.done(); const vector< FieldInterval > &intervals = fr.intervals(); if ( forward ) { for( vector< FieldInterval >::const_iterator j = intervals.begin(); j != intervals.end(); ++j ) { builders.push_back( make_pair( shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ), shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ) ) ); builders.back().first->appendElements( equalityObj ); builders.back().second->appendElements( equalityObj ); builders.back().first->appendAs( j->lower_.bound_, "" ); builders.back().second->appendAs( j->upper_.bound_, "" ); } } else { for( vector< FieldInterval >::const_reverse_iterator j = intervals.rbegin(); j != intervals.rend(); ++j ) { builders.push_back( make_pair( shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ), shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ) ) ); builders.back().first->appendElements( equalityObj ); builders.back().second->appendElements( equalityObj ); builders.back().first->appendAs( j->upper_.bound_, "" ); builders.back().second->appendAs( j->lower_.bound_, "" ); } } } } else { for( BoundBuilders::const_iterator j = builders.begin(); j != builders.end(); ++j ) { j->first->appendAs( forward ? fr.min() : fr.max(), "" ); j->second->appendAs( forward ? fr.max() : fr.min(), "" ); } } } if ( builders.empty() ) { BSONObj equalityObj = equalityBuilder.done(); assert( !equalityObj.isEmpty() ); builders.push_back( make_pair( shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ), shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ) ) ); builders.back().first->appendElements( equalityObj ); builders.back().second->appendElements( equalityObj ); } BoundList ret; for( BoundBuilders::const_iterator i = builders.begin(); i != builders.end(); ++i ) ret.push_back( make_pair( i->first->obj(), i->second->obj() ) ); return ret; } void FieldMatcher::add( const BSONObj& o ){ BSONObjIterator i( o ); while ( i.more() ){ string s = i.next().fieldName(); if ( s.find( "." ) == string::npos ){ fields.insert( pair( s , "" ) ); } else { string sub = s.substr( 0 , s.find( "." ) ); fields.insert(pair( sub , s.substr( sub.size() + 1 ) ) ); } } } int FieldMatcher::size() const { return fields.size(); } bool FieldMatcher::matches( const string& s ) const { return fields.find( s ) != fields.end(); } BSONObj FieldMatcher::getSpec() const{ BSONObjBuilder b; for ( multimap::const_iterator i=fields.begin(); i!=fields.end(); i++ ) { string s = i->first; if ( i->second.size() > 0 ) s += "." + i->second; b.append( s.c_str() , 1 ); } return b.obj(); } void FieldMatcher::extractDotted( const string& path , const BSONObj& o , BSONObjBuilder& b ) const { string::size_type i = path.find( "." ); if ( i == string::npos ){ const BSONElement & e = o.getField( path.c_str() ); if ( e.eoo() ) return; b.append(e); return; } string left = path.substr( 0 , i ); BSONElement e = o[left]; if ( e.type() != Object ) return; BSONObj sub = e.embeddedObject(); if ( sub.isEmpty() ) return; BSONObjBuilder sub_b(32); extractDotted( path.substr( i + 1 ) , sub , sub_b ); b.append( left.c_str() , sub_b.obj() ); } void FieldMatcher::append( BSONObjBuilder& b , const BSONElement& e ) const { pair::const_iterator,multimap::const_iterator> p = fields.equal_range( e.fieldName() ); BSONObjBuilder sub_b(32); for( multimap::const_iterator i = p.first; i != p.second; ++i ) { string next = i->second; if ( e.eoo() ){ } else if ( next.size() == 0 || next == "." || e.type() != Object ){ b.append( e ); return; } else { extractDotted( next , e.embeddedObject() , sub_b ); } } b.append( e.fieldName() , sub_b.obj() ); } } // namespace mongo