/** * 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/s2_access_method.h" #include #include "mongo/base/status.h" #include "mongo/db/geo/geoparser.h" #include "mongo/db/geo/geoconstants.h" #include "mongo/db/geo/s2common.h" #include "mongo/db/index_names.h" #include "mongo/db/index/s2_index_cursor.h" #include "mongo/db/jsobj.h" namespace mongo { static int configValueWithDefault(IndexDescriptor *desc, const string& name, int def) { BSONElement e = desc->getInfoElement(name); if (e.isNumber()) { return e.numberInt(); } return def; } S2AccessMethod::S2AccessMethod(IndexDescriptor *descriptor) : BtreeBasedAccessMethod(descriptor) { // Set up basic params. _params.maxKeysPerInsert = 200; // This is advisory. _params.maxCellsInCovering = 50; // Near distances are specified in meters...sometimes. _params.radius = kRadiusOfEarthInMeters; // These are not advisory. _params.finestIndexedLevel = configValueWithDefault(descriptor, "finestIndexedLevel", S2::kAvgEdge.GetClosestLevel(500.0 / _params.radius)); _params.coarsestIndexedLevel = configValueWithDefault(descriptor, "coarsestIndexedLevel", S2::kAvgEdge.GetClosestLevel(100 * 1000.0 / _params.radius)); uassert(16747, "coarsestIndexedLevel must be >= 0", _params.coarsestIndexedLevel >= 0); uassert(16748, "finestIndexedLevel must be <= 30", _params.finestIndexedLevel <= 30); uassert(16749, "finestIndexedLevel must be >= coarsestIndexedLevel", _params.finestIndexedLevel >= _params.coarsestIndexedLevel); int geoFields = 0; // Categorize the fields we're indexing and make sure we have a geo field. BSONObjIterator i(descriptor->keyPattern()); while (i.more()) { BSONElement e = i.next(); if (e.type() == String && IndexNames::GEO_2DSPHERE == e.String() ) { ++geoFields; } else { // We check for numeric in 2d, so that's the check here uassert( 16823, (string)"Cannot use " + IndexNames::GEO_2DSPHERE + " index with other special index types: " + e.toString(), e.isNumber() ); } } uassert(16750, "Expect at least one geo field, spec=" + descriptor->keyPattern().toString(), geoFields >= 1); } Status S2AccessMethod::newCursor(IndexCursor** out) { *out = new S2IndexCursor(_params, _descriptor); return Status::OK(); } void S2AccessMethod::getKeys(const BSONObj& obj, BSONObjSet* keys) { BSONObjSet keysToAdd; // We output keys in the same order as the fields we index. BSONObjIterator i(_descriptor->keyPattern()); while (i.more()) { BSONElement e = i.next(); // First, we get the keys that this field adds. Either they're added literally from // the value of the field, or they're transformed if the field is geo. BSONElementSet fieldElements; // false means Don't expand the last array, duh. obj.getFieldsDotted(e.fieldName(), fieldElements, false); BSONObjSet keysForThisField; if (IndexNames::GEO_2DSPHERE == e.valuestr()) { // We can't ever return documents that don't have geometry so don't bother indexing // them. if (fieldElements.empty()) { return; } getGeoKeys(obj, fieldElements, &keysForThisField); } else { getLiteralKeys(fieldElements, &keysForThisField); } // We expect there to be the missing field element present in the keys if data is // missing. So, this should be non-empty. verify(!keysForThisField.empty()); // We take the Cartesian product of all of the keys. This requires that we have // some keys to take the Cartesian product with. If keysToAdd.empty(), we // initialize it. if (keysToAdd.empty()) { keysToAdd = keysForThisField; continue; } BSONObjSet updatedKeysToAdd; for (BSONObjSet::const_iterator it = keysToAdd.begin(); it != keysToAdd.end(); ++it) { for (BSONObjSet::const_iterator newIt = keysForThisField.begin(); newIt!= keysForThisField.end(); ++newIt) { BSONObjBuilder b; b.appendElements(*it); b.append(newIt->firstElement()); updatedKeysToAdd.insert(b.obj()); } } keysToAdd = updatedKeysToAdd; } if (keysToAdd.size() > _params.maxKeysPerInsert) { warning() << "insert of geo object generated lots of keys (" << keysToAdd.size() << ") consider creating larger buckets. obj=" << obj; } *keys = keysToAdd; } // Get the index keys for elements that are GeoJSON. void S2AccessMethod::getGeoKeys(const BSONObj& document, const BSONElementSet& elements, BSONObjSet* out) const { for (BSONElementSet::iterator i = elements.begin(); i != elements.end(); ++i) { uassert(16754, "Can't parse geometry from element: " + i->toString(), i->isABSONObj()); const BSONObj &geoObj = i->Obj(); vector cells; bool succeeded = S2SearchUtil::getKeysForObject(geoObj, _params, &cells); uassert(16755, "Can't extract geo keys from object, malformed geometry?: " + document.toString(), succeeded); uassert(16756, "Unable to generate keys for (likely malformed) geometry: " + document.toString(), cells.size() > 0); for (vector::const_iterator it = cells.begin(); it != cells.end(); ++it) { BSONObjBuilder b; b.append("", *it); out->insert(b.obj()); } } if (0 == out->size()) { BSONObjBuilder b; b.appendNull(""); out->insert(b.obj()); } } void S2AccessMethod::getLiteralKeysArray(const BSONObj& obj, BSONObjSet* out) const { BSONObjIterator objIt(obj); if (!objIt.more()) { // Empty arrays are indexed as undefined. BSONObjBuilder b; b.appendUndefined(""); out->insert(b.obj()); } else { // Non-empty arrays are exploded. while (objIt.more()) { BSONObjBuilder b; b.appendAs(objIt.next(), ""); out->insert(b.obj()); } } } void S2AccessMethod::getOneLiteralKey(const BSONElement& elt, BSONObjSet* out) const { if (Array == elt.type()) { getLiteralKeysArray(elt.Obj(), out); } else { // One thing, not an array, index as-is. BSONObjBuilder b; b.appendAs(elt, ""); out->insert(b.obj()); } } // elements is a non-geo field. Add the values literally, expanding arrays. void S2AccessMethod::getLiteralKeys(const BSONElementSet& elements, BSONObjSet* out) const { if (0 == elements.size()) { // Missing fields are indexed as null. BSONObjBuilder b; b.appendNull(""); out->insert(b.obj()); } else { for (BSONElementSet::iterator i = elements.begin(); i != elements.end(); ++i) { getOneLiteralKey(*i, out); } } } } // namespace mongo