summaryrefslogtreecommitdiff
path: root/src/mongo/db/hashindex.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/hashindex.cpp')
-rw-r--r--src/mongo/db/hashindex.cpp166
1 files changed, 166 insertions, 0 deletions
diff --git a/src/mongo/db/hashindex.cpp b/src/mongo/db/hashindex.cpp
new file mode 100644
index 00000000000..034306bf7fd
--- /dev/null
+++ b/src/mongo/db/hashindex.cpp
@@ -0,0 +1,166 @@
+// mongo/db/hashindex.cpp
+
+/**
+* Copyright (C) 2012 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "mongo/db/hashindex.h"
+
+#include "mongo/db/json.h"
+#include "mongo/db/queryutil.h"
+
+namespace mongo {
+
+ const string HashedIndexType::HASHED_INDEX_TYPE_IDENTIFIER = "hashed";
+
+ HashedIndexType::HashedIndexType( const IndexPlugin* plugin , const IndexSpec* spec ) :
+ IndexType( plugin , spec ) {
+
+ _keyPattern = spec->keyPattern;
+
+ //change these if single-field limitation lifted later
+ uassert( 16241 , "Currently only single field hashed index supported." ,
+ _keyPattern.nFields() == 1 );
+ uassert( 16242 , "Currently hashed indexes cannot guarantee uniqueness. Use a regular index." ,
+ ! (spec->info).getField("unique").booleanSafe() );
+
+ //Default _seed to 0 if "seed" is not included in the index spec
+ //or if the value of "seed" is not a number
+ _seed = (spec->info).getField("seed").numberInt();
+
+ //Default _isSparse to false if "sparse" is not included in the index spec
+ //or if the value of "sparse" is not a boolean
+ _isSparse = (spec->info).getField("sparse").booleanSafe();
+
+ //In case we have hashed indexes based on other hash functions in
+ //the future, we store a hashVersion number. If hashVersion changes,
+ // "makeSingleKey" will need to change accordingly.
+ //Defaults to 0 if "hashVersion" is not included in the index spec
+ //or if the value of "hashversion" is not a number
+ _hashVersion = (spec->info).getField("hashVersion").numberInt();
+
+ //Get the hashfield name
+ BSONElement firstElt = _keyPattern.firstElement();
+ massert( 16243 , "error: no hashed index field" ,
+ firstElt.str().compare( HASHED_INDEX_TYPE_IDENTIFIER ) == 0 );
+ _hashedField = firstElt.fieldName();
+ }
+
+ HashedIndexType::~HashedIndexType() { }
+
+ IndexSuitability HashedIndexType::suitability( const BSONObj& query , const BSONObj& order ) const {
+ FieldRangeSet frs( "" , query , true, true );
+ if ( frs.isPointIntervalSet( _hashedField ) )
+ return HELPFUL;
+ return USELESS;
+ }
+
+ void HashedIndexType::getKeys( const BSONObj &obj, BSONObjSet &keys ) const {
+ string hashedFieldCopy = string( _hashedField );
+ const char* hashedFieldCopyPtr = hashedFieldCopy.c_str();
+ BSONElement fieldVal = obj.getFieldDottedOrArray( hashedFieldCopyPtr );
+
+ uassert( 16244 , "Error: hashed indexes do not currently support array values" , fieldVal.type() != Array );
+
+ if ( ! fieldVal.eoo() ) {
+ BSONObj key = BSON( "" << makeSingleKey( fieldVal , _seed , _hashVersion ) );
+ keys.insert( key );
+ }
+ else if (! _isSparse ) {
+ BSONObj nullobj = BSON( _hashedField << BSONNULL );
+ BSONElement nullElt = nullobj.firstElement();
+ BSONObj key = BSON( "" << makeSingleKey( nullElt , _seed , _hashVersion ) );
+ keys.insert( key );
+ }
+ }
+
+ shared_ptr<Cursor> HashedIndexType::newCursor( const BSONObj& query ,
+ const BSONObj& order , int numWanted ) const {
+
+ //Use FieldRangeSet to parse the query into a vector of intervals
+ //These should be point-intervals if this cursor is ever used
+ //So the FieldInterval vector will be, e.g. <[1,1], [3,3], [6,6]>
+ FieldRangeSet frs( "" , query , true, true );
+ const vector<FieldInterval>& intervals = frs.range( _hashedField.c_str() ).intervals();
+
+ //Force a match of the query against the actual document by giving
+ //the cursor a matcher with an empty indexKeyPattern. This insures the
+ //index is not used as a covered index.
+ //NOTE: this forcing is necessary due to potential hash collisions
+ const shared_ptr< CoveredIndexMatcher > forceDocMatcher(
+ new CoveredIndexMatcher( query , BSONObj() ) );
+
+ //Construct a new query based on the hashes of the previous point-intervals
+ //e.g. {a : {$in : [ hash(1) , hash(3) , hash(6) ]}}
+ BSONObjBuilder newQueryBuilder;
+ BSONObjBuilder inObj( newQueryBuilder.subobjStart( _hashedField ) );
+ BSONArrayBuilder inArray( inObj.subarrayStart("$in") );
+ vector<FieldInterval>::const_iterator i;
+ for( i = intervals.begin(); i != intervals.end(); ++i ){
+ if ( ! i->equality() ){
+ const shared_ptr< BtreeCursor > exhaustiveCursor(
+ BtreeCursor::make( nsdetails( _spec->getDetails()->parentNS().c_str()),
+ *( _spec->getDetails() ),
+ BSON( "" << MINKEY ) ,
+ BSON( "" << MAXKEY ) ,
+ true ,
+ 1 ) );
+ exhaustiveCursor->setMatcher( forceDocMatcher );
+ return exhaustiveCursor;
+ }
+ inArray.append( makeSingleKey( i->_lower._bound , _seed , _hashVersion ) );
+ }
+ inArray.done();
+ inObj.done();
+ BSONObj newQuery = newQueryBuilder.obj();
+
+ //Use the point-intervals of the new query to create a Btree cursor
+ FieldRangeSet newfrs( "" , newQuery , true, true );
+ shared_ptr<FieldRangeVector> newVector(
+ new FieldRangeVector( newfrs , *_spec , 1 ) );
+
+ const shared_ptr< BtreeCursor > cursor(
+ BtreeCursor::make( nsdetails( _spec->getDetails()->parentNS().c_str()),
+ *( _spec->getDetails() ), newVector , 1 ) );
+ cursor->setMatcher( forceDocMatcher );
+ return cursor;
+ }
+
+
+ /* This class registers HASHED_INDEX_NAME in a global map of special index types
+ * Using this pattern, any index with the pattern, {fieldname : HASHED_INDEX_NAME}
+ * will be recognized as a HashedIndexType and the associated methods will be used.
+ */
+ class HashedIndexPlugin : public IndexPlugin {
+ public:
+
+ HashedIndexPlugin() : IndexPlugin( HashedIndexType::HASHED_INDEX_TYPE_IDENTIFIER ) {}
+
+ virtual IndexType* generate( const IndexSpec* spec ) const {
+ return new HashedIndexType( this , spec );
+ }
+
+ } hashedIndexPlugin;
+
+
+ long long int HashedIndexType::makeSingleKey( const BSONElement& e ,
+ HashSeed seed ,
+ HashVersion v ) {
+ massert( 16245 , "Only HashVersion 0 has been defined" , v == 0 );
+ return BSONElementHasher::hash64( e , seed );
+ }
+
+}
+