// 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 ).toArray() ); // Check where matching is not folded in to DocumentSourceCursor. assertEqualResultsUnordered( findResults, t.aggregate( { $project:identityProjection }, matchStage ).toArray() ); } // Invalid matcher syntax. assertError( 16810, { 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 } ); 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 ) { 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 );