summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher/expression_parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/matcher/expression_parser.cpp')
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp484
1 files changed, 484 insertions, 0 deletions
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
new file mode 100644
index 00000000000..ccebeef0aea
--- /dev/null
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -0,0 +1,484 @@
+// expression_parser.cpp
+
+/**
+ * Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "mongo/db/matcher/expression_parser.h"
+
+#include "mongo/bson/bsonmisc.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/bsonobjiterator.h"
+#include "mongo/db/matcher/expression_array.h"
+#include "mongo/db/matcher/expression_leaf.h"
+#include "mongo/db/matcher/expression_tree.h"
+#include "mongo/util/log.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+
+ StatusWithExpression ExpressionParser::_parseComparison( const char* name,
+ ComparisonExpression::Type cmp,
+ const BSONElement& e ) {
+ std::auto_ptr<ComparisonExpression> temp( new ComparisonExpression() );
+
+ Status s = temp->init( name, cmp, e );
+ if ( !s.isOK() )
+ return StatusWithExpression(s);
+
+ return StatusWithExpression( temp.release() );
+ }
+
+ StatusWithExpression ExpressionParser::_parseSubField( const char* name,
+ const BSONElement& e ) {
+
+ // TODO: these should move to getGtLtOp, or its replacement
+
+ if ( mongoutils::str::equals( "$eq", e.fieldName() ) )
+ return _parseComparison( name, ComparisonExpression::EQ, e );
+
+ if ( mongoutils::str::equals( "$not", e.fieldName() ) ) {
+ return _parseNot( name, e );
+ }
+
+
+ int x = e.getGtLtOp(-1);
+ switch ( x ) {
+ case -1:
+ return StatusWithExpression( ErrorCodes::BadValue,
+ mongoutils::str::stream() << "unknown operator: "
+ << e.fieldName() );
+ case BSONObj::LT:
+ return _parseComparison( name, ComparisonExpression::LT, e );
+ case BSONObj::LTE:
+ return _parseComparison( name, ComparisonExpression::LTE, e );
+ case BSONObj::GT:
+ return _parseComparison( name, ComparisonExpression::GT, e );
+ case BSONObj::GTE:
+ return _parseComparison( name, ComparisonExpression::GTE, e );
+ case BSONObj::NE:
+ return _parseComparison( name, ComparisonExpression::NE, e );
+ case BSONObj::Equality:
+ return _parseComparison( name, ComparisonExpression::EQ, e );
+
+ case BSONObj::opIN: {
+ if ( e.type() != Array )
+ return StatusWithExpression( ErrorCodes::BadValue, "$in needs an array" );
+ std::auto_ptr<InExpression> temp( new InExpression() );
+ temp->init( name );
+ Status s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ return StatusWithExpression( temp.release() );
+ }
+
+ case BSONObj::NIN: {
+ if ( e.type() != Array )
+ return StatusWithExpression( ErrorCodes::BadValue, "$nin needs an array" );
+ std::auto_ptr<NinExpression> temp( new NinExpression() );
+ temp->init( name );
+ Status s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ return StatusWithExpression( temp.release() );
+ }
+
+ case BSONObj::opSIZE: {
+ int size = 0;
+ if ( e.type() == String ) {
+ // matching old odd semantics
+ size = 0;
+ }
+ else if ( e.type() == NumberInt || e.type() == NumberLong ) {
+ size = e.numberInt();
+ }
+ else if ( e.type() == NumberDouble ) {
+ if ( e.numberInt() == e.numberDouble() ) {
+ size = e.numberInt();
+ }
+ else {
+ // old semantcs require exact numeric match
+ // so [1,2] != 1 or 2
+ size = -1;
+ }
+ }
+ else {
+ return StatusWithExpression( ErrorCodes::BadValue, "$size needs a number" );
+ }
+
+ std::auto_ptr<SizeExpression> temp( new SizeExpression() );
+ Status s = temp->init( name, size );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ return StatusWithExpression( temp.release() );
+ }
+
+ case BSONObj::opEXISTS: {
+ if ( e.eoo() )
+ return StatusWithExpression( ErrorCodes::BadValue, "$exists can't be eoo" );
+ std::auto_ptr<ExistsExpression> temp( new ExistsExpression() );
+ Status s = temp->init( name, e.trueValue() );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ return StatusWithExpression( temp.release() );
+ }
+
+ case BSONObj::opTYPE: {
+ if ( !e.isNumber() )
+ return StatusWithExpression( ErrorCodes::BadValue, "$type has to be a number" );
+ int type = e.numberInt();
+ if ( e.type() != NumberInt && type != e.number() )
+ type = -1;
+ std::auto_ptr<TypeExpression> temp( new TypeExpression() );
+ Status s = temp->init( name, type );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ return StatusWithExpression( temp.release() );
+ }
+
+
+ case BSONObj::opMOD:
+ return _parseMOD( name, e );
+
+ case BSONObj::opOPTIONS:
+ return StatusWithExpression( ErrorCodes::BadValue, "$options has to be after a $regex" );
+
+ case BSONObj::opELEM_MATCH:
+ return _parseElemMatch( name, e );
+
+ case BSONObj::opALL:
+ return _parseAll( name, e );
+
+ default:
+ return StatusWithExpression( ErrorCodes::BadValue, "not done" );
+ }
+
+ }
+
+ StatusWithExpression ExpressionParser::parse( const BSONObj& obj ) {
+
+ std::auto_ptr<AndExpression> root( new AndExpression() );
+
+ 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 StatusWithExpression( ErrorCodes::BadValue,
+ "$or needs an array" );
+ std::auto_ptr<OrExpression> temp( new OrExpression() );
+ Status s = _parseTreeList( e.Obj(), temp.get() );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ root->add( temp.release() );
+ }
+ else if ( mongoutils::str::equals( "and", rest ) ) {
+ if ( e.type() != Array )
+ return StatusWithExpression( ErrorCodes::BadValue,
+ "and needs an array" );
+ std::auto_ptr<AndExpression> temp( new AndExpression() );
+ Status s = _parseTreeList( e.Obj(), temp.get() );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ root->add( temp.release() );
+ }
+ else if ( mongoutils::str::equals( "nor", rest ) ) {
+ if ( e.type() != Array )
+ return StatusWithExpression( ErrorCodes::BadValue,
+ "and needs an array" );
+ std::auto_ptr<NorExpression> temp( new NorExpression() );
+ Status s = _parseTreeList( e.Obj(), temp.get() );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ root->add( temp.release() );
+ }
+ else {
+ return StatusWithExpression( ErrorCodes::BadValue,
+ mongoutils::str::stream()
+ << "unkown operator: "
+ << e.fieldName() );
+ }
+
+ continue;
+ }
+
+ if ( e.type() == Object && e.Obj().firstElement().fieldName()[0] == '$' ) {
+ Status s = _parseSub( e.fieldName(), e.Obj(), root.get() );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ continue;
+ }
+
+ if ( e.type() == RegEx ) {
+ StatusWithExpression result = _parseRegexElement( e.fieldName(), e );
+ if ( !result.isOK() )
+ return result;
+ root->add( result.getValue() );
+ continue;
+ }
+
+ std::auto_ptr<ComparisonExpression> eq( new ComparisonExpression() );
+ Status s = eq->init( e.fieldName(), ComparisonExpression::EQ, e );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+
+ root->add( eq.release() );
+ }
+
+ return StatusWithExpression( root.release() );
+ }
+
+ Status ExpressionParser::_parseSub( const char* name,
+ const BSONObj& sub,
+ AndExpression* root ) {
+
+ bool first = true;
+
+ BSONObjIterator j( sub );
+ while ( j.more() ) {
+ BSONElement deep = j.next();
+
+ int op = deep.getGtLtOp();
+ if ( op == BSONObj::opREGEX ) {
+ if ( !first )
+ return Status( ErrorCodes::BadValue, "$regex has to be first" );
+
+ StatusWithExpression s = _parseRegexDocument( name, sub );
+ if ( !s.isOK() )
+ return s.getStatus();
+ root->add( s.getValue() );
+ return Status::OK();
+ }
+
+ StatusWithExpression s = _parseSubField( name, deep );
+ if ( !s.isOK() )
+ return s.getStatus();
+ root->add( s.getValue() );
+ }
+ return Status::OK();
+ }
+
+
+
+ StatusWithExpression ExpressionParser::_parseMOD( const char* name,
+ const BSONElement& e ) {
+
+ if ( e.type() != Array )
+ return StatusWithExpression( ErrorCodes::BadValue, "malformed mod, needs to be an array" );
+
+ BSONObjIterator i( e.Obj() );
+
+ if ( !i.more() )
+ return StatusWithExpression( ErrorCodes::BadValue, "malformed mod, not enough elements" );
+ BSONElement d = i.next();
+ if ( !d.isNumber() )
+ return StatusWithExpression( ErrorCodes::BadValue, "malformed mod, divisor not a number" );
+
+ if ( !i.more() )
+ return StatusWithExpression( ErrorCodes::BadValue, "malformed mod, not enough elements" );
+ BSONElement r = i.next();
+ if ( !d.isNumber() )
+ return StatusWithExpression( ErrorCodes::BadValue, "malformed mod, remainder not a number" );
+
+ if ( i.more() )
+ return StatusWithExpression( ErrorCodes::BadValue, "malformed mod, too many elements" );
+
+ std::auto_ptr<ModExpression> temp( new ModExpression() );
+ Status s = temp->init( name, d.numberInt(), r.numberInt() );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ return StatusWithExpression( temp.release() );
+ }
+
+ StatusWithExpression ExpressionParser::_parseRegexElement( const char* name,
+ const BSONElement& e ) {
+ if ( e.type() != RegEx )
+ return StatusWithExpression( ErrorCodes::BadValue, "not a regex" );
+
+ std::auto_ptr<RegexExpression> temp( new RegexExpression() );
+ Status s = temp->init( name, e.regex(), e.regexFlags() );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ return StatusWithExpression( temp.release() );
+ }
+
+ StatusWithExpression ExpressionParser::_parseRegexDocument( const char* name,
+ const BSONObj& doc ) {
+ string regex;
+ string regexOptions;
+
+ BSONObjIterator i( doc );
+ while ( i.more() ) {
+ BSONElement e = i.next();
+ switch ( e.getGtLtOp() ) {
+ case BSONObj::opREGEX:
+ if ( e.type() != String )
+ return StatusWithExpression( ErrorCodes::BadValue,
+ "$regex has to be a string" );
+ regex = e.String();
+ break;
+ case BSONObj::opOPTIONS:
+ if ( e.type() != String )
+ return StatusWithExpression( ErrorCodes::BadValue,
+ "$options has to be a string" );
+ regexOptions = e.String();
+ break;
+ default:
+ return StatusWithExpression( ErrorCodes::BadValue,
+ mongoutils::str::stream()
+ << "bad $regex doc option: " << e.fieldName() );
+ }
+
+ }
+
+ std::auto_ptr<RegexExpression> temp( new RegexExpression() );
+ Status s = temp->init( name, regex, regexOptions );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+ return StatusWithExpression( temp.release() );
+
+ }
+
+ Status ExpressionParser::_parseArrayFilterEntries( ArrayFilterEntries* entries,
+ const BSONObj& theArray ) {
+
+
+ BSONObjIterator i( theArray );
+ while ( i.more() ) {
+ BSONElement e = i.next();
+
+ if ( e.type() == RegEx ) {
+ std::auto_ptr<RegexExpression> r( new RegexExpression() );
+ 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();
+
+ }
+
+ StatusWithExpression ExpressionParser::_parseElemMatch( const char* name,
+ const BSONElement& e ) {
+ if ( e.type() != Object )
+ return StatusWithExpression( ErrorCodes::BadValue, "$elemMatch needs an Object" );
+
+ BSONObj obj = e.Obj();
+ if ( obj.firstElement().fieldName()[0] == '$' ) {
+ // value case
+
+ AndExpression theAnd;
+ Status s = _parseSub( "", obj, &theAnd );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+
+ std::auto_ptr<ElemMatchValueExpression> temp( new ElemMatchValueExpression() );
+ s = temp->init( name );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+
+ for ( size_t i = 0; i < theAnd.size(); i++ ) {
+ temp->add( theAnd.get( i ) );
+ }
+ theAnd.clearAndRelease();
+
+ return StatusWithExpression( temp.release() );
+ }
+
+ // object case
+
+ StatusWithExpression sub = parse( obj );
+ if ( !sub.isOK() )
+ return sub;
+
+ std::auto_ptr<ElemMatchObjectExpression> temp( new ElemMatchObjectExpression() );
+ Status status = temp->init( name, sub.getValue() );
+ if ( !status.isOK() )
+ return StatusWithExpression( status );
+
+ return StatusWithExpression( temp.release() );
+ }
+
+ StatusWithExpression ExpressionParser::_parseAll( const char* name,
+ const BSONElement& e ) {
+ if ( e.type() != Array )
+ return StatusWithExpression( ErrorCodes::BadValue, "$all needs an array" );
+
+ BSONObj arr = e.Obj();
+ if ( arr.firstElement().type() == Object &&
+ mongoutils::str::equals( "$elemMatch",
+ arr.firstElement().Obj().firstElement().fieldName() ) ) {
+ // $all : [ { $elemMatch : {} } ... ]
+
+ std::auto_ptr<AllElemMatchOp> temp( new AllElemMatchOp() );
+ Status s = temp->init( name );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+
+ BSONObjIterator i( arr );
+ while ( i.more() ) {
+ BSONElement hopefullyElemMatchElemennt = i.next();
+
+ if ( hopefullyElemMatchElemennt.type() != Object ) {
+ // $all : [ { $elemMatch : ... }, 5 ]
+ return StatusWithExpression( ErrorCodes::BadValue,
+ "$all/$elemMatch has to be consistent" );
+ }
+
+ BSONObj hopefullyElemMatchObj = hopefullyElemMatchElemennt.Obj();
+ if ( !mongoutils::str::equals( "$elemMatch",
+ hopefullyElemMatchObj.firstElement().fieldName() ) ) {
+ // $all : [ { $elemMatch : ... }, { x : 5 } ]
+ return StatusWithExpression( ErrorCodes::BadValue,
+ "$all/$elemMatch has to be consistent" );
+ }
+
+ StatusWithExpression inner = _parseElemMatch( "", hopefullyElemMatchObj.firstElement() );
+ if ( !inner.isOK() )
+ return inner;
+ temp->add( static_cast<ArrayMatchingExpression*>( inner.getValue() ) );
+ }
+
+ return StatusWithExpression( temp.release() );
+ }
+
+ std::auto_ptr<AllExpression> temp( new AllExpression() );
+ Status s = temp->init( name );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+
+ s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), arr );
+ if ( !s.isOK() )
+ return StatusWithExpression( s );
+
+ return StatusWithExpression( temp.release() );
+ }
+
+
+}