summaryrefslogtreecommitdiff
path: root/jstests/core/index13.js
blob: ec35bc8a669624de87ca8e7e0e03614cf0658204 (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
// @tags: [requires_non_retryable_writes, requires_fastcount]

// 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 assertResults(query) {
    explain = t.find(query).hint(index).explain();
    // printjson( explain ); // debug
    assertConsistentResults(query);
}

// 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.
assertResults({'a.b': 1, 'a.c': 1});
// With $elemMatch.
assertResults({a: {$elemMatch: {b: 1, c: 1}}});

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

// Cases relating to parse order and inclusion of intersected ranges.
assertResults({'a.b': 1, a: {$elemMatch: {b: {$gt: 0}, c: 1}}});
assertResults({a: {$elemMatch: {b: 1, c: 1}}, 'a.b': 1});
assertResults({'a.c': 1, a: {$elemMatch: {b: 1, c: 1}}});
assertResults({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());

// Without $elemMatch.
assertResults({'a.b': 1, 'a.c': 1, 'd.e': 1, 'd.f': 1});
// With $elemMatch.
assertResults({a: {$elemMatch: {b: 1, c: 1}}, 'd': {$elemMatch: {e: 1, f: 1}}});
assertResults({a: {$elemMatch: {b: 1, c: 1}}, 'd.e': 1, 'd.f': 1});
assertResults({'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.
assertResults({'a.b.c': 1, 'a.b.d': 1});
// With $elemMatch.
assertResults({"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.
assertResults({'a.b.x': 1, 'a.b.y': 1});
// $elemMatch with dotted children.
assertResults({a: {$elemMatch: {'b.x': 1, 'b.y': 1}}});
// $elemMatch with undotted children.
assertResults({'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);

// With $ne.
assertResults({a: {$ne: 4}, 'a.b': {$elemMatch: {x: 1, y: 1}}});

// No constraint on a prior parent field.
assertResults({'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());

// Constraint on a prior cousin field.
assertResults({'a.b': {$elemMatch: {c: 1, d: 1}}, 'a.e': {$elemMatch: {f: 1, g: 1}}});

// Different constraint on a prior cousin field.
assertResults({'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());

// Constraint on a prior cousin field with the same field names.
assertResults({'a.b': {$elemMatch: {c: 1, d: 1}}, 'a.e': {$elemMatch: {c: 1, d: 1}}});