Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

SERVER-1752 Optimize simple indexed counts by counting the number of …

…btree keys in a range, without checking the bson value of each key.
  • Loading branch information...
commit 428bb865bfd24848ffba0fb9b9514264e5b3816e 1 parent 0626526
astaple authored

Showing 28 changed files with 2,455 additions and 162 deletions. Show diff stats Hide diff stats

  1. 48  jstests/count8.js
  2. 59  jstests/countc.js
  3. 43  jstests/slowNightly/index_check9.js
  4. 2  src/mongo/SConscript
  5. 5  src/mongo/db/btree.cpp
  6. 96  src/mongo/db/btreeposition.cpp
  7. 105  src/mongo/db/btreeposition.h
  8. 11  src/mongo/db/diskloc.h
  9. 200  src/mongo/db/intervalbtreecursor.cpp
  10. 145  src/mongo/db/intervalbtreecursor.h
  11. 26  src/mongo/db/namespace_details.h
  12. 33  src/mongo/db/ops/count.cpp
  13. 9  src/mongo/db/ops/query.cpp
  14. 4  src/mongo/db/pipeline/pipeline_d.cpp
  15. 39  src/mongo/db/queryoptimizer.cpp
  16. 3  src/mongo/db/queryoptimizer.h
  17. 24  src/mongo/db/queryoptimizercursor.h
  18. 17  src/mongo/db/queryoptimizercursorimpl.cpp
  19. 2  src/mongo/db/queryoptimizercursorimpl.h
  20. 2  src/mongo/db/queryutil-inl.h
  21. 141  src/mongo/db/queryutil.cpp
  22. 63  src/mongo/db/queryutil.h
  23. 327  src/mongo/dbtests/btreepositiontests.cpp
  24. 20  src/mongo/dbtests/cursortests.cpp
  25. 660  src/mongo/dbtests/intervalbtreecursortests.cpp
  26. 55  src/mongo/dbtests/queryoptimizercursortests.cpp
  27. 216  src/mongo/dbtests/queryoptimizertests.cpp
  28. 262  src/mongo/dbtests/queryutiltests.cpp
48  jstests/count8.js
... ...
@@ -1,48 +0,0 @@
1  
-// Test count yielding, in both fast and normal count modes.
2  
-
3  
-t = db.jstests_count8;
4  
-t.drop();
5  
-
6  
-function checkYield( dropCollection, fastCount, query ) {
7  
-
8  
-    obj = fastCount ? {a:true} : {a:1};
9  
-    query = query || obj;
10  
-    
11  
-    passed = false;
12  
-    for( nDocs = 20000; nDocs < 8000000; nDocs *= 2 ) {
13  
-
14  
-        t.drop();
15  
-        t.ensureIndex( {a:1} );
16  
-        for( i = 0; i < nDocs; ++i ) {
17  
-            t.insert( obj );
18  
-        }
19  
-        db.getLastError();
20  
-
21  
-        if ( dropCollection ) {
22  
-            p = startParallelShell( 'sleep( 30 ); db.jstests_count8.drop(); db.getLastError();' );
23  
-        } else {
24  
-            p = startParallelShell( 'sleep( 30 ); db.jstests_count8.update( {$atomic:true}, {$set:{a:-1}}, false, true ); db.getLastError();' );
25  
-        }
26  
-
27  
-        printjson( query );
28  
-        count = t.count( query );
29  
-        // We test that count yields by requesting a concurrent operation modifying the collection
30  
-        // and checking that the count result is modified.
31  
-        print( 'count: ' + count + ', nDocs: ' + nDocs );
32  
-        if ( count < nDocs ) {
33  
-            passed = true;
34  
-            p();
35  
-            break;
36  
-        }
37  
-    
38  
-        p();
39  
-    }
40  
-
41  
-    assert( passed );
42  
-}
43  
-
44  
-checkYield( true, false );
45  
-checkYield( false, false );
46  
-checkYield( true, true );
47  
-checkYield( false, true );
48  
-checkYield( true, false, {$or:[{a:1},{a:2}]} );
59  jstests/countc.js
... ...
@@ -1,6 +1,6 @@
1 1
 // In fast count mode the Matcher is bypassed when matching can be performed by a BtreeCursor and
2  
-// its delegate FieldRangeVector.  The tests below check that fast count mode is enabled and
3  
-// implemented appropriately in specific cases.
  2
+// its delegate FieldRangeVector or an IntervalBtreeCursor.  The tests below check that fast count
  3
+// mode is implemented appropriately in specific cases.
4 4
 //
5 5
 // SERVER-1752
6 6
 
@@ -65,3 +65,58 @@ assert.eq( 1, t.count( { a:{ $lte:new Date( 1 ) } } ) );
65 65
 t.drop();
66 66
 t.ensureIndex( { a:1 } );
67 67
 assert.throws( function() { t.count( { a:undefined } ); } );
  68
+
  69
+
  70
+// Count using a descending order index.
  71
+t.drop();
  72
+t.ensureIndex( { a:-1 } );
  73
+t.save( { a:1 } );
  74
+t.save( { a:2 } );
  75
+t.save( { a:3 } );
  76
+assert.eq( 1, t.count( { a:{ $gt:2 } } ) );
  77
+assert.eq( 1, t.count( { a:{ $lt:2 } } ) );
  78
+assert.eq( 2, t.count( { a:{ $lte:2 } } ) );
  79
+assert.eq( 2, t.count( { a:{ $lt:3 } } ) );
  80
+
  81
+
  82
+// Count using a compound index.
  83
+t.drop();
  84
+t.ensureIndex( { a:1, b:1 } );
  85
+t.save( { a:1, b:2 } );
  86
+t.save( { a:2, b:1 } );
  87
+t.save( { a:2, b:3 } );
  88
+t.save( { a:3, b:4 } );
  89
+assert.eq( 1, t.count( { a:{ $gt:2 } } ) );
  90
+assert.eq( 1, t.count( { a:{ $lt:2 } } ) );
  91
+assert.eq( 2, t.count( { a:2, b:{ $gt:0 } } ) );
  92
+assert.eq( 1, t.count( { a:2, b:{ $lt:3 } } ) );
  93
+assert.eq( 1, t.count( { a:1, b:{ $lt:3 } } ) );
  94
+
  95
+
  96
+// Count using a compound descending order index.
  97
+t.drop();
  98
+t.ensureIndex( { a:1, b:-1 } );
  99
+t.save( { a:1, b:2 } );
  100
+t.save( { a:2, b:1 } );
  101
+t.save( { a:2, b:3 } );
  102
+t.save( { a:3, b:4 } );
  103
+assert.eq( 1, t.count( { a:{ $gt:2 } } ) );
  104
+assert.eq( 1, t.count( { a:{ $lt:2 } } ) );
  105
