diff --git a/jstests/check_shard_index.js b/jstests/check_shard_index.js new file mode 100644 index 00000000000..af8a115836e --- /dev/null +++ b/jstests/check_shard_index.js @@ -0,0 +1,40 @@ +// ------------------------- +// CHECKSHARDINGINDEX TEST UTILS +// ------------------------- + +f = db.jstests_shardingindex; +f.drop(); + + +// ------------------------- +// Case 1: all entries filled or empty should make a valid index +// + +f.drop(); +f.ensureIndex( { x: 1 , y: 1 } ); +assert.eq( 0 , f.count() , "1. initial count should be zero" ); + +res = db.runCommand( { checkShardingIndex: "test.jstests_shardingindex" , keyPattern: {x:1, y:1} , force: true }); +assert.eq( true , res.ok, "1a" ); + +f.save( { x: 1 , y : 1 } ); +assert.eq( 1 , f.count() , "1. count after initial insert should be 1" ); +res = db.runCommand( { checkShardingIndex: "test.jstests_shardingindex" , keyPattern: {x:1, y:1} , force: true }); +assert.eq( true , res.ok , "1b" ); + + +// ------------------------- +// Case 2: entry with null values would make an index unsuitable +// + +f.drop(); +f.ensureIndex( { x: 1 , y: 1 } ); +assert.eq( 0 , f.count() , "2. initial count should be zero" ); + +f.save( { x: 1 , y : 1 } ); +f.save( { y: 2 } ); +assert.eq( 2 , f.count() , "2. count after initial insert should be 2" ); +res = db.runCommand( { checkShardingIndex: "test.jstests_shardingindex" , keyPattern: {x:1, y:1} , force: true }); +assert.eq( false , res.ok , "2a" ); + +print("PASSED"); diff --git a/s/d_split.cpp b/s/d_split.cpp index 490c469f3e3..0f7e82d0058 100644 --- a/s/d_split.cpp +++ b/s/d_split.cpp @@ -124,6 +124,80 @@ namespace mongo { } } cmdMedianKey; + class CheckShardingIndex : public Command { + public: + CheckShardingIndex() : Command( "checkShardingIndex" , false ) {} + virtual bool slaveOk() const { return false; } + virtual LockType locktype() const { return READ; } + virtual void help( stringstream &help ) const { + help << "Internal command.\n"; + } + + bool run(const string& dbname, BSONObj& jsobj, string& errmsg, BSONObjBuilder& result, bool fromRepl ) { + + const char* ns = jsobj.getStringField( "checkShardingIndex" ); + BSONObj keyPattern = jsobj.getObjectField( "keyPattern" ); + + // If min and max are not provided use the "minKey" and "maxKey" for the sharding key pattern. + BSONObj min = jsobj.getObjectField( "min" ); + BSONObj max = jsobj.getObjectField( "max" ); + if ( min.isEmpty() && max.isEmpty() ) { + BSONObjBuilder minBuilder; + BSONObjBuilder maxBuilder; + BSONForEach(key, keyPattern) { + minBuilder.appendMinKey( key.fieldName() ); + maxBuilder.appendMaxKey( key.fieldName() ); + } + min = minBuilder.obj(); + max = maxBuilder.obj(); + } + else if ( min.isEmpty() || max.isEmpty() ) { + errmsg = "either provide both min and max or leave both empty"; + return false; + } + + Client::Context ctx( ns ); + NamespaceDetails *d = nsdetails( ns ); + if ( ! d ) { + errmsg = "ns not found"; + return false; + } + + IndexDetails *idx = cmdIndexDetailsForRange( ns , errmsg , min , max , keyPattern ); + if ( idx == NULL ) { + errmsg = "couldn't find index over splitting key"; + return false; + } + + BtreeCursor * bc = new BtreeCursor( d , d->idxNo(*idx) , *idx , min , max , false , 1 ); + shared_ptr c( bc ); + scoped_ptr cc( new ClientCursor( QueryOption_NoCursorTimeout , c , ns ) ); + if ( ! cc->ok() ) { + // range is empty + return true; + } + + // for now, the only check is that all shard keys are filled + // TODO if $exist for nulls were picking the index, it could be used instead efficiently + while ( cc->ok() ) { + BSONObj currKey = c->currKey(); + BSONForEach(key, currKey) { + if ( key.type() == jstNULL ) { + ostringstream os; + os << "found null value in key " << bc->prettyKey( currKey ); + log() << "checkShardingIndex for '" << ns << "' failed: " << os.str() << endl; + + errmsg = os.str(); + return false; + } + } + cc->advance(); + } + + return true; + } + } cmdCheckShardingIndex; + class SplitVector : public Command { public: SplitVector() : Command( "splitVector" , false ) {}