// Tests where the QueryOptimizerCursor enters takeover mode during a query rather than a get more.

t = db.jstests_finda;
t.drop();

numDocs = 200;

function clearQueryPlanCache() {
    t.ensureIndex( { c:1 } );
    t.dropIndex( { c:1 } );
}

function assertAllFound( matches ) {
//    printjson( matches );
    found = new Array( numDocs );
    for( i = 0; i < numDocs; ++i ) {
        found[ i ] = false;
    }
    for( i in matches ) {
        m = matches[ i ];
        found[ m._id ] = true;
    }
    for( i = 0; i < numDocs; ++i ) {
        assert( found[ i ], i );
    }
}

function makeCursor( query, projection, sort, batchSize, returnKey ) {
    cursor = t.find( query, projection );
    if ( sort ) {
        cursor.sort( sort );
    }
    if ( batchSize ) {
        cursor.batchSize( batchSize );
    }
    if ( returnKey ) {
        cursor._addSpecial( "$returnKey", true );
    }
    return cursor;
}

function checkCursorWithBatchSizeProjection( query, projection, sort, batchSize,
                                            expectedLeftInBatch ) {
    clearQueryPlanCache();
    cursor = makeCursor( query, projection, sort, batchSize );
    assert.eq( expectedLeftInBatch, cursor.objsLeftInBatch() );
    assertAllFound( cursor.toArray() );
}

function checkCursorWithBatchSize( query, sort, batchSize, expectedLeftInBatch ) {
    checkCursorWithBatchSizeProjection( query, {}, sort, batchSize, expectedLeftInBatch );
    checkCursorWithBatchSizeProjection( query, { a:1, _id:1 }, sort, batchSize,
                                       expectedLeftInBatch );
    // In the cases tested, when expectedLeftInBatch is high enough takeover will occur during
    // the query operation rather than getMore and the last few matches should properly return keys
    // from the a,_id index.
    clearQueryPlanCache();
    if ( expectedLeftInBatch > 110 ) {
        cursor = makeCursor( query, {}, sort, batchSize, true );
        lastNonAIndexResult = -1;
        for( i = 0; i < expectedLeftInBatch; ++i ) {
            next = cursor.next();
            // Identify the query plan used by checking the fields of a returnKey query.
            if ( !friendlyEqual( [ 'a', '_id' ], Object.keySet( next ) ) ) {
                lastNonAIndexResult = i;
            }
        }
        // The last results should come from the a,_id index.
        assert.lt( lastNonAIndexResult, expectedLeftInBatch - 5 );
    }
}

function queryWithPlanTypes( withDups ) {
    t.drop();
    for( i = 1; i < numDocs; ++i ) {
        t.save( { _id:i, a:i, b:0 } );
    }
    if ( withDups ) {
        t.save( { _id:0, a:[ 0, numDocs ], b:0 } ); // Add a dup on a:1 index.
    }
    else {
        t.save( { _id:0, a:0, b:0 } );
    }
    t.ensureIndex( { a:1, _id:1 } ); // Include _id for a covered index projection.

    // All plans in order.
    checkCursorWithBatchSize( { a:{ $gte:0 } }, null, 150, 150 );

    // All plans out of order.
    checkCursorWithBatchSize( { a:{ $gte:0 } }, { c:1 }, null, 200 );

    // Some plans in order, some out of order.
    checkCursorWithBatchSize( { a:{ $gte:0 }, b:0 }, { a:1 }, 150, 150 );
    checkCursorWithBatchSize( { a:{ $gte:0 }, b:0 }, { a:1 }, null, 101 );
}

queryWithPlanTypes( false );
queryWithPlanTypes( true );
