summaryrefslogtreecommitdiff
path: root/db/queryutil.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'db/queryutil.cpp')
-rw-r--r--db/queryutil.cpp594
1 files changed, 594 insertions, 0 deletions
diff --git a/db/queryutil.cpp b/db/queryutil.cpp
new file mode 100644
index 00000000000..d8854be3c77
--- /dev/null
+++ b/db/queryutil.cpp
@@ -0,0 +1,594 @@
+// queryutil.cpp
+
+/* Copyright 2009 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stdafx.h"
+
+#include "btree.h"
+#include "matcher.h"
+#include "pdfile.h"
+#include "queryoptimizer.h"
+#include "../util/unittest.h"
+
+namespace mongo {
+ namespace {
+ /** returns a string that when used as a matcher, would match a super set of regex()
+ returns "" for complex regular expressions
+ used to optimize queries in some simple regex cases that start with '^'
+ */
+ inline string simpleRegexHelper(const char* regex, const char* flags){
+ string r = "";
+
+ bool extended = false;
+ while (*flags){
+ switch (*(flags++)){
+ case 'm': // multiline
+ continue;
+ case 'x': // extended
+ extended = true;
+ break;
+ default:
+ return r; // cant use index
+ }
+ }
+
+ if ( *(regex++) != '^' )
+ return r;
+
+ stringstream ss;
+
+ while(*regex){
+ char c = *(regex++);
+ if ( c == '*' || c == '?' ){
+ // These are the only two symbols that make the last char optional
+ r = ss.str();
+ r = r.substr( 0 , r.size() - 1 );
+ return r; //breaking here fails with /^a?/
+ } else if (c == '\\'){
+ // slash followed by non-alphanumeric represents the following char
+ c = *(regex++);
+ if ((c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '0') ||
+ (c == '\0'))
+ {
+ r = ss.str();
+ break;
+ } else {
+ ss << c;
+ }
+ } else if (strchr("^$.[|()+{", c)){
+ // list of "metacharacters" from man pcrepattern
+ r = ss.str();
+ break;
+ } else if (extended && c == '#'){
+ // comment
+ r = ss.str();
+ break;
+ } else if (extended && isspace(c)){
+ continue;
+ } else {
+ // self-matching char
+ ss << c;
+ }
+ }
+
+ if ( r.size() == 0 && *regex == 0 )
+ r = ss.str();
+
+ return r;
+ }
+ inline string simpleRegex(const BSONElement& e){
+ switch(e.type()){
+ case RegEx:
+ return simpleRegexHelper(e.regex(), e.regexFlags());
+ case Object:{
+ BSONObj o = e.embeddedObject();
+ return simpleRegexHelper(o["$regex"].valuestrsafe(), o["$options"].valuestrsafe());
+ }
+ default: assert(false); return ""; //return squashes compiler warning
+ }
+ }
+ }
+
+ 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
+ || (e.type() == Object && !e.embeddedObject()["$regex"].eoo())
+ )
+ {
+ const string r = simpleRegex(e);
+ 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( 10370 , "$all requires array", e.type() == Array );
+ BSONObjIterator i( e.embeddedObject() );
+ if ( i.more() )
+ lower = upper = i.next();
+ break;
+ }
+ case BSONObj::opMOD: {
+ {
+ BSONObjBuilder b;
+ b.appendMinForType( "" , NumberDouble );
+ lower = addObj( b.obj() ).firstElement();
+ }
+ {
+ BSONObjBuilder b;
+ b.appendMaxForType( "" , NumberDouble );
+ upper = addObj( b.obj() ).firstElement();
+ }
+ 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;
+ }
+ case BSONObj::opELEM_MATCH: {
+ log() << "warning: shouldn't get here?" << endl;
+ 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.more() ) {
+ BSONElement e = i.next();
+ // e could be x:1 or x:{$gt:1}
+
+ if ( strcmp( e.fieldName(), "$where" ) == 0 )
+ continue;
+
+ int op = getGtLtOp( e );
+
+ if ( op == BSONObj::Equality || op == BSONObj::opREGEX || op == BSONObj::opOPTIONS ) {
+ ranges_[ e.fieldName() ] &= FieldRange( e , optimize );
+ }
+ else if ( op == BSONObj::opELEM_MATCH ){
+ BSONObjIterator i( e.embeddedObjectUserCheck().firstElement().embeddedObjectUserCheck() );
+ while ( i.more() ){
+ BSONElement f = i.next();
+ StringBuilder buf(32);
+ buf << e.fieldName() << "." << f.fieldName();
+ string fullname = buf.str();
+
+ int op2 = getGtLtOp( f );
+ if ( op2 == BSONObj::Equality ){
+ ranges_[ fullname ] &= FieldRange( f , optimize );
+ }
+ else {
+ BSONObjIterator j( f.embeddedObject() );
+ while ( j.more() ){
+ ranges_[ fullname ] &= FieldRange( j.next() , optimize );
+ }
+ }
+ }
+ }
+ else {
+ BSONObjIterator i( e.embeddedObject() );
+ while( i.more() ) {
+ BSONElement f = i.next();
+ 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.more() ) {
+ BSONElement e = i.next();
+ 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;
+ }
+
+ ///////////////////
+ // FieldMatcher //
+ ///////////////////
+
+ void FieldMatcher::add( const BSONObj& o ){
+ massert( 10371 , "can only add to FieldMatcher once", source_.isEmpty());
+ source_ = o;
+
+ BSONObjIterator i( o );
+ int true_false = -1;
+ while ( i.more() ){
+ BSONElement e = i.next();
+ add (e.fieldName(), e.trueValue());
+
+ // validate input
+ if (true_false == -1){
+ true_false = e.trueValue();
+ include_ = !e.trueValue();
+ }else{
+ if((bool) true_false != e.trueValue())
+ errmsg = "You cannot currently mix including and excluding fields. Contact us if this is an issue.";
+ }
+ }
+ }
+
+ void FieldMatcher::add(const string& field, bool include){
+ if (field.empty()){ // this is the field the user referred to
+ include_ = include;
+ } else {
+ const size_t dot = field.find('.');
+ const string subfield = field.substr(0,dot);
+ const string rest = (dot == string::npos ? "" : field.substr(dot+1,string::npos));
+
+ boost::shared_ptr<FieldMatcher>& fm = fields_[subfield];
+ if (!fm)
+ fm.reset(new FieldMatcher(!include));
+
+ fm->add(rest, include);
+ }
+ }
+
+ BSONObj FieldMatcher::getSpec() const{
+ return source_;
+ }
+
+ //b will be the value part of an array-typed BSONElement
+ void FieldMatcher::appendArray( BSONObjBuilder& b , const BSONObj& a ) const {
+ int i=0;
+ BSONObjIterator it(a);
+ while (it.more()){
+ BSONElement e = it.next();
+
+ switch(e.type()){
+ case Array:{
+ BSONObjBuilder subb;
+ appendArray(subb , e.embeddedObject());
+ b.appendArray(b.numStr(i++).c_str(), subb.obj());
+ break;
+ }
+ case Object:{
+ BSONObjBuilder subb;
+ BSONObjIterator jt(e.embeddedObject());
+ while (jt.more()){
+ append(subb , jt.next());
+ }
+ b.append(b.numStr(i++), subb.obj());
+ break;
+ }
+ default:
+ if (include_)
+ b.appendAs(e, b.numStr(i++).c_str());
+ }
+
+
+ }
+ }
+
+ void FieldMatcher::append( BSONObjBuilder& b , const BSONElement& e ) const {
+ FieldMap::const_iterator field = fields_.find( e.fieldName() );
+
+ if (field == fields_.end()){
+ if (include_)
+ b.append(e);
+ } else {
+ FieldMatcher& subfm = *field->second;
+
+ if (subfm.fields_.empty() || !(e.type()==Object || e.type()==Array) ){
+ if (subfm.include_)
+ b.append(e);
+ } else if (e.type() == Object){
+ BSONObjBuilder subb;
+ BSONObjIterator it(e.embeddedObject());
+ while (it.more()){
+ subfm.append(subb, it.next());
+ }
+ b.append(e.fieldName(), subb.obj());
+
+ } else { //Array
+ BSONObjBuilder subb;
+ subfm.appendArray(subb, e.embeddedObject());
+ b.appendArray(e.fieldName(), subb.obj());
+ }
+ }
+ }
+
+ struct SimpleRegexUnitTest : UnitTest {
+ void run(){
+ {
+ BSONObjBuilder b;
+ b.appendRegex("r", "^foo");
+ BSONObj o = b.done();
+ assert( simpleRegex(o.firstElement()) == "foo" );
+ }
+ {
+ BSONObjBuilder b;
+ b.appendRegex("r", "^f?oo");
+ BSONObj o = b.done();
+ assert( simpleRegex(o.firstElement()) == "" );
+ }
+ {
+ BSONObjBuilder b;
+ b.appendRegex("r", "^fz?oo");
+ BSONObj o = b.done();
+ assert( simpleRegex(o.firstElement()) == "f" );
+ }
+ {
+ BSONObjBuilder b;
+ b.appendRegex("r", "^f", "");
+ BSONObj o = b.done();
+ assert( simpleRegex(o.firstElement()) == "f" );
+ }
+ {
+ BSONObjBuilder b;
+ b.appendRegex("r", "^f", "m");
+ BSONObj o = b.done();
+ assert( simpleRegex(o.firstElement()) == "f" );
+ }
+ {
+ BSONObjBuilder b;
+ b.appendRegex("r", "^f", "mi");
+ BSONObj o = b.done();
+ assert( simpleRegex(o.firstElement()) == "" );
+ }
+ {
+ BSONObjBuilder b;
+ b.appendRegex("r", "^f \t\vo\n\ro \\ \\# #comment", "mx");
+ BSONObj o = b.done();
+ assert( simpleRegex(o.firstElement()) == "foo #" );
+ }
+ }
+ } simple_regex_unittest;
+} // namespace mongo