// 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 );
