summaryrefslogtreecommitdiff
path: root/jstests/index13.js
blob: 6dbc317e59fb94e68b8623ea01ab763bfde04d99 (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
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// Top level match fields within an $elemMatch clause may constrain multiple subfields from a
// compound multikey index.  SERVER-3104
//
// Given a multikey index { 'a.b':1, 'a.c':1 } and query { 'a.b':3, 'a.c':3 } only the index field
// 'a.b' is constrained to the range [3, 3], while the index field 'a.c' is just constrained
// to be within minkey and maxkey.  This implementation ensures that the document
// { a:[ { b:3 }, { c:3 } ] }, which generates index keys { 'a.b':3, 'a.c':null } and
// { 'a.b':null and 'a.c':3 } will be retrieved for the query.  (See SERVER-958 for more
// information.)
//
// If the query is instead { a:{ $elemMatch:{ b:3, c:3 } } } then the document
// { a:[ { b:3 }, { c:3 } ] } does not match.  Until SERVER-3104 was implemented, the index
// constraints would be [3,3] on the 'a.b' field and [minkey,maxkey] on the 'a.c' field, the same as
// for the non $elemMatch query in the previous paragraph.  With the SERVER-3104 implementation,
// constraints on two fields within a $elemMatch parent can both be applied to an index.  Due to the
// SERVER-3104 implementation, the index constraints become [3,3] on the 'a.b' field _and_ [3,3] on
// the 'a.c' field.

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

function assertConsistentResults( query ) {
    assert.eq( t.find( query ).hint( { $natural:1 } ).sort( { _id:1 } ).toArray(),
               t.find( query ).hint( index ).sort( { _id:1 } ).toArray() );
}

function assertResultsAndIndexBounds( expectedBounds, query ) {
    explain = t.find( query ).hint( index ).explain();
    // printjson( explain ); // debug
    assert.eq( expectedBounds, explain.indexBounds );
    assertConsistentResults( query );
}

MIN = { $minElement:1 };
MAX = { $maxElement:1 };


// Cases with single dotted index fied names.
index = { 'a.b':1, 'a.c':1 };
t.ensureIndex( index );
t.save( { a:[ { b:1 }, { c:1 } ] } );
t.save( { a:[ { b:1, c:1 } ] } );
assert.eq( 2, t.count() );
// Without $elemMatch.
assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ MIN, MAX ]] },
                             { 'a.b':1, 'a.c':1 } );
// QUERY MIGRATION
// Bounds with elem match
// With $elemMatch.
//assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ 1, 1 ]] },
//                             { a:{ $elemMatch:{ b:1, c:1 } } } );

// Without shared $elemMatch.
assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ MIN, MAX ]] },
                             { 'a.b':1, a:{ $elemMatch:{ c:1 } } } );
// Two different $elemMatch expressions.
assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ MIN, MAX ]] },
                             { $and:[ { a:{ $elemMatch:{ b:1 } } },
                                      { a:{ $elemMatch:{ c:1 } } } ] } );


// Cases relating to parse order and inclusion of intersected ranges.
assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ MIN, MAX ]] },
                             { 'a.b':1, a:{ $elemMatch:{ b:{ $gt:0 }, c:1 } } } );
assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ MIN, MAX ]] },
                             { 'a.b':1, a:{ $elemMatch:{ b:{ $gt:0 }, c:1 } } } );
assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ MIN, MAX ]] },
                             { a:{ $elemMatch:{ b:1, c:1 } }, 'a.b':1 } );

// QUERY MIGRATION
// Bounds with elem match
//assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ 1, 1 ]] },
//                             { 'a.b':1, a:{ $elemMatch:{ b:1, c:1 } } } );
//assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ 1, 1 ]] },
//                             { 'a.c':1, a:{ $elemMatch:{ b:1, c:1 } } } );
//assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ 1, 1 ]] },
//                             { a:{ $elemMatch:{ b:1, c:1 } }, 'a.b':{ $gt:0 } } );

// Cases with $elemMatch on multiple fields.
t.remove(); 
index = { 'a.b':1, 'a.c':1, 'd.e':1, 'd.f':1 };
t.ensureIndex( index );
t.insert( { a:[ { b:1 }, { c:1 } ], d: { e:1, f:1 } } );
t.insert( { a:[ { b:1, c:1 } ], d: { e:1, f:1 }  } );
t.insert( { a:{ b:1, c:1 }, d:[ { e:1, f:1 } ] } );
t.insert( { a:{ b:1, c:1 }, d:[ { e:1 }, { f:1 } ] } );

