// Matching behavior for $elemMatch applied to a top level element. // SERVER-1264 // SERVER-4180 var debuggingEnabled = false; t = db.jstests_arrayfind8; t.drop(); function debug(x) { if (debuggingEnabled) { printjson(x); } } /** Set index state for the test. */ function setIndexKey(key) { indexKey = key; indexSpec = {}; indexSpec[key] = 1; } setIndexKey('a'); /** 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);