+assert.eq( 2, t.count( { a:2, b:{ $gt:0 } } ) );
  106
+assert.eq( 1, t.count( { a:2, b:{ $lt:3 } } ) );
  107
+assert.eq( 1, t.count( { a:1, b:{ $lt:3 } } ) );
  108
+
  109
+
  110
+// Count with a multikey value.
  111
+t.drop();
  112
+t.ensureIndex( { a:1 } );
  113
+t.save( { a:[ 1, 2 ] } );
  114
+assert.eq( 1, t.count( { a:{ $gt:0, $lte:2 } } ) );
  115
+
  116
+
  117
+// Count with a match constraint on an unindexed field.
  118
+t.drop();
  119
+t.ensureIndex( { a:1 } );
  120
+t.save( { a:1, b:1 } );
  121
+t.save( { a:1, b:2 } );
  122
+assert.eq( 1, t.count( { a:1, $where:'this.b == 1' } ) );
43  jstests/slowNightly/index_check9.js
@@ -57,38 +57,53 @@ function check() {
57 57
     }
58 58
     var spec = {};
59 59
     for( var i = 0; i < n; ++i ) {
60  
-        if ( Random.rand() > 0.5 ) {
  60
+        var predicateType = Random.randInt( 4 );
  61
+        switch( predicateType ) {
  62
+        case 0 /* range */ : {
61 63
             var bounds = [ r( alphas[ i ] ), r( alphas[ i ] ) ];
62 64
             if ( bounds[ 0 ] > bounds[ 1 ] ) {
63 65
                 bounds.reverse();
64 66
             }
65  
-	    var s = {};
66  
-	    if ( Random.rand() > 0.5 ) {
67  
-		s[ "$gte" ] = bounds[ 0 ];
68  
-	    } else {
69  
-		s[ "$gt" ] = bounds[ 0 ];
70  
-	    }
71  
-	    if ( Random.rand() > 0.5 ) {
72  
-		s[ "$lte" ] = bounds[ 1 ];
73  
-	    } else {
74  
-		s[ "$lt" ] = bounds[ 1 ];
75  
-	    }
  67
+            var s = {};
  68
+            if ( Random.rand() > 0.5 ) {
  69
+                s[ "$gte" ] = bounds[ 0 ];
  70
+            } else {
  71
+                s[ "$gt" ] = bounds[ 0 ];
  72
+            }
  73
+            if ( Random.rand() > 0.5 ) {
  74
+                s[ "$lte" ] = bounds[ 1 ];
  75
+            } else {
  76
+                s[ "$lt" ] = bounds[ 1 ];
  77
+            }
76 78
             spec[ fields[ i ] ] = s;
77  
-        } else {
  79
+            break;
  80
+        }
  81
+        case 1 /* $in */ : {
78 82
             var vals = []
79  
-            for( var j = 0; j < Random.randInt( 15 ); ++j ) {
  83
+            var inLength = Random.randInt( 15 );
  84
+            for( var j = 0; j < inLength; ++j ) {
80 85
                 vals.push( r( alphas[ i ] ) );
81 86
             }
82 87
             spec[ fields[ i ] ] = { $in: vals };
  88
+            break;
  89
+        }
  90
+        case 2 /* equality */ : {
  91
+            spec[ fields[ i ] ] = r( alphas[ i ] );
  92
+            break;
  93
+        }
  94
+        default /* no predicate */ :
  95
+            break;
83 96
         }
84 97
     }
85 98
     s = sort();
86 99
     c1 = t.find( spec, { _id:null } ).sort( s ).hint( idx ).toArray();
87 100
     c2 = t.find( spec ).sort( s ).explain().nscanned;
88 101
     c3 = t.find( spec, { _id:null } ).sort( s ).hint( {$natural:1} ).toArray();
  102
+    count = t.count( spec );
89 103
     //    assert.eq( c1, c3, "spec: " + tojson( spec ) + ", sort: " + tojson( s ) );
90 104
     //    assert.eq( c1.length, c2 );
91 105
     assert.eq( c1, c3 );
  106
+    assert.eq( c3.length, count );
92 107
 }
93 108
 
94 109
 for( var i = 0; i < 10000; ++i ) {
2  src/mongo/SConscript
@@ -333,6 +333,8 @@ serverOnlyFiles = [ "db/curop.cpp",
333 333
                     "db/prefetch.cpp",
334 334
                     "db/repl_block.cpp",
335 335
                     "db/btreecursor.cpp",
  336
+                    "db/intervalbtreecursor.cpp",
  337
+                    "db/btreeposition.cpp",
336 338
                     "db/cloner.cpp",
337 339
                     "db/namespace_details.cpp",
338 340
                     "db/cap.cpp",
5  src/mongo/db/btree.cpp
@@ -894,6 +894,11 @@ namespace mongo {
894 894
      * This function is only needed in cases where k has a left or right child;
895 895
      * in other cases a simpler key removal implementation is possible.
896 896
      *
  897
+     * NOTE on noncompliant BtreeBuilder btrees:
  898
+     * It is possible (though likely rare) for btrees created by BtreeBuilder to
  899
+     * have k' that is not a leaf, see SERVER-2732.  These cases are handled in
  900
+     * the same manner as described in the "legacy btree structures" note below.
  901
+     *
897 902
      * NOTE on legacy btree structures:
898 903
      * In legacy btrees, k' can be a nonleaf.  In such a case we 'delete' k by
899 904
      * marking it as an unused node rather than replacing it with k'.  Also, k'
96  src/mongo/db/btreeposition.cpp
... ...
@@ -0,0 +1,96 @@
  1
+/**
  2
+ *    Copyright (C) 2012 10gen Inc.
  3
+ *
  4
+ *    This program is free software: you can redistribute it and/or  modify
  5
+ *    it under the terms of the GNU Affero General Public License, version 3,
  6
+ *    as published by the Free Software Foundation.
  7
+ *
  8
+ *    This program is distributed in the hope that it will be useful,
  9
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  10
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11
+ *    GNU Affero General Public License for more details.
  12
+ *
  13
+ *    You should have received a copy of the GNU Affero General Public License
  14
+ *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15
+ */
  16
+
  17
+#include "mongo/db/btreeposition.h"
  18
+
  19
+#include "mongo/db/btree.h"
  20
+#include "mongo/db/index.h"
  21
+
  22
+namespace mongo {
  23
+
  24
+    std::ostream& operator<<( std::ostream& stream, const BtreeKeyLocation& loc ) {
  25
+        return stream << BSON( "bucket" << loc.bucket.toString() <<
  26
+                               "index" << loc.pos ).jsonString();
  27
+    }
  28
+
  29
+    LogicalBtreePosition::LogicalBtreePosition( const IndexDetails& indexDetails,
  30
+                                                Ordering ordering,
  31
+                                                const BtreeKeyLocation& initialLocation ) :
  32
+        _indexDetails( &indexDetails ),
  33
+        _ordering( ordering ),
  34
+        _initialLocation( initialLocation ),
  35
+        _initialLocationValid() {
  36
+        fassert( 16494, _indexDetails->version() == 1 );
  37
+    }
  38
+
  39
+    void LogicalBtreePosition::init() {
  40
+        if ( _initialLocation.bucket.isNull() ) {
  41
+            // Abort if the initial location is not a valid bucket.
  42
+            return;
  43
+        }
  44
+
  45
+        // Store the key and record referenced at the supplied initial location.
  46
+        BucketBasics<V1>::KeyNode keyNode =
  47
+                _initialLocation.bucket.btree<V1>()->keyNode( _initialLocation.pos );
  48
+        _key = keyNode.key.toBson().getOwned();
  49
+        _record = keyNode.recordLoc;
  50
+        _initialLocationValid = true;
  51
+    }
  52
+
  53
+    BtreeKeyLocation LogicalBtreePosition::currentLocation() const {
  54
+        if ( _initialLocation.bucket.isNull() ) {
  55
+            // Abort if the initial location is not a valid bucket.
  56
+            return BtreeKeyLocation();
  57
+        }
  58
+
  59
+        // If the initial location has not been invalidated ...
  60
+        if ( _initialLocationValid ) {
  61
+
  62
+            const BtreeBucket<V1>* bucket = _initialLocation.bucket.btree<V1>();
  63
+            if ( // ... and the bucket was not marked as invalid ...
  64
+                 bucket->getN() != bucket->INVALID_N_SENTINEL &&
  65
+                 // ... and the initial location index is valid for the bucket ...
  66
+                 _initialLocation.pos < bucket->getN() ) {
  67
+
  68
+                BucketBasics<V1>::KeyNode keyNode = bucket->keyNode( _initialLocation.pos );
  69
+                if ( // ... and the record location equals the initial record location ...
  70
+                     keyNode.recordLoc == _record &&
  71
+                     // ... and the key equals the initial key ...
  72
+                     keyNode.key.toBson().binaryEqual( _key ) ) {
  73
+                    // ... then the initial location is the current location, so return it.
  74
+                    return _initialLocation;
  75
+                }
  76
+            }
  77
+        }
  78
+
  79
+        // Relocate the key and record location retrieved from _initialLocation.
  80
+        BtreeKeyLocation ret;
  81
+        bool found;
  82
+        ret.bucket = _indexDetails->head.btree<V1>()->locate( *_indexDetails,
  83
+                                                              _indexDetails->head,
  84
+                                                              _key,
  85
+                                                              _ordering,
  86
+                                                              ret.pos,
  87
+                                                              found,
  88
+                                                              _record,
  89
+                                                              1 // Forward direction means the next
  90
+                                                                // ordered key will be returned if
  91
+                                                                // the requested key is missing.
  92
+                                                             );
  93
+        return ret;
  94
+    }
  95
+
  96
+} // namespace mongo
105  src/mongo/db/btreeposition.h
... ...
@@ -0,0 +1,105 @@
  1
+/**
  2
+ *    Copyright (C) 2012 10gen Inc.
  3
+ *
  4
+ *    This program is free software: you can redistribute it and/or  modify
  5
+ *    it under the terms of the GNU Affero General Public License, version 3,
  6
+ *    as published by the Free Software Foundation.
  7
+ *
  8
+ *    This program is distributed in the hope that it will be useful,
  9
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  10
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11
+ *    GNU Affero General Public License for more details.
  12
+ *
  13
+ *    You should have received a copy of the GNU Affero General Public License
  14
+ *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15
+ */
  16
+
  17
+#pragma once
  18
+
  19
+#include "mongo/db/diskloc.h"
  20
+#include "mongo/db/jsobj.h"
  21
+#include "mongo/platform/cstdint.h"
  22
+
  23
+namespace mongo {
  24
+
  25
+    class IndexDetails;
  26
+
  27
+    /**
  28
+     * Physical location of a key within a btree.  Comprised of the DiskLoc address of a btree
  29
+     * bucket and the index of a key within that bucket.
  30
+     */
  31
+    struct BtreeKeyLocation {
  32
+
  33
+        BtreeKeyLocation() :
  34
+            pos() {
  35
+        }
  36
+
  37
+        BtreeKeyLocation( DiskLoc initialBucket, int32_t initialPos ) :
  38
+            bucket( initialBucket ),
  39
+            pos( initialPos ) {
  40
+        }
  41
+
  42
+        bool operator==( const BtreeKeyLocation& other ) const {
  43
+            return bucket == other.bucket && pos == other.pos;
  44
+        }
  45
+
  46
+        DiskLoc bucket; // Bucket within btree.
  47
+        int32_t pos;    // Index within bucket.
  48
+    };
  49
+
  50
+    std::ostream& operator<<( std::ostream& stream, const BtreeKeyLocation& loc );
  51
+
  52
+    /**
  53
+     * Logical btree position independent of the physical structure of a btree.  This is used to
  54
+     * track a position within a btree while the structure of the btree is changing.
  55
+     *
  56
+     * For example, a btree containing keys 'a', 'b', and 'c' might have all three keys in one
  57
+     * bucket or alternatively 'b' within a left child of 'c'.  The same LogicalBtreePosition can
  58
+     * represent the position of 'b' in both cases and can retrieve the physical BtreeKeyLocation of
  59
+     * 'b' in each case.  If the btree is changed so that it lacks a 'b' key, the position will
  60
+     * reference the lowest key greater than 'b'.  This is desirable behavior when the logical btree
  61
+     * position is used to implement a forward direction iterator.
  62
+     *
  63
+     * The class is seeded with a BtreeKeyLocation identifying a btree key.  This initial physical
  64
+     * location is cached in order to quickly check if the physical location corresponding to the
  65
+     * logical position is unchanged and can be returned as is.
  66
+     *
  67
+     * NOTE Only supports V1 indexes.
  68
+     */
  69
+    class LogicalBtreePosition {
  70
+    public:
  71
+
  72
+        /**
  73
+         * Create a position with the @param 'indexDetails', @param 'ordering', and initial key
  74
+         * location @param 'initialLocation' specified.
  75
+         * @fasserts if 'indexDetails' is not a V1 index.
  76
+         */
  77
+        LogicalBtreePosition( const IndexDetails& indexDetails,
  78
+                              Ordering ordering,
  79
+                              const BtreeKeyLocation& initialLocation );
  80
+
  81
+        /** Initialize the position by reading the key at the supplied initial location. */
  82
+        void init();
  83
+
  84
+        /**
  85
+         * Invalidate the supplied initial location.  This may be called when bucket containing the
  86
+         * supplied location is deleted.
  87
+         */
  88
+        void invalidateInitialLocation() { _initialLocationValid = false; }
  89
+
  90
+        /**
  91
+         * Retrieve the current physical location in the btree corresponding to this logical
  92
+         * position.
  93
+         */
  94
+        BtreeKeyLocation currentLocation() const;
  95
+
  96
+    private:
  97
+        const IndexDetails* _indexDetails;
  98
+        Ordering _ordering;
  99
+        BtreeKeyLocation _initialLocation;
  100
+        bool _initialLocationValid;
  101
+        BSONObj _key;
  102
+        DiskLoc _record;
  103
+    };
  104
+
  105
+} // namespace mongo
11  src/mongo/db/diskloc.h
@@ -22,7 +22,8 @@
22 22
 
23 23
 #pragma once
24 24
 
25  
-#include "jsobj.h"
  25
+#include "mongo/db/jsobj.h"
  26
+#include "mongo/platform/cstdint.h"
26 27
 
27 28
 namespace mongo {
28 29
 
@@ -51,7 +52,7 @@ namespace mongo {
51 52
 
52 53
             // Caps the number of files that may be allocated in a database, allowing about 32TB of
53 54
             // data per db.  Note that the DiskLoc and DiskLoc56Bit types supports more files than
54  
-            // this value, as does the storage format.
  55
+            // this value, as does the data storage format.
55 56
             MaxFiles=16000
56 57
         };
57 58
 
@@ -130,6 +131,8 @@ namespace mongo {
130 131
             return compare(b) < 0;
131 132
         }
132 133
 
  134
+        uint64_t asUint64() const { return *reinterpret_cast<const uint64_t*>( this ); }
  135
+
133 136
         /**
134 137
          * Marks this disk loc for writing
135 138
          * @returns a non const reference to this disk loc
@@ -158,6 +161,10 @@ namespace mongo {
158 161
     };
159 162
 #pragma pack()
160 163
 
  164
+    inline std::ostream& operator<<( std::ostream &stream, const DiskLoc &loc ) {
  165
+        return stream << loc.toString();
  166
+    }
  167
+
161 168
     // Minimum allowed DiskLoc.  No Record may begin at this location because file and extent
162 169
     // headers must precede Records in a file.
163 170
     const DiskLoc minDiskLoc(0, 0);
200  src/mongo/db/intervalbtreecursor.cpp
... ...
@@ -0,0 +1,200 @@
  1
+/**
  2
+ *    Copyright (C) 2012 10gen Inc.
  3
+ *
  4
+ *    This program is free software: you can redistribute it and/or  modify
  5
+ *    it under the terms of the GNU Affero General Public License, version 3,
  6
+ *    as published by the Free Software Foundation.
  7
+ *
  8
+ *    This program is distributed in the hope that it will be useful,
  9
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  10
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11
+ *    GNU Affero General Public License for more details.
  12
+ *
  13
+ *    You should have received a copy of the GNU Affero General Public License
  14
+ *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15
+ */
  16
+
  17
+#include "mongo/db/intervalbtreecursor.h"
  18
+
  19
+#include "mongo/db/btree.h"
  20
+#include "mongo/db/kill_current_op.h"
  21
+
  22
+namespace mongo {
  23
+
  24
+    /**
  25
+     * Advance 'loc' until it does not reference an unused key, or the end of the btree is reached.
  26
+     */
  27
+    static void skipUnused( BtreeKeyLocation* loc ) {
  28
+
  29
+        // While loc points to an unused key ...
  30
+        while( !loc->bucket.isNull() &&
  31
+               loc->bucket.btree<V1>()->k( loc->pos ).isUnused() ) {
  32
+
  33
+            // ... advance loc to the next key in the btree.
  34
+            loc->bucket = loc->bucket.btree<V1>()->advance( loc->bucket,
  35
+                                                            loc->pos,
  36
+                                                            1,
  37
+                                                            __FUNCTION__ );
  38
+        }
  39
+    }
  40
+
  41
+    IntervalBtreeCursor* IntervalBtreeCursor::make( NamespaceDetails* namespaceDetails,
  42
+                                                    const IndexDetails& indexDetails,
  43
+                                                    const BSONObj& lowerBound,
  44
+                                                    bool lowerBoundInclusive,
  45
+                                                    const BSONObj& upperBound,
  46
+                                                    bool upperBoundInclusive ) {
  47
+        if ( indexDetails.version() != 1 ) {
  48
+            // Only v1 indexes are supported.
  49
+            return NULL;
  50
+        }
  51
+        auto_ptr<IntervalBtreeCursor> ret( new IntervalBtreeCursor( namespaceDetails,
  52
+                                                                    indexDetails,
  53
+                                                                    lowerBound,
  54
+                                                                    lowerBoundInclusive,
  55
+                                                                    upperBound,
  56
+                                                                    upperBoundInclusive ) );
  57
+        ret->init();
  58
+        return ret.release();
  59
+    }
  60
+
  61
+    IntervalBtreeCursor::IntervalBtreeCursor( NamespaceDetails* namespaceDetails,
  62
+                                              const IndexDetails& indexDetails,
  63
+                                              const BSONObj& lowerBound,
  64
+                                              bool lowerBoundInclusive,
  65
+                                              const BSONObj& upperBound,
  66
+                                              bool upperBoundInclusive ) :
  67
+        _namespaceDetails( *namespaceDetails ),
  68
+        _indexNo( namespaceDetails->idxNo( indexDetails ) ),
  69
+        _indexDetails( indexDetails ),
  70
+        _ordering( Ordering::make( _indexDetails.keyPattern() ) ),
  71
+        _lowerBound( lowerBound ),
  72
+        _lowerBoundInclusive( lowerBoundInclusive ),
  73
+        _upperBound( upperBound ),
  74
+        _upperBoundInclusive( upperBoundInclusive ),
  75
+        _currRecoverable( _indexDetails, _ordering, _curr ),
  76
+        _nscanned(),
  77
+        _multikeyFlag() {
  78
+    }
  79
+
  80
+    void IntervalBtreeCursor::init() {
  81
+        _multikeyFlag = _namespaceDetails.isMultikey( _indexNo );
  82
+        _curr = locateKey( _lowerBound, !_lowerBoundInclusive );
  83
+        skipUnused( &_curr );
  84
+        relocateEnd();
  85
+        if ( ok() ) {
  86
+            _nscanned = 1;
  87
+        }
  88
+    }
  89
+
  90
+    bool IntervalBtreeCursor::ok() {
  91
+        return !_curr.bucket.isNull();
  92
+    }
  93
+
  94
+    DiskLoc IntervalBtreeCursor::currLoc() {
  95
+        if ( eof() ) {
  96
+            return DiskLoc();
  97
+        }
  98
+        return _curr.bucket.btree<V1>()->keyNode( _curr.pos ).recordLoc;
  99
+    }
  100
+
  101
+    bool IntervalBtreeCursor::advance() {
  102
+        RARELY killCurrentOp.checkForInterrupt();
  103
+        if ( eof() ) {
  104
+            return false;
  105
+        }
  106
+        // Advance _curr to the next key in the btree.
  107
+        _curr.bucket = _curr.bucket.btree<V1>()->advance( _curr.bucket,
  108
+                                                          _curr.pos,
  109
+                                                          1,
  110
+                                                          __FUNCTION__ );
  111
+        skipUnused( &_curr );
  112
+        if ( _curr == _end ) {
  113
+            // _curr has reached _end, so iteration is complete.
  114
+            _curr.bucket.Null();
  115
+        }
  116
+        else {
  117
+            ++_nscanned;
  118
+        }
  119
+        return ok();
  120
+    }
  121
+
  122
+    BSONObj IntervalBtreeCursor::currKey() const {
  123
+        if ( _curr.bucket.isNull() ) {
  124
+            return BSONObj();
  125
+        }
  126
+        return _curr.bucket.btree<V1>()->keyNode( _curr.pos ).key.toBson();
  127
+    }
  128
+
  129
+    void IntervalBtreeCursor::aboutToDeleteBucket( const DiskLoc& b ) {
  130
+        if ( b == _curr.bucket ) {
  131
+            _currRecoverable.invalidateInitialLocation();
  132
+        }
  133
+    }
  134
+
  135
+    void IntervalBtreeCursor::noteLocation() {
  136
+        _currRecoverable = LogicalBtreePosition( _indexDetails, _ordering, _curr );
  137
+        _currRecoverable.init();
  138
+    }
  139
+
  140
+    void IntervalBtreeCursor::checkLocation() {
  141
+        _multikeyFlag = _namespaceDetails.isMultikey( _indexNo );
  142
+        _curr = _currRecoverable.currentLocation();
  143
+        skipUnused( &_curr );
  144
+        relocateEnd();
  145
+    }
  146
+
  147
+    bool IntervalBtreeCursor::getsetdup( DiskLoc loc ) {
  148
+        // TODO _multikeyFlag may be set part way through an iteration by checkLocation().  In this
  149
+        // case results returned earlier, when _multikeyFlag was false, will not be deduped.  This
  150
+        // is an old issue with all mongo btree cursor implementations.
  151
+        return _multikeyFlag && !_dups.insert( loc.asUint64() ).second;
  152
+    }
  153
+
  154
+    BSONObj IntervalBtreeCursor::prettyIndexBounds() const {
  155
+        return BSON( "lower" << _lowerBound.replaceFieldNames( _indexDetails.keyPattern() ) <<
  156
+                     "upper" << _upperBound.replaceFieldNames( _indexDetails.keyPattern() ) );
  157
+    }
  158
+
  159
+    BtreeKeyLocation IntervalBtreeCursor::locateKey( const BSONObj& key, bool afterKey ) {
  160
+        bool found;
  161
+        BtreeKeyLocation ret;
  162
+
  163
+        // To find the first btree location equal to the specified key, specify a record location of
  164
+        // minDiskLoc, which is below any actual Record location.  To find the first btree location
  165
+        // greater than the specified key, specify a record location of maxDiskLoc, which is above
  166
+        // any actual Record location.
  167
+        DiskLoc targetRecord = afterKey ? maxDiskLoc : minDiskLoc;
  168
+
  169
+        // Find the requested location in the btree.
  170
+        ret.bucket = _indexDetails.head.btree<V1>()->locate( _indexDetails,
  171
+                                                             _indexDetails.head,
  172
+                                                             key,
  173
+                                                             _ordering,
  174
+                                                             ret.pos,
  175
+                                                             found,
  176
+                                                             targetRecord,
  177
+                                                             1 );
  178
+        return ret;
  179
+    }
  180
+
  181
+    void IntervalBtreeCursor::relocateEnd() {
  182
+        if ( eof() ) {
  183
+            return;
  184
+        }
  185
+
  186
+        // If the current key is above the upper bound ...
  187
+        int32_t cmp = currKey().woCompare( _upperBound, _ordering, false );
  188
+        if ( cmp > 0 || ( cmp == 0 && !_upperBoundInclusive ) ) {
  189
+
  190
+            // ... then iteration is complete.
  191
+            _curr.bucket.Null();
  192
+            return;
  193
+        }
  194
+
  195
+        // Otherwise, relocate _end.
  196
+        _end = locateKey( _upperBound, _upperBoundInclusive );
  197
+        skipUnused( &_end );
  198
+    }
  199
+
  200
+} // namespace mongo
145  src/mongo/db/intervalbtreecursor.h
... ...
@@ -0,0 +1,145 @@
  1
+/**
  2
+ *    Copyright (C) 2012 10gen Inc.
  3
+ *
  4
+ *    This program is free software: you can redistribute it and/or  modify
  5
+ *    it under the terms of the GNU Affero General Public License, version 3,
  6
+ *    as published by the Free Software Foundation.
  7
+ *
  8
+ *    This program is distributed in the hope that it will be useful,
  9
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  10
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11
+ *    GNU Affero General Public License for more details.
  12
+ *
  13
+ *    You should have received a copy of the GNU Affero General Public License
  14
+ *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15
+ */
  16
+
  17
+#pragma once
  18
+
  19
+#include "mongo/db/btreeposition.h"
  20
+#include "mongo/db/cursor.h"
  21
+#include "mongo/db/namespace_details.h"
  22
+#include "mongo/platform/cstdint.h"
  23
+#include "mongo/platform/unordered_set.h"
  24
+
  25
+namespace mongo {
  26
+
  27
+    /**
  28
+     * An optimized btree cursor that iterates through all btree keys between a lower bound and an
  29
+     * upper bound.  The contents of the individual keys are not examined by the implementation,
  30
+     * which simply advances through the tree until reaching a predetermined end location.  The goal
  31
+     * is to optimize count operations where the keys must only be counted, not tested for matching.
  32
+     *
  33
+     * Limitations compared to a standard BtreeCursor (partial list):
  34
+     * - Only supports index constraints consisting of a single interval within an index.
  35
+     * - Only supports forward direction iteration.
  36
+     * - Does not support covered index projections.
  37
+     * - Does not support get more.
  38
+     * - Only supports V1 indexes (not V0).
  39
+     */
  40
+    class IntervalBtreeCursor : public Cursor {
  41
+    public:
  42
+
  43
+        /**
  44
+         * @return a cursor, or NULL if no cursor can be created.
  45
+         * @param namespaceDetails - Collection metadata that will not be modified.
  46
+         * @param indexDetails - Index metadata, if not a v1 index then make() will return NULL.
  47
+         * @param lowerBound - Lower bound of the key range to iterate, according to the index's
  48
+         *     native ordering.
  49
+         * @param lowerBoundInclusive - If true, the lower bound includes the endpoint.
  50
+         * @param upperBound - Upper bound of the key range to iterate.
  51
+         * @param upperBoundInclusive - If true, the upper bound includes the endpoint.
  52
+         */
  53
+        static IntervalBtreeCursor* make( /* const */ NamespaceDetails* namespaceDetails,
  54
+                                          const IndexDetails& indexDetails,
  55
+                                          const BSONObj& lowerBound,
  56
+                                          bool lowerBoundInclusive,
  57
+                                          const BSONObj& upperBound,
  58
+                                          bool upperBoundInclusive );
  59
+
  60
+        /** Virtuals from Cursor. */
  61
+
  62
+        virtual bool ok();
  63
+
  64
+        virtual Record* _current() { return currLoc().rec(); }
  65
+
  66
+        virtual BSONObj current() { return currLoc().obj(); }
  67
+
  68
+        virtual DiskLoc currLoc();
  69
+
  70
+        virtual bool advance();
  71
+
  72
+        virtual BSONObj currKey() const;
  73
+
  74
+        virtual DiskLoc refLoc() { return currLoc(); }
  75
+
  76
+        virtual void aboutToDeleteBucket( const DiskLoc& b );
  77
+
  78
+        virtual BSONObj indexKeyPattern() { return _indexDetails.keyPattern(); }
  79
+
  80
+        virtual bool supportGetMore() { return false; }
  81
+
  82
+        virtual void noteLocation();
  83
+
  84
+        virtual void checkLocation();
  85
+
  86
+        virtual bool supportYields() { return true; }
  87
+
  88
+        virtual string toString() { return "IntervalBtreeCursor"; }
  89
+
  90
+        virtual bool getsetdup( DiskLoc loc );
  91
+
  92
+        virtual bool isMultiKey() const { return _multikeyFlag; }
  93
+
  94
+        virtual bool modifiedKeys() const { return _multikeyFlag; }
  95
+
  96
+        virtual BSONObj prettyIndexBounds() const;
  97
+
  98
+        virtual long long nscanned() { return _nscanned; }
  99
+
  100
+        virtual CoveredIndexMatcher* matcher() const { return _matcher.get(); }
  101
+
  102
+        virtual void setMatcher( shared_ptr<CoveredIndexMatcher> matcher ) { _matcher = matcher; }
  103
+
  104
+    private:
  105
+        IntervalBtreeCursor( NamespaceDetails* namespaceDetails,
  106
+                             const IndexDetails& indexDetails,
  107
+                             const BSONObj& lowerBound,
  108
+                             bool lowerBoundInclusive,
  109
+                             const BSONObj& upperBound,
  110
+                             bool upperBoundInclusive );
  111
+
  112
+        void init();
  113
+
  114
+        /**
  115
+         * @return a location in the btree, determined by the parameters specified.
  116
+         * @param key - The key to search for.
  117
+         * @param afterKey - If true, return the first btree key greater than the supplied 'key'.
  118
+         *     If false, return the first key equal to the supplied 'key'.
  119
+         */
  120
+        BtreeKeyLocation locateKey( const BSONObj& key, bool afterKey );
  121
+
  122
+        /** Find the iteration end location and set _end to it. */
  123
+        void relocateEnd();
  124
+
  125
+        const NamespaceDetails& _namespaceDetails;
  126
+        const int32_t _indexNo;
  127
+        const IndexDetails& _indexDetails;
  128
+        const Ordering _ordering;
  129
+        const BSONObj _lowerBound;
  130
+        const bool _lowerBoundInclusive;
  131
+        const BSONObj _upperBound;
  132
+        const bool _upperBoundInclusive;
  133
+
  134
+        BtreeKeyLocation _curr; // Current position in the btree.
  135
+        LogicalBtreePosition _currRecoverable; // Helper to track the position of _curr if the
  136
+                                               // btree is modified during a mutex yield.
  137
+        BtreeKeyLocation _end; // Exclusive end location in the btree.
  138
+        int64_t _nscanned;
  139
+
  140
+        shared_ptr<CoveredIndexMatcher> _matcher;
  141
+        bool _multikeyFlag;
  142
+        unordered_set<uint64_t> _dups;
  143
+    };
  144
+
  145
+} // namespace mongo
26  src/mongo/db/namespace_details.h
@@ -484,14 +484,6 @@ namespace mongo {
484 484
          *
485 485
          * @param planPolicy - A policy for selecting query plans - see queryoptimizercursor.h
486 486
          *
487  
-         * @param requestMatcher - Set to true to request that the returned Cursor provide a
488  
-         * matcher().  If false, the cursor's matcher() may return NULL if the Cursor can perform
489  
-         * accurate query matching internally using a non Matcher mechanism.  One case where a
490  
-         * Matcher might be requested even though not strictly necessary to select matching
491  
-         * documents is if metadata about matches may be requested using MatchDetails.  NOTE This is
492  
-         * a hint that the Cursor use a Matcher, but the hint may be ignored.  In some cases the
493  
-         * returned cursor may not provide a matcher even if 'requestMatcher' is true.
494  
-         *
495 487
          * @param parsedQuery - Additional query parameters, as from a client query request.
496 488
          *
497 489
          * @param requireOrder - If false, the resulting cursor may return results in an order
@@ -511,15 +503,15 @@ namespace mongo {
511 503
          * - covered indexes
512 504
          * - in memory sorting
513 505
          */
514  
-        static shared_ptr<Cursor> getCursor( const char *ns, const BSONObj &query,
515  
-                                            const BSONObj &order = BSONObj(),
516  
-                                            const QueryPlanSelectionPolicy &planPolicy =
517  
-                                            QueryPlanSelectionPolicy::any(),
518  
-                                            bool requestMatcher = true,
519  
-                                            const shared_ptr<const ParsedQuery> &parsedQuery =
520  
-                                            shared_ptr<const ParsedQuery>(),
521  
-                                            bool requireOrder = true,
522  
-                                            QueryPlanSummary *singlePlanSummary = 0 );
  506
+        static shared_ptr<Cursor> getCursor( const char* ns,
  507
+                                             const BSONObj& query,
  508
+                                             const BSONObj& order = BSONObj(),
  509
+                                             const QueryPlanSelectionPolicy& planPolicy =
  510
+                                                 QueryPlanSelectionPolicy::any(),
  511
+                                             const shared_ptr<const ParsedQuery>& parsedQuery =
  512
+                                                 shared_ptr<const ParsedQuery>(),
  513
+                                             bool requireOrder = true,
  514
+                                             QueryPlanSummary* singlePlanSummary = NULL );
523 515
 
524 516
         /**
525 517
          * @return a single cursor that may work well for the given query.  A $or style query will
33  src/mongo/db/ops/count.cpp
@@ -26,6 +26,33 @@
26 26
 #include "mongo/util/elapsed_tracker.h"
27 27
 
28 28
 namespace mongo {
  29
+
  30
+    namespace {
  31
+
  32
+        /**
  33
+         * Specialized Cursor creation rules that the count operator provides to the query
  34
+         * processing system.  These rules limit the performance overhead when counting index keys
  35
+         * matching simple predicates.  See SERVER-1752.
  36
+         */
  37
+        class CountPlanPolicies : public QueryPlanSelectionPolicy {
  38
+
  39
+            virtual string name() const { return "CountPlanPolicies"; }
  40
+
  41
+            virtual bool requestMatcher() const {
  42
+                // Avoid using a Matcher when a Cursor will exactly match a query.
  43
+                return false;
  44
+            }
  45
+
  46
+            virtual bool requestIntervalCursor() const {
  47
+                // Request use of an IntervalBtreeCursor when the index bounds represent a single
  48
+                // btree interval.  This Cursor implementation is optimized for performing counts
  49
+                // between two endpoints.
  50
+                return true;
  51
+            }
  52
+
  53
+        } _countPlanPolicies;
  54
+
  55
+    }
29 56
     
30 57
     long long runCount( const char *ns, const BSONObj &cmd, string &err, int &errCode ) {
31 58
         Client::Context cx(ns);
@@ -53,11 +80,7 @@ namespace mongo {
53 80
                 NamespaceDetailsTransient::getCursor( ns,
54 81
                                                       query,
55 82
                                                       BSONObj(),
56  
-                                                      QueryPlanSelectionPolicy::any(),
57  
-                                                      // Avoid using a Matcher when a Cursor can
58  
-                                                      // exactly match the query using a
59  
-                                                      // FieldRangeVector.  See SERVER-1752.
60  
-                                                      false /* requestMatcher */ );
  83
+                                                      _countPlanPolicies );
61 84
         ClientCursor::Holder ccPointer;
62 85
         ElapsedTracker timeToStartYielding( 256, 20 );
63 86
         try {
9  src/mongo/db/ops/query.cpp
@@ -678,8 +678,13 @@ namespace mongo {
678 678
         }
679 679
         else {
680 680
             cursor =
681  
-                NamespaceDetailsTransient::getCursor( ns.c_str(), query, order, QueryPlanSelectionPolicy::any(),
682  
-                                                      true, pq_shared, false, &queryPlan );
  681
+                NamespaceDetailsTransient::getCursor( ns.c_str(),
  682
+                                                      query,
  683
+                                                      order,
  684
+                                                      QueryPlanSelectionPolicy::any(),
  685
+                                                      pq_shared,
  686
+                                                      false,
  687
+                                                      &queryPlan );
683 688
         }
684 689
         verify( cursor );
685 690
         
4  src/mongo/db/pipeline/pipeline_d.cpp
@@ -146,7 +146,7 @@ namespace mongo {
146 146
             shared_ptr<Cursor> pSortedCursor(
147 147
                 pCursor = NamespaceDetailsTransient::getCursor(
148 148
                     fullName.c_str(), *pQueryObj, *pSortObj,
149  
-                    QueryPlanSelectionPolicy::any(), true, pq));
  149
+                    QueryPlanSelectionPolicy::any(), pq));
150 150
 
151 151
             if (pSortedCursor.get()) {
152 152
                 /* success:  remove the sort from the pipeline */
@@ -165,7 +165,7 @@ namespace mongo {
165 165
             shared_ptr<Cursor> pUnsortedCursor(
166 166
                 pCursor = NamespaceDetailsTransient::getCursor(
167 167
                     fullName.c_str(), *pQueryObj, BSONObj(),
168  
-                    QueryPlanSelectionPolicy::any(), true, pq));
  168
+                    QueryPlanSelectionPolicy::any(), pq));
169 169
 
170 170
             pCursor = pUnsortedCursor;
171 171
         }
39  src/mongo/db/queryoptimizer.cpp
@@ -16,14 +16,17 @@
16 16
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 17
 */
18 18
 
19  
-#include "pch.h"
  19
+#include "mongo/pch.h"
  20
+
20 21
 #include "mongo/db/queryoptimizer.h"
21  
-#include "db.h"
22  
-#include "mongo/db/btreecursor.h"
23  
-#include "cmdline.h"
24  
-#include "../server.h"
25  
-#include "pagefault.h"
  22
+
26 23
 #include "mongo/client/dbclientinterface.h"
  24
+#include "mongo/db/btreecursor.h"
  25
+#include "mongo/db/cmdline.h"
  26
+#include "mongo/db/db.h"
  27
+#include "mongo/db/intervalbtreecursor.h"
  28
+#include "mongo/db/pagefault.h"
  29
+#include "mongo/server.h"
27 30
 
28 31
 //#define DEBUGQO(x) cout << x << endl;
29 32
 #define DEBUGQO(x)
@@ -258,7 +261,8 @@ namespace mongo {
258 261
         }
259 262
     }
260 263
 
261  
-    shared_ptr<Cursor> QueryPlan::newCursor( const DiskLoc &startLoc ) const {
  264
+    shared_ptr<Cursor> QueryPlan::newCursor( const DiskLoc& startLoc,
  265
+                                             bool requestIntervalCursor ) const {
262 266
 
263 267
         if ( _type ) {
264 268
             // hopefully safe to use original query in these contexts - don't think we can mix type with $or clause separation yet
@@ -301,6 +305,27 @@ namespace mongo {
301 305
                                                           _direction >= 0 ? 1 : -1 ) );
302 306
         }
303 307
 
  308
+        // An IntervalBtreeCursor is returned if explicitly requested AND _frv is exactly
  309
+        // represented by a single interval within the btree.
  310
+        if ( // If an interval cursor is requested and ...
  311
+             requestIntervalCursor &&
  312
+             // ... equalities come before ranges (a requirement of Optimal) and ...
  313
+             _utility == Optimal &&
  314
+             // ... the field range vector exactly represents a single interval ...
  315
+             _frv->isSingleInterval() ) {
  316
+            // ... and an interval cursor can be created ...
  317
+            shared_ptr<Cursor> ret( IntervalBtreeCursor::make( _d,
  318
+                                                               *_index,
  319
+                                                               _frv->startKey(),
  320
+                                                               _frv->startKeyInclusive(),
  321
+                                                               _frv->endKey(),
  322
+                                                               _frv->endKeyInclusive() ) );
  323
+            if ( ret ) {
  324
+                // ... then return the interval cursor.
  325
+                return ret;
  326
+            }
  327
+        }
  328
+
304 329
         return shared_ptr<Cursor>( BtreeCursor::make( _d,
305 330
                                                       *_index,
306 331
                                                       _frv,
3  src/mongo/db/queryoptimizer.h
@@ -88,7 +88,8 @@ namespace mongo {
88 88
         const string &special() const { return _special; }
89 89
                 
90 90
         /** @return a new cursor based on this QueryPlan's index and FieldRangeSet. */
91  
-        shared_ptr<Cursor> newCursor( const DiskLoc &startLoc = DiskLoc() ) const;
  91
+        shared_ptr<Cursor> newCursor( const DiskLoc& startLoc = DiskLoc(),
  92
+                                      bool requestIntervalCursor = false ) const;
92 93
         /** @return a new reverse cursor if this is an unindexed plan. */
93 94
         shared_ptr<Cursor> newReverseCursor() const;
94 95
         /** Register this plan as a winner for its QueryPattern, with specified 'nscanned'. */
24  src/mongo/db/queryoptimizercursor.h
@@ -27,8 +27,8 @@ namespace mongo {
27 27
     class CandidatePlanCharacter;
28 28
     
29 29
     /**
30  
-     * An interface for policies overriding the query optimizer's default query plan selection
31  
-     * behavior.
  30
+     * An interface for policies overriding the query optimizer's default behavior for selecting
  31
+     * query plans and creating cursors.
32 32
      */
33 33
     class QueryPlanSelectionPolicy {
34 34
     public:
@@ -38,6 +38,26 @@ namespace mongo {
38 38
         virtual bool permitOptimalIdPlan() const { return true; }
39 39
         virtual bool permitPlan( const QueryPlan &plan ) const { return true; }
40 40
         virtual BSONObj planHint( const char *ns ) const { return BSONObj(); }
  41
+
  42
+        /**
  43
+         * @return true to request that a created Cursor provide a matcher().  If false, the
  44
+         * Cursor's matcher() may be NULL if the Cursor can perform accurate query matching
  45
+         * internally using a non Matcher mechanism.  One case where a Matcher might be requested
  46
+         * even though not strictly necessary to select matching documents is if metadata about
  47
+         * matches may be requested using MatchDetails.  NOTE This is a hint that the Cursor use a
  48
+         * Matcher, but the hint may be ignored.  In some cases the Cursor may not provide
  49
+         * a Matcher even if 'requestMatcher' is true.
  50
+         */
  51
+        virtual bool requestMatcher() const { return true; }
  52
+
  53
+        /**
  54
+         * @return true to request creating an IntervalBtreeCursor rather than a BtreeCursor when
  55
+         * possible.  An IntervalBtreeCursor is optimized for counting the number of documents
  56
+         * between two endpoints in a btree.  NOTE This is a hint to create an interval cursor, but
  57
+         * the hint may be ignored.  In some cases a different cursor type may be created even if
  58
+         * 'requestIntervalCursor' is true.
  59
+         */
  60
+        virtual bool requestIntervalCursor() const { return false; }
41 61
         
42 62
         /** Allow any query plan selection, permitting the query optimizer's default behavior. */
43 63
         static const QueryPlanSelectionPolicy &any();
17  src/mongo/db/queryoptimizercursorimpl.cpp
@@ -386,13 +386,17 @@ namespace mongo {
386 386
                                          const BSONObj &query,
387 387
                                          const BSONObj &order,
388 388
                                          const QueryPlanSelectionPolicy &planPolicy,
389  
-                                         bool requestMatcher,
390 389
                                          const shared_ptr<const ParsedQuery> &parsedQuery,
391 390
                                          bool requireOrder,
392 391
                                          QueryPlanSummary *singlePlanSummary ) {
393 392
 
394  
-        CursorGenerator generator( ns, query, order, planPolicy, requestMatcher, parsedQuery,
395  
-                                   requireOrder, singlePlanSummary );
  393
+        CursorGenerator generator( ns,
  394
+                                   query,
  395
+                                   order,
  396
+                                   planPolicy,
  397
+                                   parsedQuery,
  398
+                                   requireOrder,
  399
+                                   singlePlanSummary );
396 400
         return generator.generate();
397 401
     }
398 402
     
@@ -400,7 +404,6 @@ namespace mongo {
400 404
                                      const BSONObj &query,
401 405
                                      const BSONObj &order,
402 406
                                      const QueryPlanSelectionPolicy &planPolicy,
403  
-                                     bool requestMatcher,
404 407
                                      const shared_ptr<const ParsedQuery> &parsedQuery,
405 408
                                      bool requireOrder,
406 409
                                      QueryPlanSummary *singlePlanSummary ) :
@@ -408,7 +411,6 @@ namespace mongo {
408 411
     _query( query ),
409 412
     _order( order ),
410 413
     _planPolicy( planPolicy ),
411  
-    _requestMatcher( requestMatcher ),
412 414
     _parsedQuery( parsedQuery ),
413 415
     _requireOrder( requireOrder ),
414 416
     _singlePlanSummary( singlePlanSummary ) {
@@ -486,7 +488,8 @@ namespace mongo {
486 488
         if ( _singlePlanSummary ) {
487 489
             *_singlePlanSummary = singlePlan->summary();
488 490
         }
489  
-        shared_ptr<Cursor> single = singlePlan->newCursor();
  491
+        shared_ptr<Cursor> single = singlePlan->newCursor( DiskLoc(),
  492
+                                                           _planPolicy.requestIntervalCursor() );
490 493
         if ( !_query.isEmpty() && !single->matcher() ) {
491 494
 
492 495
             // The query plan must have a matcher.  The matcher's constructor performs some aspects
@@ -494,7 +497,7 @@ namespace mongo {
494 497
             fassert( 16449, singlePlan->matcher() );
495 498
 
496 499
             if ( // If a matcher is requested or ...
497  
-                 _requestMatcher ||
  500
+                 _planPolicy.requestMatcher() ||
498 501
                  // ... the index ranges do not exactly match the query or ...
499 502
                  singlePlan->mayBeMatcherNecessary() ||
500 503
                  // ... the matcher must look at the full record ...
2  src/mongo/db/queryoptimizercursorimpl.h
@@ -257,7 +257,6 @@ namespace mongo {
257 257
                         const BSONObj &query,
258 258
                         const BSONObj &order,
259 259
                         const QueryPlanSelectionPolicy &planPolicy,
260  
-                        bool requestMatcher,
261 260
                         const shared_ptr<const ParsedQuery> &parsedQuery,
262 261
                         bool requireOrder,
263 262
                         QueryPlanSummary *singlePlanSummary );
@@ -288,7 +287,6 @@ namespace mongo {
288 287
         BSONObj _query;
289 288
         BSONObj _order;
290 289
         const QueryPlanSelectionPolicy &_planPolicy;
291  
-        bool _requestMatcher;
292 290
         shared_ptr<const ParsedQuery> _parsedQuery;
293 291
         bool _requireOrder;
294 292
         QueryPlanSummary *_singlePlanSummary;
2  src/mongo/db/queryutil-inl.h