assert.eq( 4, t.count() );

// QUERY MIGRATION
// bounds over d.e
// Without $elemMatch.
//assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ MIN, MAX ]], 'd.e':[[ 1, 1 ]], 'd.f':[[ MIN, MAX ]]  },
//                             { 'a.b':1, 'a.c':1, 'd.e':1, 'd.f':1 } );

// QUERY MIGRATION
// bounds with elem match
// With $elemMatch on two fields
//assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ 1, 1 ]], 'd.e':[[ 1, 1 ]], 'd.f':[[ 1, 1 ]] },
//                             { a:{ $elemMatch:{ b:1, c:1 } }, 'd': { $elemMatch:{ e:1, f:1 } } } );

// QUERY MIGRATION
// bounds with elem match
// With $elemMatch on first field but not the second
//assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ 1, 1 ]], 'd.e':[[ 1, 1 ]], 'd.f':[[ MIN, MAX ]] },
//                             { a:{ $elemMatch:{ b:1, c:1 } }, 'd.e': 1, 'd.f' : 1 } );

// QUERY MIGRATION
// bounds with elem match
// With $elemMatch on second field but not the first
//assertResultsAndIndexBounds( { 'a.b':[[ 1, 1 ]], 'a.c':[[ MIN, MAX ]], 'd.e':[[ 1, 1 ]], 'd.f':[[ 1, 1 ]] },
//                             { 'a.b': 1, 'a.c' : 1, 'd': { $elemMatch:{ e:1, f:1 } } } );


// Cases with nested $elemMatch.
t.remove()
index = { 'a.b.c':1, 'a.b.d' :1 };
t.ensureIndex( index );
t.insert( { a:[ { b: [ { c : 1, d : 1 } ] } ] } ) ;
t.insert( { a:[ { b: [ { c : 1 } , { d : 1 } ] } ] } ) ;
assert.eq( 2, t.count() );
// Without $elemMatch.
assertResultsAndIndexBounds( { 'a.b.c':[[ 1, 1 ]], 'a.b.d':[[ MIN, MAX ]] },
                             { 'a.b.c':1, 'a.b.d':1 } );
// QUERY MIGRATION
// bounds with elem match
// With $elemMatch.
//assertResultsAndIndexBounds( { 'a.b.c':[[ 1, 1 ]], 'a.b.d':[[ 1, 1 ]] },
//                             { "a" : { $elemMatch : { "b" : { $elemMatch : { c : 1, d : 1 } } } } } );

// Cases with double dotted index field names.
t.drop();
index = { 'a.b.x':1, 'a.b.y':1 };
t.ensureIndex( index );
t.save( { a:{ b:{ x:1, y:1 } } } );
t.save( { a:[ { b:{ x:1 } }, { b:{ y:1 } } ] } );
t.save( { a:[ { b:[ { x:1 }, { y:1 } ] } ] } );
t.save( { a:[ { b:[ { x:1, y:1 } ] } ] } );
assert.eq( 4, t.count() );
// No $elemMatch.
assertResultsAndIndexBounds( { 'a.b.x':[[ 1, 1 ]], 'a.b.y':[[ MIN, MAX ]] },
                             { 'a.b.x':1, 'a.b.y':1 } );
// $elemMatch with dotted children.
assertResultsAndIndexBounds( { 'a.b.x':[[ 1, 1 ]], 'a.b.y':[[ MIN, MAX ]] },
                             { a:{ $elemMatch:{ 'b.x':1, 'b.y':1 } } } );

// QUERY MIGRATION
// bounds on elem match
// $elemMatch with undotted children.
// assertResultsAndIndexBounds( { 'a.b.x':[[ 1, 1 ]], 'a.b.y':[[ 1, 1 ]] },
//                             { 'a.b':{ $elemMatch:{ x:1, y:1 } } } );

// Cases where a field is indexed along with its children.
t.dropIndexes();
index = { 'a':1, 'a.b.x':1, 'a.b.y':1 };
t.ensureIndex( index );
// QUERY_MIGRATION negation bounds
// Constraint on a prior parent field.
//assertResultsAndIndexBounds( { a:[[ MIN, 4 ], [ 4, MAX ]], 'a.b.x':[[ MIN, MAX ]],
                               //'a.b.y':[[ MIN, MAX ]] },
                             //{ a:{ $ne:4 }, 'a.b':{ $elemMatch:{ x:1, y:1 } } } );

