// Matching behavior for $elemMatch applied to a top level element. // SERVER-1264 // SERVER-4180 t = db.jstests_arrayfind8; t.drop(); function debug( x ) { if ( debuggingEnabled = false ) { printjson( x ); } } /** Set index state for the test. */ function setIndexKey( key ) { indexKey = key; indexSpec = {}; indexSpec[ key ] = 1; } setIndexKey( 'a' ); function indexBounds( query ) { debug( query ); debug( t.find( query ).hint( indexSpec ).explain() ); return t.find( query ).hint( indexSpec ).explain().indexBounds[ indexKey ]; } /** Check that the query results match the documents in the 'expected' array. */ function assertResults( expected, query, context ) { debug( query ); assert.eq( expected.length, t.count( query ), 'unexpected count in ' + context ); results = t.find( query ).toArray(); for( i in results ) { found = false; for( j in expected ) { if ( friendlyEqual( expected[ j ], results[ i ].a ) ) { found = true; } } assert( found, 'unexpected result ' + results[ i ] + ' in ' + context ); } } /** * Check matching for different query types. * @param bothMatch - document matched by both standardQuery and elemMatchQuery * @param elemMatch - document matched by elemMatchQuery but not standardQuery * @param notElemMatch - document matched by standardQuery but not elemMatchQuery */ function checkMatch( bothMatch, elemMatch, nonElemMatch, standardQuery, elemMatchQuery, context ) { function mayPush( arr, elt ) { if ( elt ) { arr.push( elt ); } } expectedStandardQueryResults = []; mayPush( expectedStandardQueryResults, bothMatch ); mayPush( expectedStandardQueryResults, nonElemMatch ); assertResults( expectedStandardQueryResults, standardQuery, context + ' standard query' ); expectedElemMatchQueryResults = []; mayPush( expectedElemMatchQueryResults, bothMatch ); mayPush( expectedElemMatchQueryResults, elemMatch ); assertResults( expectedElemMatchQueryResults, elemMatchQuery, context + ' elemMatch query' ); } /** * Check matching and for different query types. * @param subQuery - part of a query, to be provided as is for a standard query and within a * $elemMatch clause for a $elemMatch query * @param bothMatch - document matched by both standardQuery and elemMatchQuery * @param elemMatch - document matched by elemMatchQuery but not standardQuery * @param notElemMatch - document matched by standardQuery but not elemMatchQuery * @param additionalConstraints - additional query parameters not generated from @param subQuery */ function checkQuery( subQuery, bothMatch, elemMatch, nonElemMatch, additionalConstraints ) { t.drop(); additionalConstraints = additionalConstraints || {}; // Construct standard and elemMatch queries from subQuery. firstSubQueryKey = Object.keySet( subQuery )[ 0 ]; if ( firstSubQueryKey[ 0 ] == '$' ) { standardQuery = { $and:[ { a:subQuery }, additionalConstraints ] }; } else { // If the subQuery contains a field rather than operators, append to the 'a' field. modifiedSubQuery = {}; modifiedSubQuery[ 'a.' + firstSubQueryKey ] = subQuery[ firstSubQueryKey ]; standardQuery = { $and:[ modifiedSubQuery, additionalConstraints ] }; } elemMatchQuery = { $and:[ { a:{ $elemMatch:subQuery } }, additionalConstraints ] }; debug( elemMatchQuery ); function maySave( aValue ) { if ( aValue ) { debug( { a:aValue } ); t.save( { a:aValue } ); } } // Save all documents and check matching without indexes. maySave( bothMatch ); maySave( elemMatch ); maySave( nonElemMatch ); checkMatch( bothMatch, elemMatch, nonElemMatch, standardQuery, elemMatchQuery, 'unindexed' ); // Check matching and index bounds for a single key index. t.drop(); maySave( bothMatch ); maySave( elemMatch ); // The nonElemMatch document is not tested here, as it will often make the index multikey. t.ensureIndex( indexSpec ); checkMatch( bothMatch, elemMatch, null, standardQuery, elemMatchQuery, 'single key index' ); // Check matching and index bounds for a multikey index. // Now the nonElemMatch document is tested. maySave( nonElemMatch ); // Force the index to be multikey. t.save( { a:[ -1, -2 ] } ); t.save( { a:{ b:[ -1, -2 ] } } ); checkMatch( bothMatch, elemMatch, nonElemMatch, standardQuery, elemMatchQuery, 'multikey index' ); } maxNumber = Infinity; // Basic test. checkQuery( { $gt:4 }, [ 5 ] ); // Multiple constraints within a $elemMatch clause. checkQuery( { $gt:4, $lt:6 }, [ 5 ], null, [ 3, 7 ] ); checkQuery( { $gt:4, $not:{ $gte:6 } }, [ 5 ] ); checkQuery( { $gt:4, $not:{ $ne:6 } }, [ 6 ] ); checkQuery( { $gte:5, $lte:5 }, [ 5 ], null, [ 4, 6 ] ); checkQuery( { $in:[ 4, 6 ], $gt:5 }, [ 6 ], null, [ 4, 7 ] ); checkQuery( { $regex:'^a' }, [ 'a' ] ); // Some constraints within a $elemMatch clause and other constraints outside of it. checkQuery( { $gt:4 }, [ 5 ], null, null, { a:{ $lt:6 } } ); checkQuery( { $gte:5 }, [ 5 ], null, null, { a:{ $lte:5 } } ); checkQuery( { $in:[ 4, 6 ] }, [ 6 ], null, null, { a:{ $gt:5 } } ); // Constraints in different $elemMatch clauses. checkQuery( { $gt:4 }, [ 5 ], null, null, { a:{ $elemMatch:{ $lt:6 } } } ); checkQuery( { $gt:4 }, [ 3, 7 ], null, null, { a:{ $elemMatch:{ $lt:6 } } } ); checkQuery( { $gte:5 }, [ 5 ], null, null, { a:{ $elemMatch:{ $lte:5 } } } ); checkQuery( { $in:[ 4, 6 ] }, [ 6 ], null, null, { a:{ $elemMatch:{ $gt:5 } } } ); // TODO SERVER-1264 if ( 0 ) { checkQuery( { $elemMatch:{ $in:[ 5 ] } }, null, [[ 5 ]], [ 5 ], null ); } setIndexKey( 'a.b' ); checkQuery( { $elemMatch:{ b:{ $gte:1, $lte:1 } } }, null, [[ { b:1 } ]], [ { b:1 } ], null ); checkQuery( { $elemMatch:{ b:{ $gte:1, $lte:1 } } }, null, [[ { b:[ 0, 2 ] } ]], [ { b:[ 0, 2 ] } ], null ); // Constraints for a top level (SERVER-1264 style) $elemMatch nested within a non top level // $elemMatch. checkQuery( { b:{ $elemMatch:{ $gte:1, $lte:1 } } }, [ { b:[ 1 ] } ] ); checkQuery( { b:{ $elemMatch:{ $gte:1, $lte:4 } } }, [ { b:[ 1 ] } ] ); checkQuery( { b:{ $elemMatch:{ $gte:1, $lte:4 } } }, [ { b:[ 2 ] } ], null, null, { 'a.b':{ $in:[ 2, 5 ] } } ); checkQuery( { b:{ $elemMatch:{ $in:[ 1, 2 ] }, $in:[ 2, 3 ] } }, [ { b:[ 2 ] } ], null, [ { b:[ 1 ] }, { b:[ 3 ] } ], null );