1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
// Tests for an optimization where limits are distributed to individual key intervals. SERVER-5063
t = db.jstests_sortk;
t.drop();
function resetCollection() {
t.drop();
t.save( { a:1, b:1 } );
t.save( { a:1, b:2 } );
t.save( { a:1, b:3 } );
t.save( { a:2, b:4 } );
t.save( { a:2, b:5 } );
t.save( { a:2, b:0 } );
}
resetCollection();
t.ensureIndex( { a:1, b:1 } );
function simpleQuery( extraFields, sort, hint ) {
query = { a:{ $in:[ 1, 2 ] } };
Object.extend( query, extraFields );
sort = sort || { b:1 };
hint = hint || { a:1, b:1 };
return t.find( query ).sort( sort ).hint( hint );
}
function simpleQueryWithLimit( limit ) {
return simpleQuery().limit( limit );
}
// The limit is -1.
assert.eq( 0, simpleQueryWithLimit( -1 )[ 0 ].b );
// Nscanned 3 == 2 results + 1 interval skip.
assert.eq( 3, simpleQueryWithLimit( -1 ).explain().nscanned );
// The limit is -2.
assert.eq( 0, simpleQueryWithLimit( -2 )[ 0 ].b );
assert.eq( 1, simpleQueryWithLimit( -2 )[ 1 ].b );
assert.eq( 5, simpleQueryWithLimit( -2 ).explain().nscanned );
// The batchSize is 2.
assert.eq( 2, simpleQuery().batchSize( 2 ).itcount() );
// No limit optimization is performed.
assert.eq( 6, simpleQuery().batchSize( 2 ).explain().nscanned );
// A skip is applied.
assert.eq( 1, simpleQueryWithLimit( -1 ).skip( 1 )[ 0 ].b );
assert.eq( 5, simpleQueryWithLimit( -1 ).skip( 1 ).explain().nscanned );
// No limit is applied.
assert.eq( 6, simpleQueryWithLimit( 0 ).itcount() );
assert.eq( 6, simpleQueryWithLimit( 0 ).explain().nscanned );
assert.eq( 5, simpleQueryWithLimit( 0 ).skip( 1 ).itcount() );
assert.eq( 6, simpleQueryWithLimit( 0 ).skip( 1 ).explain().nscanned );
// The query has additional constriants, preventing limit optimization.
assert.eq( 2, simpleQuery( { $where:'this.b>=2' } ).limit( -1 )[ 0 ].b );
assert.eq( 6, simpleQuery( { $where:'this.b>=2' } ).limit( -1 ).explain().nscanned );
// The sort order is the reverse of the index order.
assert.eq( 5, simpleQuery( {}, { b:-1 } ).limit( -1 )[ 0 ].b );
assert.eq( 6, simpleQuery( {}, { b:-1 } ).limit( -1 ).explain().nscanned );
// The sort order is the reverse of the index order on a constrained field.
assert.eq( 0, simpleQuery( {}, { a:-1, b:1 } ).limit( -1 )[ 0 ].b );
assert.eq( 3, simpleQuery( {}, { a:-1, b:1 } ).limit( -1 ).explain().nscanned );
// No sort is requested.
assert.eq( 1, simpleQuery( {}, {} ).limit( -1 ).explain().nscanned );
// Without a hint, multiple cursors are attempted.
assert.eq( 0, t.find( { a:{ $in:[ 1, 2 ] } } ).sort( { b:1 } ).limit( -1 )[ 0 ].b );
explain = t.find( { a:{ $in:[ 1, 2 ] } } ).sort( { b:1 } ).limit( -1 ).explain( true );
assert.eq( 'BtreeCursor a_1_b_1 multi', explain.cursor );
assert.eq( 1, explain.n );
assert.eq( 'BtreeCursor a_1_b_1 multi', explain.allPlans[ 0 ].cursor );
assert.eq( 2, explain.allPlans[ 0 ].n );
assert.eq( 3, explain.allPlans[ 0 ].nscanned );
// The expected first result now comes from the first interval.
t.remove( { b:0 } );
assert.eq( 1, simpleQueryWithLimit( -1 )[ 0 ].b );
assert.eq( 3, simpleQueryWithLimit( -1 ).explain().nscanned );
// With three intervals.
function inThreeIntervalQueryWithLimit( limit ) {
return t.find( { a:{ $in: [ 1, 2, 3 ] } } ).sort( { b:1 } ).hint( { a:1, b:1 } ).limit( limit );
}
assert.eq( 1, inThreeIntervalQueryWithLimit( -1 )[ 0 ].b );
assert.eq( 4, inThreeIntervalQueryWithLimit( -1 ).explain().nscanned );
assert.eq( 1, inThreeIntervalQueryWithLimit( -2 )[ 0 ].b );
assert.eq( 2, inThreeIntervalQueryWithLimit( -2 )[ 1 ].b );
assert.eq( 5, inThreeIntervalQueryWithLimit( -2 ).explain().nscanned );
t.save( { a:3, b:0 } );
assert.eq( 0, inThreeIntervalQueryWithLimit( -1 )[ 0 ].b );
assert.eq( 5, inThreeIntervalQueryWithLimit( -1 ).explain().nscanned );
assert.eq( 0, inThreeIntervalQueryWithLimit( -2 )[ 0 ].b );
assert.eq( 1, inThreeIntervalQueryWithLimit( -2 )[ 1 ].b );
assert.eq( 6, inThreeIntervalQueryWithLimit( -2 ).explain().nscanned );
// The index is multikey.
t.remove();
t.save( { a:1, b:[ 0, 1, 2 ] } );
t.save( { a:2, b:[ 0, 1, 2 ] } );
t.save( { a:1, b:5 } );
assert.eq( 3, simpleQueryWithLimit( -3 ).itcount() );
// The index ordering is reversed.
resetCollection();
t.ensureIndex( { a:1, b:-1 } );
// The sort order is consistent with the index order.
assert.eq( 5, simpleQuery( {}, { b:-1 }, { a:1, b:-1 } ).limit( -1 )[ 0 ].b );
assert.eq( 3, simpleQuery( {}, { b:-1 }, { a:1, b:-1 } ).limit( -1 ).explain().nscanned );
// The sort order is the reverse of the index order.
assert.eq( 0, simpleQuery( {}, { b:1 }, { a:1, b:-1 } ).limit( -1 )[ 0 ].b );
assert.eq( 6, simpleQuery( {}, { b:1 }, { a:1, b:-1 } ).limit( -1 ).explain().nscanned );
// An equality constraint precedes the $in constraint.
t.drop();
t.ensureIndex( { a:1, b:1, c:1 } );
t.save( { a:0, b:0, c:-1 } );
t.save( { a:0, b:2, c:1 } );
t.save( { a:1, b:1, c:1 } );
t.save( { a:1, b:1, c:2 } );
t.save( { a:1, b:1, c:3 } );
t.save( { a:1, b:2, c:4 } );
t.save( { a:1, b:2, c:5 } );
t.save( { a:1, b:2, c:0 } );
function eqInQueryWithLimit( limit ) {
return t.find( { a:1, b:{ $in:[ 1, 2 ] } } ).sort( { c: 1 } ).hint( { a:1, b:1, c:1 } ).
limit( limit );
}
function andEqInQueryWithLimit( limit ) {
return t.find( { $and:[ { a:1 }, { b:{ $in:[ 1, 2 ] } } ] } ).sort( { c: 1 } ).
hint( { a:1, b:1, c:1 } ).limit( limit );
}
// The limit is -1.
assert.eq( 0, eqInQueryWithLimit( -1 )[ 0 ].c );
assert.eq( 3, eqInQueryWithLimit( -1 ).explain().nscanned );
assert.eq( 0, andEqInQueryWithLimit( -1 )[ 0 ].c );
assert.eq( 3, andEqInQueryWithLimit( -1 ).explain().nscanned );
// The limit is -2.
assert.eq( 0, eqInQueryWithLimit( -2 )[ 0 ].c );
assert.eq( 1, eqInQueryWithLimit( -2 )[ 1 ].c );
assert.eq( 5, eqInQueryWithLimit( -2 ).explain().nscanned );
assert.eq( 0, andEqInQueryWithLimit( -2 )[ 0 ].c );
assert.eq( 1, andEqInQueryWithLimit( -2 )[ 1 ].c );
assert.eq( 5, andEqInQueryWithLimit( -2 ).explain().nscanned );
function inQueryWithLimit( limit, sort ) {
sort = sort || { b:1 };
return t.find( { a:{ $in:[ 0, 1 ] } } ).sort( sort ).hint( { a:1, b:1, c:1 } ).limit( limit );
}
// The index has two suffix fields unconstrained by the query.
assert.eq( 0, inQueryWithLimit( -1 )[ 0 ].b );
assert.eq( 3, inQueryWithLimit( -1 ).explain().nscanned );
// The index has two ordered suffix fields unconstrained by the query.
assert.eq( 0, inQueryWithLimit( -1, { b:1, c:1 } )[ 0 ].b );
assert.eq( 3, inQueryWithLimit( -1, { b:1, c:1 } ).explain().nscanned );
// The index has two ordered suffix fields unconstrained by the query and the limit is -2.
assert.eq( 0, inQueryWithLimit( -2, { b:1, c:1 } )[ 0 ].b );
assert.eq( 1, inQueryWithLimit( -2, { b:1, c:1 } )[ 1 ].b );
assert.eq( 4, inQueryWithLimit( -2, { b:1, c:1 } ).explain().nscanned );
|