// QUERY MIGRATION
// bounds on elem match
// No constraint on a prior parent field.
// assertResultsAndIndexBounds( { a:[[ MIN, MAX ]], 'a.b.x':[[ 1, 1 ]], 'a.b.y':[[ 1, 1 ]] },
//                             { 'a.b':{ $elemMatch:{ x:1, y:1 } } } );


// Cases with double dotted index field names branching to different fields at each dot.
t.drop();
index = { 'a.b.c':1, 'a.e.f':1, 'a.b.d':1, 'a.e.g':1 }
t.ensureIndex( index );
t.save( { a:{ b:{ c:1, d:1 }, e:{ f:1, g:1 } } } );
t.save( { a:[ { b:{ c:1 }, e:{ f:1 } }, { b:{ d:1 }, e:{ g:1 } } ] } );
t.save( { a:[ { b:{ c:1 } }, { e:{ f:1 } }, { b:{ d:1 } }, { e:{ g:1 } } ] } );
t.save( { a:[ { b:[ { c:1 }, { d:1 } ] }, { e:[ { f:1 }, { g:1 } ] } ] } );
t.save( { a:[ { b:[ { c:[ 1 ] }, { d:[ 1 ] } ] }, { e:[ { f:[ 1 ] }, { g:[ 1 ] } ] } ] } );
t.save( { a:[ { b:[ { c:1, d:1 } ] }, { e:[ { f:1 }, { g:1 } ] } ] } );
t.save( { a:[ { b:[ { c:1, d:1 } ] }, { e:[ { f:1, g:1 } ] } ] } );
assert.eq( 7, t.count() );

// QUERY MIGRATION
// bounds on elem match
// Constraint on a prior cousin field.
// assertResultsAndIndexBounds( { 'a.b.c':[[ 1, 1 ]], 'a.e.f':[[ MIN, MAX ]],
//                                'a.b.d':[[ 1, 1 ]], 'a.e.g':[[ MIN, MAX ]] },
//                              { 'a.b':{ $elemMatch:{ c:1, d:1 } },
//                                'a.e':{ $elemMatch:{ f:1, g:1 } } } );

// QUERY MIGRATION
// bounds on elem match
// Different constraint on a prior cousin field.
// assertResultsAndIndexBounds( { 'a.b.c':[[ MIN, MAX ]], 'a.e.f':[[ 1, 1 ]],
//                                'a.b.d':[[ MIN, MAX ]], 'a.e.g':[[ 1, 1 ]] },
//                              { 'a.b':{ $elemMatch:{ d:1 } },
//                                'a.e':{ $elemMatch:{ f:1, g:1 } } } );


// Cases with double dotted index field names branching to different fields at each dot, and the
// same field name strings after the second dot.
t.drop();
index = { 'a.b.c':1, 'a.e.c':1, 'a.b.d':1, 'a.e.d':1 }
t.ensureIndex( index );
t.save( { a:[ { b:[ { c:1, d:1 } ] }, { e:[ { c:1, d:1 } ] } ] } );
assert.eq( 1, t.count() );

// QUERY MIGRATION
// bounds on elem match
// Constraint on a prior cousin field with the same field names.
// assertResultsAndIndexBounds( { 'a.b.c':[[ 1, 1 ]], 'a.e.c':[[ MIN, MAX ]],
//                                'a.b.d':[[ 1, 1 ]], 'a.e.d':[[ MIN, MAX ]] },
//                              { 'a.b':{ $elemMatch:{ c:1, d:1 } },
//                                'a.e':{ $elemMatch:{ c:1, d:1 } } } );

// QUERY MIGRATION
// bounds on elem match
// Constraint on a prior cousin field with the same field names.
// assertResultsAndIndexBounds( { 'a.b.c':[[ MIN, MAX ]], 'a.e.c':[[ 1, 1 ]],
//                                'a.b.d':[[ MIN, MAX ]], 'a.e.d':[[ 1, 1 ]] },
//                              { 'a.b':{ $elemMatch:{ d:1 } },
//                                'a.e':{ $elemMatch:{ c:1, d:1 } } } );