summaryrefslogtreecommitdiff
path: root/jstests/existsa.js
blob: 9521bb51f301e4c89188288363ff2d8486352f64 (plain)
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
// Sparse indexes are disallowed for $exists:false queries.  SERVER-3918

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

t.save( {} );
t.save( { a:1 } );
t.save( { a:{ x:1 }, b:1 } );

/** Configure testing of an index { <indexKeyField>:1 }. */
function setIndex( _indexKeyField ) {
    indexKeyField = _indexKeyField;
    indexKeySpec = {};
    indexKeySpec[ indexKeyField ] = 1;
    t.ensureIndex( indexKeySpec, { sparse:true } );
    indexCursorName = 'BtreeCursor ' + indexKeyField + '_1';
}
setIndex( 'a' );

/** Validate the prefix of 'str'. */
function assertPrefix( prefix, str ) {
    assert.eq( prefix, str.substring( 0, prefix.length ) );
}

/** @return count when hinting the index to use. */
function hintedCount( query ) {
    assertPrefix( indexCursorName, t.find( query ).hint( indexKeySpec ).explain().cursor );
    return t.find( query ).hint( indexKeySpec ).itcount();
}

/** The query field does not exist and the sparse index is not used without a hint. */
function assertMissing( query, expectedMissing, expectedIndexedMissing ) {
    expectedMissing = expectedMissing || 1;
    expectedIndexedMissing = expectedIndexedMissing || 0;
    assert.eq( expectedMissing, t.count( query ) );
    assert.eq( 'BasicCursor', t.find( query ).explain().cursor );
    assert.eq( expectedIndexedMissing, hintedCount( query ) );
}

/** The query field exists and the sparse index is used without a hint. */
function assertExists( query, expectedExists ) {
    expectedExists = expectedExists || 2;
    assert.eq( expectedExists, t.count( query ) );
    assert.eq( 'BasicCursor', t.find( query ).explain().cursor );
    // An $exists:true predicate generates no index filters.  Add another predicate on the index key
    // to trigger use of the index.
    andClause = {}
    andClause[ indexKeyField ] = { $ne:null };
    Object.extend( query, { $and:[ andClause ] } );
    assert.eq( expectedExists, t.count( query ) );
    assertPrefix( indexCursorName, t.find( query ).explain().cursor );
    assert.eq( expectedExists, hintedCount( query ) );
}

/** The query field exists and the sparse index is not used without a hint. */
function assertExistsUnindexed( query, expectedExists ) {
    expectedExists = expectedExists || 2;
    assert.eq( expectedExists, t.count( query ) );
    assert.eq( 'BasicCursor', t.find( query ).explain().cursor );
    // Even with another predicate on the index key, the sparse index is disallowed.
    andClause = {}
    andClause[ indexKeyField ] = { $ne:null };
    Object.extend( query, { $and:[ andClause ] } );
    assert.eq( expectedExists, t.count( query ) );
    assert.eq( 'BasicCursor', t.find( query ).explain().cursor );
    assert.eq( expectedExists, hintedCount( query ) );
}

// $exists:false queries match the proper number of documents and disallow the sparse index.
assertMissing( { a:{ $exists:false } } );
assertMissing( { a:{ $not:{ $exists:true } } } );
assertMissing( { $and:[ { a:{ $exists:false } } ] } );
assertMissing( { $or:[ { a:{ $exists:false } } ] } );
assertMissing( { $nor:[ { a:{ $exists:true } } ] } );
assertMissing( { 'a.x':{ $exists:false } }, 2, 1 );

// Currently a sparse index is disallowed even if the $exists:false query is on a different field.
assertMissing( { b:{ $exists:false } }, 2, 1 );
assertMissing( { b:{ $exists:false }, a:{ $ne:6 } }, 2, 1 );

// Top level $exists:true queries match the proper number of documents and allow the sparse index.
assertExists( { a:{ $exists:true } } );
assertExists( { 'a.x':{ $exists:true } }, 1 );
assertExists( { b:{ $exists:true } }, 1 );
assertExists( { a:{ $not:{ $exists:false } } } );

// Nested $exists queries match the proper number of documents and disallow the sparse index.
assertExistsUnindexed( { $nor:[ { a:{ $exists:false } } ] } );
assertExistsUnindexed( { $nor:[ { 'a.x':{ $exists:false } } ] }, 1 );

// Nested $exists queries disallow the sparse index in some cases where it is not strictly
// necessary to do so.  (Descriptive tests.)
assertExistsUnindexed( { $nor:[ { b:{ $exists:false } } ] }, 1 );  // Unindexed field.
assertExistsUnindexed( { $or:[ { a:{ $exists:true } } ] } );  // $exists:true not $exists:false.

// Behavior is similar with $elemMatch.
t.drop();
t.save( { a:[ {} ] } );
t.save( { a:[ { b:1 } ] } );
t.save( { a:[ { b:1 } ] } );
setIndex( 'a.b' );

assertMissing( { a:{ $elemMatch:{ b:{ $exists:false } } } } );
// A $elemMatch predicate is treated as nested, and the index is disallowed even for $exists:true.
assertExistsUnindexed( { a:{ $elemMatch:{ b:{ $exists:true } } } } );

// A non sparse index will not be disallowed.
t.drop();
t.save( {} );
t.ensureIndex( { a:1 } );
assert.eq( 1, t.find( { a:{ $exists:false } } ).itcount() );
assert.eq( 'BtreeCursor a_1', t.find( { a:{ $exists:false } } ).explain().cursor );