Browse files

SERVER-958 Looser bounds for multikey indexes with parallel nested fi…

…elds
  • Loading branch information...
1 parent 2c05215 commit e780ffc12fc56885566a93f5fa97f8281885f334 @astaple astaple committed May 2, 2011
Showing with 76 additions and 4 deletions.
  1. +22 −2 db/queryutil.cpp
  2. +2 −1 jstests/arrayfind2.js
  3. +1 −1 jstests/index_check5.js
  4. +51 −0 jstests/indexr.js
View
24 db/queryutil.cpp
@@ -24,6 +24,7 @@
#include "../util/unittest.h"
#include "dbmessage.h"
#include "indexkey.h"
+#include "../util/mongoutils/str.h"
namespace mongo {
extern BSONObj staticNull;
@@ -693,19 +694,23 @@ namespace mongo {
while( i != _ranges.end() && j != other._ranges.end() ) {
int cmp = i->first.compare( j->first );
if ( cmp == 0 ) {
+ // Same field name, so find range intersection.
i->second &= j->second;
++i;
++j;
}
else if ( cmp < 0 ) {
+ // Field present in *this.
++i;
}
else {
+ // Field not present in *this, so add it.
range( j->first.c_str() ) = j->second;
++j;
}
}
while( j != other._ranges.end() ) {
+ // Field not present in *this, add it.
range( j->first.c_str() ) = j->second;
++j;
}
@@ -831,16 +836,31 @@ namespace mongo {
:_indexSpec( indexSpec ), _direction( direction >= 0 ? 1 : -1 ) {
_queries = frs._queries;
BSONObjIterator i( _indexSpec.keyPattern );
+ set< string > baseObjectNontrivialPrefixes;
while( i.more() ) {
BSONElement e = i.next();
+ const FieldRange *range = &frs.range( e.fieldName() );
+ if ( !frs.singleKey() ) {
+ string prefix = str::before( e.fieldName(), '.' );
+ if ( baseObjectNontrivialPrefixes.count( prefix ) > 0 ) {
+ // A field with the same parent field has already been
+ // constrainted, and with a multikey index we cannot
+ // constrain this field.
+ range = &frs.trivialRange();
+ } else {
+ if ( range->nontrivial() ) {
+ baseObjectNontrivialPrefixes.insert( prefix );
+ }
+ }
+ }
int number = (int) e.number(); // returns 0.0 if not numeric
bool forward = ( ( number >= 0 ? 1 : -1 ) * ( direction >= 0 ? 1 : -1 ) > 0 );
if ( forward ) {
- _ranges.push_back( frs.range( e.fieldName() ) );
+ _ranges.push_back( *range );
}
else {
_ranges.push_back( FieldRange( BSONObj().firstElement(), frs.singleKey(), false, true ) );
- frs.range( e.fieldName() ).reverse( _ranges.back() );
+ range->reverse( _ranges.back() );
}
assert( !_ranges.back().empty() );
}
View
3 jstests/arrayfind2.js
@@ -32,4 +32,5 @@ assert.eq( {"a.x":[[3,3]]}, t.find( { a : { $all : [ { $elemMatch : { x : 3 } },
t.ensureIndex( { "a.x":1,"a.y":-1 } );
-assert.eq( {"a.x":[[3,3]],"a.y":[[1.7976931348623157e+308,4]]}, t.find( { a : { $all : [ { $elemMatch : { x : 3, y : { $gt: 4 } } } ] } } ).explain().indexBounds );
+// TODO Index bounds below for elemMatch could be improved.
+assert.eq( {"a.x":[[3,3]],"a.y":[[{$maxElement:1},{$minElement:1}]]}, t.find( { a : { $all : [ { $elemMatch : { x : 3, y : { $gt: 4 } } } ] } } ).explain().indexBounds );
View
2 jstests/index_check5.js
@@ -14,4 +14,4 @@ t.save( { "name" : "Player2" ,
assert.eq( 2 , t.find( { "scores.level": 2, "scores.score": {$gt:30} } ).itcount() , "A" );
t.ensureIndex( { "scores.level" : 1 , "scores.score" : 1 } );
-assert.eq( 1 , t.find( { "scores.level": 2, "scores.score": {$gt:30} } ).itcount() , "B" );
+assert.eq( 2 , t.find( { "scores.level": 2, "scores.score": {$gt:30} } ).itcount() , "B" );
View
51 jstests/indexr.js
@@ -0,0 +1,51 @@
+// Check multikey index cases with parallel nested fields SERVER-958.
+
+t = db.jstests_indexr;
+t.drop();
+
+// Check without indexes.
+t.save( { a: [ { b: 3, c: 6 }, { b: 1, c: 1 } ] } );
+assert.eq( 1, t.count( { 'a.b':{ $gt:2 }, 'a.c': { $lt:4 } } ) );
+assert.eq( 1, t.count( { a:{ b:3, c:6 }, 'a.c': { $lt:4 } } ) );
+
+// Check with single key indexes.
+t.remove();
+t.ensureIndex( {'a.b':1,'a.c':1} );
+t.ensureIndex( {a:1,'a.c':1} );
+assert.eq( 0, t.count( { 'a.b':{ $gt:2 }, 'a.c': { $lt:4 } } ) );
+assert.eq( 0, t.count( { a:{ b:3, c:6 }, 'a.c': { $lt:4 } } ) );
+assert.eq( 4, t.find( { 'a.b':{ $gt:2 }, 'a.c': { $lt:4 } } ).explain().indexBounds['a.c'][0][1] );
+assert.eq( 4, t.find( { a:{ b:3, c:6 }, 'a.c': { $lt:4 } } ).explain().indexBounds['a.c'][0][1] );
+
+t.save( { a: { b: 3, c: 3 } } );
+assert.eq( 1, t.count( { 'a.b':{ $gt:2 }, 'a.c': { $lt:4 } } ) );
+assert.eq( 1, t.count( { a:{ b:3, c:3 }, 'a.c': { $lt:4 } } ) );
+assert.eq( 4, t.find( { 'a.b':{ $gt:2 }, 'a.c': { $lt:4 } } ).explain().indexBounds['a.c'][0][1] );
+assert.eq( 4, t.find( { a:{ b:3, c:3 }, 'a.c': { $lt:4 } } ).explain().indexBounds['a.c'][0][1] );
+
+// Check with multikey indexes.
+t.remove();
+t.save( { a: [ { b: 3, c: 6 }, { b: 1, c: 1 } ] } );
+
+assert.eq( 1, t.count( { 'a.b':{ $gt:2 }, 'a.c': { $lt:4 } } ) );
+if ( 0 ) { // SERVER-3005
+assert.eq( 1, t.count( { a:{ b:3, c:6 }, 'a.c': { $lt:4 } } ) );
+}
+assert.eq( [[{$minElement:1},{$maxElement:1}]], t.find( { 'a.b':{ $gt:2 }, 'a.c': { $lt:4 } } ).explain().indexBounds['a.c'] );
+assert.eq( [[{$minElement:1},{$maxElement:1}]], t.find( { a:{ b:3, c:6 }, 'a.c': { $lt:4 } } ).explain().indexBounds['a.c'] );
+
+// Check reverse direction.
+assert.eq( 1, t.find( { 'a.b':{ $gt:2 }, 'a.c': { $lt:4 } } ).sort( {'a.b':-1} ).itcount() );
+if ( 0 ) { // SERVER-3005
+assert.eq( 1, t.find( { a:{ b:3, c:6 }, 'a.c': { $lt:4 } } ).sort( {a:-1} ).itcount() );
+}
+
+assert.eq( [[{$maxElement:1},{$minElement:1}]], t.find( { 'a.b':{ $gt:2 }, 'a.c': { $lt:4 } } ).sort( {'a.b':-1} ).explain().indexBounds['a.c'] );
+assert.eq( [[{$maxElement:1},{$minElement:1}]], t.find( { a:{ b:3, c:6 }, 'a.c': { $lt:4 } } ).sort( {a:-1} ).explain().indexBounds['a.c'] );
+
+// Check second field is constrained if first is not.
+assert.eq( 1, t.find( { 'a.c': { $lt:4 } } ).hint( {'a.b':1,'a.c':1} ).itcount() );
+assert.eq( 1, t.find( { 'a.c': { $lt:4 } } ).hint( {a:1,'a.c':1} ).itcount() );
+
+assert.eq( 4, t.find( { 'a.c': { $lt:4 } } ).hint( {'a.b':1,'a.c':1} ).explain().indexBounds['a.c'][0][1] );
+assert.eq( 4, t.find( { 'a.c': { $lt:4 } } ).hint( {a:1,'a.c':1} ).explain().indexBounds['a.c'][0][1] );

0 comments on commit e780ffc

Please sign in to comment.