1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
// Index bounds and 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 index bounds for a query. */
function assertBounds( expectedBounds, query, context ) {
bounds = indexBounds( query );
debug( bounds );
assert.eq( expectedBounds, bounds, 'unexpected bounds in ' + context );
}
/** 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 index bounds 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 singleKeyBounds - expected single key index bounds for the elem match query generated from
* @param subQuery
* @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
* @param multiKeyBounds - expected multi key index bounds for the elem match query generated from
* @param subQuery. If not provided, singleKeyBounds will be expected.
*/
function checkBoundsAndMatch( subQuery, singleKeyBounds, bothMatch, elemMatch,
nonElemMatch, additionalConstraints, multiKeyBounds ) {
t.drop();
multiKeyBounds = multiKeyBounds || singleKeyBounds;
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' );
assertBounds( singleKeyBounds, 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' );
assertBounds( multiKeyBounds, elemMatchQuery, 'multikey index' );
}
maxNumber = 1.7976931348623157e+308;
// Basic test.
checkBoundsAndMatch( { $gt:4 }, [[ 4, maxNumber ]], [ 5 ] );
// Multiple constraints within a $elemMatch clause.
checkBoundsAndMatch( { $gt:4, $lt:6 }, [[ 4, 6 ]], [ 5 ], null, [ 3, 7 ] );
checkBoundsAndMatch( { $gt:4, $not:{ $gte:6 } }, [[ 4, 6 ]], [ 5 ] );
checkBoundsAndMatch( { $gt:4, $not:{ $ne:6 } }, [[ 6, 6 ]], [ 6 ] );
checkBoundsAndMatch( { $gte:5, $lte:5 }, [[ 5, 5 ]], [ 5 ], null, [ 4, 6 ] );
checkBoundsAndMatch( { $in:[ 4, 6 ], $gt:5 }, [[ 6, 6 ]], [ 6 ], null, [ 4, 7 ] );
checkBoundsAndMatch( { $regex:'^a' }, [[ 'a', 'b' ], [ /^a/, /^a/ ]], [ 'a' ] );
checkBoundsAndMatch( { $regex:'^a', $in:['b'] }, undefined ); // ?? undefined
// Some constraints within a $elemMatch clause and other constraints outside of it.
checkBoundsAndMatch( { $gt:4 }, [[ 4, 6 ]], [ 5 ], null, null, { a:{ $lt:6 } },
[[ 4, maxNumber ]] );
checkBoundsAndMatch( { $gte:5 }, [[ 5, 5 ]], [ 5 ], null, null, { a:{ $lte:5 } },
[[ 5, maxNumber ]] );
checkBoundsAndMatch( { $in:[ 4, 6 ] }, [[ 6, 6 ]], [ 6 ], null, null, { a:{ $gt:5 } },
[[ 4, 4 ], [ 6, 6 ]] );
// Constraints in different $elemMatch clauses.
checkBoundsAndMatch( { $gt:4 }, [[ 4, 6 ]], [ 5 ], null, null, { a:{ $elemMatch:{ $lt:6 } } },
[[ 4, maxNumber ]] );
checkBoundsAndMatch( { $gt:4 }, [[ 4, maxNumber ]], [ 3, 7 ], null, null,
{ a:{ $elemMatch:{ $lt:6 } } }, [[ 4, maxNumber ]] );
checkBoundsAndMatch( { $gte:5 }, [[ 5, 5 ]], [ 5 ], null, null, { a:{ $elemMatch:{ $lte:5 } } },
[[ 5, maxNumber ]] );
checkBoundsAndMatch( { $in:[ 4, 6 ] }, [[ 6, 6 ]], [ 6 ], null, null,
{ a:{ $elemMatch:{ $gt:5 } } }, [[ 4, 4 ], [ 6, 6 ]] );
// TODO SERVER-1264
if ( 0 ) {
checkBoundsAndMatch( { $elemMatch:{ $in:[ 5 ] } }, [[ {$minElement:1}, {$maxElement:1} ]], null,
[[ 5 ]], [ 5 ], null, [[ {$minElement:1}, {$maxElement:1} ]] );
}
// Index bounds are not computed for $elemMatch nested within a $elemMatch applied to a top level
// element (descriptive, not normative test). The reason is that { a:[ [ { b:1 } ] ] } matches a
// query as in the example, but double nested arrays are not indexed as multikeys.
setIndexKey( 'a.b' );
checkBoundsAndMatch( { $elemMatch:{ b:{ $gte:1, $lte:1 } } },
[[ {$minElement:1}, {$maxElement:1} ]], null, [[ { b:1 } ]], [ { b:1 } ],
null, [[ {$minElement:1}, {$maxElement:1} ]] );
checkBoundsAndMatch( { $elemMatch:{ b:{ $gte:1, $lte:1 } } },
[[ {$minElement:1}, {$maxElement:1} ]], null, [[ { b:[ 0, 2 ] } ]],
[ { b:[ 0, 2 ] } ], null, [[ {$minElement:1}, {$maxElement:1} ]] );
// Constraints for a top level (SERVER-1264 style) $elemMatch nested within a non top level
// $elemMatch.
checkBoundsAndMatch( { b:{ $elemMatch:{ $gte:1, $lte:1 } } }, [[ 1, 1 ]], [ { b:[ 1 ] } ] );
checkBoundsAndMatch( { b:{ $elemMatch:{ $gte:1, $lte:4 } } }, [[ 1, 4 ]], [ { b:[ 1 ] } ] );
checkBoundsAndMatch( { b:{ $elemMatch:{ $gte:1, $lte:4 } } }, [[ 2, 2 ]], [ { b:[ 2 ] } ], null,
null, { 'a.b':{ $in:[ 2, 5 ] } }, [[ 1, 4 ]] );
checkBoundsAndMatch( { b:{ $elemMatch:{ $in:[ 1, 2 ] }, $in:[ 2, 3 ] } }, [[ 2, 2 ]],
[ { b:[ 2 ] } ], null, [ { b:[ 1 ] }, { b:[ 3 ] } ], null,
[[ 1, 1 ], [ 2, 2 ]] );
|