LeafMatchExpression* GeoMatchExpression::shallowClone() const { GeoMatchExpression* next = new GeoMatchExpression(); next->init( path(), NULL, _rawObj); next->_query = _query; if (getTag()) { next->setTag(getTag()->clone()); } return next; }
TEST( ExpressionGeoTest, Geo1 ) { BSONObj query = fromjson("{loc:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); GeoQuery gq; ASSERT( gq.parseFrom( query["loc"].Obj() ) ); GeoMatchExpression ge; ASSERT( ge.init( "a", gq ).isOK() ); ASSERT(!ge.matches(fromjson("{a: [3,4]}"))); ASSERT(ge.matches(fromjson("{a: [4,4]}"))); ASSERT(ge.matches(fromjson("{a: [5,5]}"))); ASSERT(ge.matches(fromjson("{a: [5,5.1]}"))); ASSERT(ge.matches(fromjson("{a: {x: 5, y:5.1}}"))); }
TEST( ExpressionGeoTest, Geo1 ) { BSONObj query = fromjson("{loc:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); auto_ptr<GeoQuery> gq(new GeoQuery); ASSERT( gq->parseFrom( query["loc"].Obj() ) ); GeoMatchExpression ge; ASSERT( ge.init("a", gq.release(), query ).isOK() ); ASSERT(!ge.matchesBSON(fromjson("{a: [3,4]}"))); ASSERT(ge.matchesBSON(fromjson("{a: [4,4]}"))); ASSERT(ge.matchesBSON(fromjson("{a: [5,5]}"))); ASSERT(ge.matchesBSON(fromjson("{a: [5,5.1]}"))); ASSERT(ge.matchesBSON(fromjson("{a: {x: 5, y:5.1}}"))); }
LeafMatchExpression* GeoMatchExpression::shallowClone() const { GeoMatchExpression* next = new GeoMatchExpression(); next->init( path(), _query ); return next; }
// static bool QueryPlannerIXSelect::compatible(const BSONElement& elt, const IndexEntry& index, MatchExpression* node) { // Historically one could create indices with any particular value for the index spec, // including values that now indicate a special index. As such we have to make sure the // index type wasn't overridden before we pay attention to the string in the index key // pattern element. // // e.g. long ago we could have created an index {a: "2dsphere"} and it would // be treated as a btree index by an ancient version of MongoDB. To try to run // 2dsphere queries over it would be folly. string indexedFieldType; if (String != elt.type() || (INDEX_BTREE == index.type)) { indexedFieldType = ""; } else { indexedFieldType = elt.String(); } // We know elt.fieldname() == node->path(). MatchExpression::MatchType exprtype = node->matchType(); if (indexedFieldType.empty()) { // Can't check for null w/a sparse index. if (exprtype == MatchExpression::EQ && index.sparse) { const EqualityMatchExpression* expr = static_cast<const EqualityMatchExpression*>(node); if (expr->getData().isNull()) { return false; } } // We can't use a btree-indexed field for geo expressions. if (exprtype == MatchExpression::GEO || exprtype == MatchExpression::GEO_NEAR) { return false; } // There are restrictions on when we can use the index if // the expression is a NOT. if (exprtype == MatchExpression::NOT) { // Prevent negated preds from using sparse or // multikey indices. We do so for sparse indices because // we will fail to return the documents which do not contain // the indexed fields. // // We avoid multikey indices because of the semantics of // negations on multikey fields. For example, with multikey // index {a:1}, the document {a: [1,2,3]} does *not* match // the query {a: {$ne: 3}}. We'd mess this up if we used // an index scan over [MinKey, 3) and (3, MaxKey] without // a filter. if (index.sparse || index.multikey) { return false; } // Can't index negations of MOD or REGEX MatchExpression::MatchType childtype = node->getChild(0)->matchType(); if (MatchExpression::REGEX == childtype || MatchExpression::MOD == childtype) { return false; } } // We can only index EQ using text indices. This is an artificial limitation imposed by // FTSSpec::getIndexPrefix() which will fail if there is not an EQ predicate on each // index prefix field of the text index. // // Example for key pattern {a: 1, b: "text"}: // - Allowed: node = {a: 7} // - Not allowed: node = {a: {$gt: 7}} if (INDEX_TEXT != index.type) { return true; } // If we're here we know it's a text index. Equalities are OK anywhere in a text index. if (MatchExpression::EQ == exprtype) { return true; } // Not-equalities can only go in a suffix field of an index kp. We look through the key // pattern to see if the field we're looking at now appears as a prefix. If so, we // can't use this index for it. BSONObjIterator specIt(index.keyPattern); while (specIt.more()) { BSONElement elt = specIt.next(); // We hit the dividing mark between prefix and suffix, so whatever field we're // looking at is a suffix, since it appears *after* the dividing mark between the // two. As such, we can use the index. if (String == elt.type()) { return true; } // If we're here, we're still looking at prefix elements. We know that exprtype // isn't EQ so we can't use this index. if (node->path() == elt.fieldNameStringData()) { return false; } } // NOTE: This shouldn't be reached. Text index implies there is a separator implies we // will always hit the 'return true' above. invariant(0); return true; } else if (IndexNames::HASHED == indexedFieldType) { return exprtype == MatchExpression::MATCH_IN || exprtype == MatchExpression::EQ; } else if (IndexNames::GEO_2DSPHERE == indexedFieldType) { if (exprtype == MatchExpression::GEO) { // within or intersect. GeoMatchExpression* gme = static_cast<GeoMatchExpression*>(node); const GeoQuery& gq = gme->getGeoQuery(); const GeometryContainer& gc = gq.getGeometry(); return gc.hasS2Region(); } else if (exprtype == MatchExpression::GEO_NEAR) { GeoNearMatchExpression* gnme = static_cast<GeoNearMatchExpression*>(node); // Make sure the near query is compatible with 2dsphere. if (gnme->getData().centroid.crs == SPHERE || gnme->getData().isNearSphere) { return true; } } return false; } else if (IndexNames::GEO_2D == indexedFieldType) { if (exprtype == MatchExpression::GEO_NEAR) { GeoNearMatchExpression* gnme = static_cast<GeoNearMatchExpression*>(node); return gnme->getData().centroid.crs == FLAT; } else if (exprtype == MatchExpression::GEO) { // 2d only supports within. GeoMatchExpression* gme = static_cast<GeoMatchExpression*>(node); const GeoQuery& gq = gme->getGeoQuery(); if (GeoQuery::WITHIN != gq.getPred()) { return false; } const GeometryContainer& gc = gq.getGeometry(); // 2d indices answer flat queries. if (gc.hasFlatRegion()) { return true; } // 2d indices can answer centerSphere queries. if (NULL == gc._cap.get()) { return false; } verify(SPHERE == gc._cap->crs); const Circle& circle = gc._cap->circle; // No wrapping around the edge of the world is allowed in 2d centerSphere. return twoDWontWrap(circle, index); } return false; } else if (IndexNames::TEXT == indexedFieldType) { return (exprtype == MatchExpression::TEXT); } else if (IndexNames::GEO_HAYSTACK == indexedFieldType) { return false; } else { warning() << "Unknown indexing for node " << node->toString() << " and field " << elt.toString() << endl; verify(0); } }
// static bool QueryPlannerIXSelect::compatible(const BSONElement& elt, const IndexEntry& index, MatchExpression* node) { // XXX: CatalogHack::getAccessMethodName: do we have to worry about this? when? string ixtype; if (String != elt.type()) { ixtype = ""; } else { ixtype = elt.String(); } // We know elt.fieldname() == node->path(). MatchExpression::MatchType exprtype = node->matchType(); // TODO: use indexnames if ("" == ixtype) { if (index.sparse && exprtype == MatchExpression::EQ) { // Can't check for null w/a sparse index. const EqualityMatchExpression* expr = static_cast<const EqualityMatchExpression*>(node); return !expr->getData().isNull(); } return exprtype != MatchExpression::GEO && exprtype != MatchExpression::GEO_NEAR; } else if ("hashed" == ixtype) { return exprtype == MatchExpression::MATCH_IN || exprtype == MatchExpression::EQ; } else if ("2dsphere" == ixtype) { if (exprtype == MatchExpression::GEO) { // within or intersect. GeoMatchExpression* gme = static_cast<GeoMatchExpression*>(node); const GeoQuery& gq = gme->getGeoQuery(); const GeometryContainer& gc = gq.getGeometry(); return gc.hasS2Region(); } else if (exprtype == MatchExpression::GEO_NEAR) { GeoNearMatchExpression* gnme = static_cast<GeoNearMatchExpression*>(node); // Make sure the near query is compatible with 2dsphere. if (gnme->getData().centroid.crs == SPHERE || gnme->getData().isNearSphere) { return true; } } return false; } else if ("2d" == ixtype) { if (exprtype == MatchExpression::GEO_NEAR) { GeoNearMatchExpression* gnme = static_cast<GeoNearMatchExpression*>(node); return gnme->getData().centroid.crs == FLAT; } else if (exprtype == MatchExpression::GEO) { // 2d only supports within. GeoMatchExpression* gme = static_cast<GeoMatchExpression*>(node); const GeoQuery& gq = gme->getGeoQuery(); if (GeoQuery::WITHIN != gq.getPred()) { return false; } const GeometryContainer& gc = gq.getGeometry(); // 2d indices answer flat queries. if (gc.hasFlatRegion()) { return true; } // 2d indices can answer centerSphere queries. if (NULL == gc._cap.get()) { return false; } verify(SPHERE == gc._cap->crs); const Circle& circle = gc._cap->circle; // No wrapping around the edge of the world is allowed in 2d centerSphere. return twoDWontWrap(circle, index); } return false; } else if ("text" == ixtype) { return (exprtype == MatchExpression::TEXT); } else if ("geoHaystack" == ixtype) { return false; } else { warning() << "Unknown indexing for node " << node->toString() << " and field " << elt.toString() << endl; verify(0); } }