int getGtLtOp(const BSONElement& e) { if ( e.type() != Object ) return BSONObj::Equality; BSONElement fe = e.embeddedObject().firstElement(); return fe.getGtLtOp(); }
bool GeoQuery::parseNewQuery(const BSONObj &obj) { // pointA = { "type" : "Point", "coordinates": [ 40, 5 ] } // t.find({ "geo" : { "$intersect" : { "$geometry" : pointA} } }) // t.find({ "geo" : { "$within" : { "$geometry" : polygon } } }) // where field.name is "geo" BSONElement e = obj.firstElement(); if (!e.isABSONObj()) { return false; } BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp()); if (BSONObj::opGEO_INTERSECTS == matchType) { predicate = GeoQuery::INTERSECT; } else if (BSONObj::opWITHIN == matchType) { predicate = GeoQuery::WITHIN; } else { return false; } bool hasGeometry = false; BSONObjIterator argIt(e.embeddedObject()); while (argIt.more()) { BSONElement e = argIt.next(); if (mongoutils::str::equals(e.fieldName(), "$geometry")) { if (e.isABSONObj()) { BSONObj embeddedObj = e.embeddedObject(); if (geoContainer.parseFrom(embeddedObj)) { hasGeometry = true; } } } } // Don't want to give the error below if we could not pull any geometry out. if (!hasGeometry) { return false; } if (GeoQuery::WITHIN == predicate) { // Why do we only deal with $within {polygon}? // 1. Finding things within a point is silly and only valid // for points and degenerate lines/polys. // // 2. Finding points within a line is easy but that's called intersect. // Finding lines within a line is kind of tricky given what S2 gives us. // Doing line-within-line is a valid yet unsupported feature, // though I wonder if we want to preserve orientation for lines or // allow (a,b),(c,d) to be within (c,d),(a,b). Anyway, punt on // this for now. uassert(16672, "$within not supported with provided geometry: " + obj.toString(), geoContainer.supportsContains()); } return hasGeometry; }
Status MatchExpressionParser::_parseSub( const char* name, const BSONObj& sub, AndMatchExpression* root ) { // The one exception to {field : {fully contained argument} } is, of course, geo. Example: // sub == { field : {$near[Sphere]: [0,0], $maxDistance: 1000, $minDistance: 10 } } // We peek inside of 'sub' to see if it's possibly a $near. If so, we can't iterate over // its subfields and parse them one at a time (there is no $maxDistance without $near), so // we hand the entire object over to the geo parsing routines. BSONObjIterator geoIt(sub); if (geoIt.more()) { BSONElement firstElt = geoIt.next(); if (firstElt.isABSONObj()) { const char* fieldName = firstElt.fieldName(); // TODO: Having these $fields here isn't ideal but we don't want to pull in anything // from db/geo at this point, since it may not actually be linked in... if (mongoutils::str::equals(fieldName, "$near") || mongoutils::str::equals(fieldName, "$nearSphere") || mongoutils::str::equals(fieldName, "$geoNear") || mongoutils::str::equals(fieldName, "$maxDistance") || mongoutils::str::equals(fieldName, "$minDistance")) { StatusWithMatchExpression s = expressionParserGeoCallback(name, firstElt.getGtLtOp(), sub); if (s.isOK()) { root->add(s.getValue()); return Status::OK(); } } } } BSONObjIterator j( sub ); while ( j.more() ) { BSONElement deep = j.next(); StatusWithMatchExpression s = _parseSubField( sub, root, name, deep ); if ( !s.isOK() ) return s.getStatus(); if ( s.getValue() ) root->add( s.getValue() ); } return Status::OK(); }
Status GeoExpression::parseQuery(const BSONObj& obj) { BSONObjIterator outerIt(obj); // "within" / "geoWithin" / "geoIntersects" BSONElement queryElt = outerIt.next(); if (outerIt.more()) { return Status(ErrorCodes::BadValue, str::stream() << "can't parse extra field: " << outerIt.next()); } BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(queryElt.getGtLtOp()); if (BSONObj::opGEO_INTERSECTS == matchType) { predicate = GeoExpression::INTERSECT; } else if (BSONObj::opWITHIN == matchType) { predicate = GeoExpression::WITHIN; } else { // eoo() or unknown query predicate. return Status(ErrorCodes::BadValue, str::stream() << "invalid geo query predicate: " << obj); } // Parse geometry after predicates. if (Object != queryElt.type()) return Status(ErrorCodes::BadValue, "geometry must be an object"); BSONObj geoObj = queryElt.Obj(); BSONObjIterator geoIt(geoObj); while (geoIt.more()) { BSONElement elt = geoIt.next(); if (str::equals(elt.fieldName(), "$uniqueDocs")) { // Deprecated "$uniqueDocs" field warning() << "deprecated $uniqueDocs option: " << obj.toString() << endl; } else { // The element must be a geo specifier. "$box", "$center", "$geometry", etc. geoContainer.reset(new GeometryContainer()); Status status = geoContainer->parseFromQuery(elt); if (!status.isOK()) return status; } } if (geoContainer == NULL) { return Status(ErrorCodes::BadValue, "geo query doesn't have any geometry"); } return Status::OK(); }
StatusWithMatchExpression MatchExpressionParser::_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 ) { regex = e.String(); } else if ( e.type() == RegEx ) { regex = e.regex(); regexOptions = e.regexFlags(); } else { return StatusWithMatchExpression( ErrorCodes::BadValue, "$regex has to be a string" ); } break; case BSONObj::opOPTIONS: if ( e.type() != String ) return StatusWithMatchExpression( ErrorCodes::BadValue, "$options has to be a string" ); regexOptions = e.String(); break; default: break; } } std::auto_ptr<RegexMatchExpression> temp( new RegexMatchExpression() ); Status s = temp->init( name, regex, regexOptions ); if ( !s.isOK() ) return StatusWithMatchExpression( s ); return StatusWithMatchExpression( temp.release() ); }
bool parseQuery(const BSONObj &obj, QueryGeometry *out, bool *isNear, bool *intersect, double *maxDistance) const { // pointA = { "type" : "Point", "coordinates": [ 40, 5 ] } // t.find({ "geo" : { "$intersect" : { "$geometry" : pointA} } }) // t.find({ "geo" : { "$near" : { "$geometry" : pointA, $maxDistance : 20 }}}) // where field.name is "geo" BSONElement e = obj.firstElement(); if (!e.isABSONObj()) { return false; } BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp()); if (BSONObj::opGEO_INTERSECTS == matchType) { *intersect = true; } else if (BSONObj::opNEAR == matchType) { *isNear = true; } else { return false; } bool ret = false; BSONObjIterator argIt(e.embeddedObject()); while (argIt.more()) { BSONElement e = argIt.next(); if (mongoutils::str::equals(e.fieldName(), "$geometry")) { if (e.isABSONObj()) { BSONObj embeddedObj = e.embeddedObject(); if (out->parseFrom(embeddedObj)) { uassert(16570, "near requires point, given " + embeddedObj.toString(), !(*isNear) || GeoParser::isPoint(embeddedObj)); ret = true; } } } else if (mongoutils::str::equals(e.fieldName(), "$maxDistance")) { if (e.isNumber()) { *maxDistance = e.Number(); } } } return ret; }
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() ); }
Status GeoNearExpression::parseNewQuery(const BSONObj &obj) { bool hasGeometry = false; BSONObjIterator objIt(obj); if (!objIt.more()) { return Status(ErrorCodes::BadValue, "empty geo near query object"); } BSONElement e = objIt.next(); // Just one arg. to $geoNear. if (objIt.more()) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "geo near accepts just one argument when querying for a GeoJSON " << "point. Extra field found: " << objIt.next()); } // Parse "new" near: // t.find({"geo" : {"$near" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}}) // t.find({"geo" : {"$geoNear" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}}) if (!e.isABSONObj()) { return Status(ErrorCodes::BadValue, "geo near query argument is not an object"); } BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp()); if (BSONObj::opNEAR != matchType) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "invalid geo near query operator: " << e.fieldName()); } // Iterate over the argument. BSONObjIterator it(e.embeddedObject()); while (it.more()) { BSONElement e = it.next(); if (equals(e.fieldName(), "$geometry")) { if (e.isABSONObj()) { BSONObj embeddedObj = e.embeddedObject(); Status status = GeoParser::parseQueryPoint(e, centroid.get()); if (!status.isOK()) { return Status(ErrorCodes::BadValue, str::stream() << "invalid point in geo near query $geometry argument: " << embeddedObj << " " << status.reason()); } uassert(16681, "$near requires geojson point, given " + embeddedObj.toString(), (SPHERE == centroid->crs)); hasGeometry = true; } } else if (equals(e.fieldName(), "$minDistance")) { uassert(16897, "$minDistance must be a number", e.isNumber()); minDistance = e.Number(); uassert(16898, "$minDistance must be non-negative", minDistance >= 0.0); } else if (equals(e.fieldName(), "$maxDistance")) { uassert(16899, "$maxDistance must be a number", e.isNumber()); maxDistance = e.Number(); uassert(16900, "$maxDistance must be non-negative", maxDistance >= 0.0); } } if (!hasGeometry) { return Status(ErrorCodes::BadValue, "$geometry is required for geo near query"); } return Status::OK(); }
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" ); } }