// Check $match pipeline stage.
// - Filtering behavior equivalent to a mongo query.
// - $where and geo operators are not allowed
load('jstests/aggregation/extras/utils.js');

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

identityProjection = { _id:'$_id', a:'$a' };

/** Assert that an aggregation generated the expected error. */
function assertError( expectedCode, matchSpec ) {
    matchStage = { $match:matchSpec };
    // Check where matching is folded in to DocumentSourceCursor.
    assertErrorCode(t, [matchStage], expectedCode)
    // Check where matching is not folded in to DocumentSourceCursor.
    assertErrorCode(t, [{$project: identityProjection}, matchStage], expectedCode)
}

/** Assert that the contents of two arrays are equal, ignoring element ordering. */
function assertEqualResultsUnordered( one, two ) {
    oneStr = one.map( function( x ) { return tojson( x ); } );
    twoStr = two.map( function( x ) { return tojson( x ); } );
    oneStr.sort();
    twoStr.sort();
    assert.eq( oneStr, twoStr );
}

/** Assert that an aggregation result is as expected. */
function assertResults( expectedResults, matchSpec ) {
    findResults = t.find( matchSpec ).toArray();
    if ( expectedResults ) {
        assertEqualResultsUnordered( expectedResults, findResults );
    }
    matchStage = { $match:matchSpec };
    // Check where matching is folded in to DocumentSourceCursor.
    assertEqualResultsUnordered( findResults, t.aggregate( matchStage ).result );
    // Check where matching is not folded in to DocumentSourceCursor.
    assertEqualResultsUnordered( findResults,
                                 t.aggregate( { $project:identityProjection },
                                              matchStage ).result );    
}

// Invalid matcher syntax.
assertError( 10073, { a:{ $mod:[ 0 /* invalid */, 0 ] } } );

// $where not allowed.
assertError( 16395, { $where:'true' } );

// Geo not allowed.
assertError( 16424, { $match:{ a:{ $near:[ 0, 0 ] } } } );

// Update modifier not allowed.
if ( 0 ) { // SERVER-6650
assertError( 0, { a:1, $inc:{ b:1 } } );
}

// Aggregation expression not allowed.
if ( 0 ) { // SERVER-6650
assertError( 0, { a:1, b:{ $gt:{ $add:[ 1, 1 ] } } } );
}

