/**
* 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 .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/index_selection.h"
#include "mongo/db/index/catalog_hack.h"
#include "mongo/db/index_names.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/keypattern.h"
#include "mongo/db/queryutil.h"
namespace mongo {
IndexSuitability IndexSelection::isSuitableFor(const BSONObj& keyPattern,
const FieldRangeSet& queryConstraints,
const BSONObj& order) {
string type = CatalogHack::getAccessMethodName(keyPattern);
BSONObj query = queryConstraints.originalQuery();
// "" means it's a b-tree index, ascending or descending.
if ("" == type) {
// This is a quick first pass to determine the suitability of the index. It produces
// some false positives (returns HELPFUL for some indexes which are not particularly).
// When we return HELPFUL a more precise determination of utility is done by the query
// optimizer.
// check whether any field in the index is constrained at all by the query
BSONForEach( elt, keyPattern ){
const FieldRange& frange = queryConstraints.range( elt.fieldName() );
if( ! frange.universal() )
return HELPFUL;
}
// or whether any field in the desired sort order is in the index
set orderFields;
order.getFieldNames( orderFields );
BSONForEach( k, keyPattern ) {
if ( orderFields.find( k.fieldName() ) != orderFields.end() )
return HELPFUL;
}
return USELESS;
} else if (IndexNames::GEO_2D == type) {
string fieldName;
BSONObjIterator i(keyPattern);
while (i.more()) {
BSONElement ie = i.next();
if (ie.type() == String && IndexNames::GEO_2D == ie.valuestr()) {
fieldName = ie.fieldName();
break;
}
}
verify("" != fieldName);
BSONElement e = query.getFieldDotted(fieldName);
switch (e.type()) {
case Object: {
BSONObj sub = e.embeddedObject();
switch (sub.firstElement().getGtLtOp()) {
case BSONObj::opNEAR:
return OPTIMAL;
case BSONObj::opWITHIN: {
// Don't return optimal if it's $within: {$geometry: ... }
// because we will error out in that case, but the matcher
// or 2dsphere index may handle it.
BSONElement elt = sub.firstElement();
if (Object == elt.type()) {
BSONObjIterator it(elt.embeddedObject());
while (it.more()) {
BSONElement elt = it.next();
if (mongoutils::str::equals("$geometry", elt.fieldName())) {
return USELESS;
}
}
}
return OPTIMAL;
}
default:
// We can try to match if there's no other indexing defined,
// this is assumed a point
return HELPFUL;
}
}
case Array:
// We can try to match if there's no other indexing defined,
// this is assumed a point
return HELPFUL;
default:
return USELESS;
}
} else if (IndexNames::HASHED == type) {
/* This index is only considered "HELPFUL" for a query
* if it's the union of at least one equality constraint on the
* hashed field. Otherwise it's considered USELESS.
* Example queries (supposing the indexKey is {a : "hashed"}):
* {a : 3} HELPFUL
* {a : 3 , b : 3} HELPFUL
* {a : {$in : [3,4]}} HELPFUL
* {a : {$gte : 3, $lte : 3}} HELPFUL
* {} USELESS
* {b : 3} USELESS
* {a : {$gt : 3}} USELESS
*/
BSONElement firstElt = keyPattern.firstElement();
if (queryConstraints.isPointIntervalSet(firstElt.fieldName())) {
return HELPFUL;
} else {
return USELESS;
}
} else if (IndexNames::GEO_2DSPHERE == type) {
BSONObjIterator i(keyPattern);
while (i.more()) {
BSONElement ie = i.next();
if (ie.type() != String || IndexNames::GEO_2DSPHERE != ie.valuestr()) {
continue;
}
BSONElement e = query.getFieldDotted(ie.fieldName());
// Some locations are given to us as arrays. Sigh.
if (Array == e.type()) { return HELPFUL; }
if (Object != e.type()) { continue; }
// getGtLtOp is horribly misnamed and really means get the operation.
switch (e.embeddedObject().firstElement().getGtLtOp()) {
case BSONObj::opNEAR:
return OPTIMAL;
case BSONObj::opWITHIN: {
BSONElement elt = e.embeddedObject().firstElement();
if (Object != elt.type()) { continue; }
const char* fname = elt.embeddedObject().firstElement().fieldName();
if (mongoutils::str::equals("$geometry", fname)
|| mongoutils::str::equals("$centerSphere", fname)) {
return OPTIMAL;
} else {
return USELESS;
}
}
case BSONObj::opGEO_INTERSECTS:
return OPTIMAL;
default:
return USELESS;
}
}
return USELESS;
} else if (IndexNames::TEXT == type || IndexNames::TEXT_INTERNAL == type) {
return USELESS;
} else if (IndexNames::GEO_HAYSTACK == type) {
return USELESS;
} else {
cout << "Can't find index for keypattern " << keyPattern << endl;
verify(0);
return USELESS;
}
}
} // namespace mongo