StatusWithMatchExpression MatchExpressionParser::_parseAll( const char* name, const BSONElement& e ) { if ( e.type() != Array ) return StatusWithMatchExpression( 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 StatusWithMatchExpression( s ); BSONObjIterator i( arr ); while ( i.more() ) { BSONElement hopefullyElemMatchElemennt = i.next(); if ( hopefullyElemMatchElemennt.type() != Object ) { // $all : [ { $elemMatch : ... }, 5 ] return StatusWithMatchExpression( 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 StatusWithMatchExpression( ErrorCodes::BadValue, "$all/$elemMatch has to be consistent" ); } StatusWithMatchExpression inner = _parseElemMatch( "", hopefullyElemMatchObj.firstElement() ); if ( !inner.isOK() ) return inner; temp->add( static_cast<ArrayMatchingMatchExpression*>( inner.getValue() ) ); } return StatusWithMatchExpression( temp.release() ); } std::auto_ptr<AllMatchExpression> temp( new AllMatchExpression() ); Status s = temp->init( name ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), arr ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); }
StatusWithMatchExpression MatchExpressionParser::_parseAll( const char* name, const BSONElement& e ) { if ( e.type() != Array ) return StatusWithMatchExpression( 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 StatusWithMatchExpression( s ); BSONObjIterator i( arr ); while ( i.more() ) { BSONElement hopefullyElemMatchElement = i.next(); if ( hopefullyElemMatchElement.type() != Object ) { // $all : [ { $elemMatch : ... }, 5 ] return StatusWithMatchExpression( ErrorCodes::BadValue, "$all/$elemMatch has to be consistent" ); } 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" ); } StatusWithMatchExpression inner = _parseElemMatch( "", hopefullyElemMatchObj.firstElement() ); if ( !inner.isOK() ) return inner; temp->add( static_cast<ArrayMatchingMatchExpression*>( inner.getValue() ) ); } return StatusWithMatchExpression( temp.release() ); } std::auto_ptr<AndMatchExpression> myAnd( new AndMatchExpression() ); BSONObjIterator i( arr ); while ( i.more() ) { BSONElement e = i.next(); if ( e.type() == RegEx ) { std::auto_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::auto_ptr<EqualityMatchExpression> x( new EqualityMatchExpression() ); Status s = x->init( name, e ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); myAnd->add( x.release() ); } } if ( myAnd->numChildren() == 0 ) { return StatusWithMatchExpression( new FalseMatchExpression() ); } return StatusWithMatchExpression( myAnd.release() ); }
StatusWithMatchExpression MatchExpressionParser::_parseSubField( const BSONObj& context, const AndMatchExpression* andSoFar, 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, new EqualityMatchExpression(), e ); if ( mongoutils::str::equals( "$not", e.fieldName() ) ) { return _parseNot( name, e ); } int x = e.getGtLtOp(-1); switch ( x ) { case -1: return StatusWithMatchExpression( ErrorCodes::BadValue, mongoutils::str::stream() << "unknown operator: " << e.fieldName() ); case BSONObj::LT: return _parseComparison( name, new LTMatchExpression(), e ); case BSONObj::LTE: return _parseComparison( name, new LTEMatchExpression(), e ); case BSONObj::GT: return _parseComparison( name, new GTMatchExpression(), e ); case BSONObj::GTE: return _parseComparison( name, new GTEMatchExpression(), e ); case BSONObj::NE: { StatusWithMatchExpression s = _parseComparison( name, new EqualityMatchExpression(), e ); if ( !s.isOK() ) return s; std::auto_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 ); case BSONObj::opIN: { if ( e.type() != Array ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$in needs an array" ); std::auto_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::auto_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::auto_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 ) { // matching old odd semantics size = 0; } 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 { 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 StatusWithMatchExpression( ErrorCodes::BadValue, "$size needs a number" ); } std::auto_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::auto_ptr<ExistsMatchExpression> temp( new ExistsMatchExpression() ); Status s = temp->init( name ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); if ( e.trueValue() ) return StatusWithMatchExpression( temp.release() ); std::auto_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" ); int type = e.numberInt(); if ( e.type() != NumberInt && type != e.number() ) type = -1; std::auto_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 ); 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() ) { BSONElement temp = i.next(); if ( temp.getGtLtOp( -1 ) == BSONObj::opREGEX ) return StatusWithMatchExpression( NULL ); } return StatusWithMatchExpression( ErrorCodes::BadValue, "$options needs a $regex" ); } case BSONObj::opREGEX: { return _parseRegexDocument( name, context ); } case BSONObj::opELEM_MATCH: return _parseElemMatch( name, e ); case BSONObj::opALL: return _parseAll( name, e ); case BSONObj::opWITHIN: case BSONObj::opGEO_INTERSECTS: return expressionParserGeoCallback( name, x, context ); } return StatusWithMatchExpression( ErrorCodes::BadValue, mongoutils::str::stream() << "not handled: " << e.fieldName() ); }
StatusWithMatchExpression MatchExpressionParser::_parseSubField( const BSONObj& context, const char* name, const BSONElement& e, int position, bool* stop ) { *stop = false; // 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( "$not", e.fieldName() ) ) { return _parseNot( name, e ); } int x = e.getGtLtOp(-1); switch ( x ) { case -1: return StatusWithMatchExpression( ErrorCodes::BadValue, mongoutils::str::stream() << "unknown operator: " << e.fieldName() ); case BSONObj::LT: return _parseComparison( name, new LTMatchExpression(), e ); case BSONObj::LTE: return _parseComparison( name, new LTEMatchExpression(), e ); case BSONObj::GT: return _parseComparison( name, new GTMatchExpression(), e ); case BSONObj::GTE: return _parseComparison( name, new GTEMatchExpression(), e ); case BSONObj::NE: return _parseComparison( name, new NEMatchExpression(), e ); case BSONObj::Equality: return _parseComparison( name, new EqualityMatchExpression(), e ); case BSONObj::opIN: { if ( e.type() != Array ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$in needs an array" ); std::auto_ptr<InMatchExpression> temp( new InMatchExpression() ); temp->init( name ); Status 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::auto_ptr<NinMatchExpression> temp( new NinMatchExpression() ); temp->init( name ); Status s = _parseArrayFilterEntries( temp->getArrayFilterEntries(), e.Obj() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( 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 StatusWithMatchExpression( ErrorCodes::BadValue, "$size needs a number" ); } std::auto_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::auto_ptr<ExistsMatchExpression> temp( new ExistsMatchExpression() ); Status s = temp->init( name, e.trueValue() ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); } case BSONObj::opTYPE: { if ( !e.isNumber() ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$type has to be a number" ); int type = e.numberInt(); if ( e.type() != NumberInt && type != e.number() ) type = -1; std::auto_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 ); case BSONObj::opOPTIONS: return StatusWithMatchExpression( ErrorCodes::BadValue, "$options has to be after a $regex" ); case BSONObj::opREGEX: { if ( position != 0 ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$regex has to be first" ); *stop = true; return _parseRegexDocument( name, context ); } case BSONObj::opELEM_MATCH: return _parseElemMatch( name, e ); case BSONObj::opALL: return _parseAll( name, e ); case BSONObj::opWITHIN: return expressionParserGeoCallback( name, context ); default: return StatusWithMatchExpression( ErrorCodes::BadValue, "not done" ); } }