function checkMatchResults( indexed ) {

    // No results.
    t.remove();
    assertResults( [], {} );
    
    t.save( { _id:0, a:1 } );
    t.save( { _id:1, a:2 } );
    t.save( { _id:2, a:3 } );

    // Empty query.
    assertResults( [ { _id:0, a:1 }, { _id:1, a:2 }, { _id:2, a:3 } ], {} );

    // Simple queries.
    assertResults( [ { _id:0, a:1 } ], { a:1 } );
    assertResults( [ { _id:1, a:2 } ], { a:2 } );
    assertResults( [ { _id:1, a:2 }, { _id:2, a:3 } ], { a:{ $gt:1 } } );
    assertResults( [ { _id:0, a:1 }, { _id:1, a:2 } ], { a:{ $lte:2 } } );
    assertResults( [ { _id:0, a:1 }, { _id:2, a:3 } ], { a:{ $in:[ 1, 3 ] } } );

    // Regular expression.
    t.remove();
    t.save( { _id:0, a:'x' } );
    t.save( { _id:1, a:'yx' } );
    assertResults( [ { _id:0, a:'x' } ], { a:/^x/ } );
    assertResults( [ { _id:0, a:'x' }, { _id:1, a:'yx' } ], { a:/x/ } );

    // Dotted field.
    t.remove();
    t.save( { _id:0, a:{ b:4 } } );
    t.save( { _id:1, a:2 } );
    assertResults( [ { _id:0, a:{ b:4 } } ], { 'a.b':4 } );

    // Value within an array.
    t.remove();
    t.save( { _id:0, a:[ 1, 2, 3 ] } );
    t.save( { _id:1, a:[ 2, 2, 3 ] } );
    t.save( { _id:2, a:[ 2, 2, 2 ] } );
    assertResults( [ { _id:0, a:[ 1, 2, 3 ] }, { _id:1, a:[ 2, 2, 3 ] } ], { a:3 } );

    // Missing, null, $exists matching.
    t.remove();
    t.save( { _id:0 } );
    t.save( { _id:1, a:null } );
    if ( 0 ) { // SERVER-6571
    t.save( { _id:2, a:undefined } );
    }
    t.save( { _id:3, a:0 } );
    assertResults( [ { _id:0 }, { _id:1, a:null } ], { a:null } );
    if ( !indexed ) { // SERVER-1160
        assertResults( null, { a:{ $in:[ undefined ] } } );
    }
    assertResults( null, { a:{ $exists:true } } );
    assertResults( null, { a:{ $exists:false } } );

    // $elemMatch
    t.remove();
    t.save( { _id:0, a:[ 1, 2 ] } );
    t.save( { _id:1, a:[ 1, 2, 3 ] } );
    assertResults( [ { _id:1, a:[ 1, 2, 3 ] } ], { a:{ $elemMatch:{ $gt:1, $mod:[ 2, 1 ] } } } );

    t.remove();
    t.save( { _id:0, a:[ { b:1 }, { c:2 } ] } );
    t.save( { _id:1, a:[ { b:1, c:2 } ] } );
    assertResults( [ { _id:1, a:[ { b:1, c:2 } ] } ], { a:{ $elemMatch:{ b:1, c:2 } } } );

    // $size
    t.remove();
    t.save( {} );
    t.save( { a:null } );
    t.save( { a:[] } );
    t.save( { a:[ 1 ] } );
    t.save( { a:[ 1, 2 ] } );
    assertResults( null, { a:{ $size:0 } } );
    assertResults( null, { a:{ $size:1 } } );
    assertResults( null, { a:{ $size:2 } } );

    // $type
    t.remove();
    t.save( {} );
    t.save( { a:null } );
    if ( 0 ) { // SERVER-6571
    t.save( { a:undefined } );
    }
    t.save( { a:NumberInt( 1 ) } );
    t.save( { a:NumberLong( 2 ) } );
    t.save( { a:66.6 } );
    t.save( { a:'abc' } );
    t.save( { a:/xyz/ } );
    t.save( { a:{ q:1 } } );
    t.save( { a:true } );
    t.save( { a:new Date() } );
    t.save( { a:new ObjectId() } );
    for( type = 1; type <= 18; ++type ) {
        if ( indexed && type == 17 ) {
            // SERVER-3304
            continue;
        }
        assertResults( null, { a:{ $type:type } } );
    }

    // $atomic does not affect results.
    t.remove();
    t.save( { _id:0, a:1 } );
    t.save( { _id:1, a:2 } );
    t.save( { _id:2, a:3 } );
    assertResults( [ { _id:0, a:1 } ], { a:1, $atomic:true } );
    assertResults( [ { _id:1, a:2 } ], { a:2, $atomic:true } );
    assertResults( [ { _id:1, a:2 }, { _id:2, a:3 } ], { a:{ $gt:1 }, $atomic:true } );
    assertResults( [ { _id:0, a:1 }, { _id:1, a:2 } ], { a:{ $lte:2 }, $atomic:true } );
    assertResults( [ { _id:0, a:1 }, { _id:2, a:3 } ], { a:{ $in:[ 1, 3 ] }, $atomic:true } );

    // $and
    assertResults( [ { _id:1, a:2 } ], { $and:[ { a:2 }, { _id:1 } ] } );
    assertResults( [], { $and:[ { a:1 }, { _id:1 } ] } );
    assertResults( [ { _id:1, a:2 }, { _id:2, a:3 } ],
                   { $and:[ { $or:[ { _id:1 }, { a:3 } ] }, { $or:[ { _id:2 }, { a:2 } ] } ] } );

    // $or
    assertResults( [ { _id:0, a:1 }, { _id:2, a:3 } ], { $or:[ { _id:0 }, { a:3 } ] } );
}

checkMatchResults( false );
t.ensureIndex( { a:1 } );
checkMatchResults( true );
t.ensureIndex( { 'a.b':1 } );
t.ensureIndex( { 'a.c':1 } );
checkMatchResults( true );
