Skip to content

Commit 089b61b

Browse files
committed
SERVER-25139 Add tests to verify that $lookup respects the collation.
1 parent 4d976e3 commit 089b61b

File tree

2 files changed

+136
-2
lines changed

2 files changed

+136
-2
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* Tests that the $lookup stage respects the collation.
3+
*
4+
* The comparison of string values between the 'localField' and 'foreignField' should use the
5+
* collation either explicitly set on the aggregation operation, or the collation inherited from the
6+
* collection the "aggregate" command was performed on.
7+
*/
8+
(function() {
9+
"use strict";
10+
11+
load("jstests/aggregation/extras/utils.js"); // for arrayEq
12+
13+
const caseInsensitive = {collation: {locale: "en_US", strength: 2}};
14+
15+
const withDefaultCollationColl = db.collation_lookup_with_default;
16+
const withoutDefaultCollationColl = db.collation_lookup_without_default;
17+
18+
withDefaultCollationColl.drop();
19+
withoutDefaultCollationColl.drop();
20+
21+
assert.commandWorked(db.createCollection(withDefaultCollationColl.getName(), caseInsensitive));
22+
assert.writeOK(withDefaultCollationColl.insert({_id: "lowercase", str: "abc"}));
23+
24+
assert.writeOK(withoutDefaultCollationColl.insert({_id: "lowercase", str: "abc"}));
25+
assert.writeOK(withoutDefaultCollationColl.insert({_id: "uppercase", str: "ABC"}));
26+
assert.writeOK(withoutDefaultCollationColl.insert({_id: "unmatched", str: "def"}));
27+
28+
// Test that the $lookup stage respects the inherited collation.
29+
let res = withDefaultCollationColl
30+
.aggregate([{
31+
$lookup: {
32+
from: withoutDefaultCollationColl.getName(),
33+
localField: "str",
34+
foreignField: "str",
35+
as: "matched",
36+
},
37+
}])
38+
.toArray();
39+
assert.eq(1, res.length, tojson(res));
40+
41+
let expected = [{_id: "lowercase", str: "abc"}, {_id: "uppercase", str: "ABC"}];
42+
assert(
43+
arrayEq(expected, res[0].matched),
44+
"Expected " + tojson(expected) + " to equal " + tojson(res[0].matched) + " up to ordering");
45+
46+
// Test that the $lookup stage respects the inherited collation when it optimizes with an
47+
// $unwind stage.
48+
res = withDefaultCollationColl
49+
.aggregate([
50+
{
51+
$lookup: {
52+
from: withoutDefaultCollationColl.getName(),
53+
localField: "str",
54+
foreignField: "str",
55+
as: "matched",
56+
},
57+
},
58+
{$unwind: "$matched"},
59+
])
60+
.toArray();
61+
assert.eq(2, res.length, tojson(res));
62+
63+
expected = [
64+
{_id: "lowercase", str: "abc", matched: {_id: "lowercase", str: "abc"}},
65+
{_id: "lowercase", str: "abc", matched: {_id: "uppercase", str: "ABC"}}
66+
];
67+
assert(arrayEq(expected, res),
68+
"Expected " + tojson(expected) + " to equal " + tojson(res) + " up to ordering");
69+
70+
// Test that the $lookup stage respects an explicit collation on the aggregation operation.
71+
res = withoutDefaultCollationColl
72+
.aggregate(
73+
[
74+
{$match: {_id: "lowercase"}},
75+
{
76+
$lookup: {
77+
from: withoutDefaultCollationColl.getName(),
78+
localField: "str",
79+
foreignField: "str",
80+
as: "matched",
81+
},
82+
},
83+
],
84+
caseInsensitive)
85+
.toArray();
86+
assert.eq(1, res.length, tojson(res));
87+
88+
expected = [{_id: "lowercase", str: "abc"}, {_id: "uppercase", str: "ABC"}];
89+
assert(
90+
arrayEq(expected, res[0].matched),
91+
"Expected " + tojson(expected) + " to equal " + tojson(res[0].matched) + " up to ordering");
92+
93+
// Test that the $lookup stage respects an explicit collation on the aggregation operation when
94+
// it optimizes with an $unwind stage.
95+
res = withoutDefaultCollationColl
96+
.aggregate(
97+
[
98+
{$match: {_id: "lowercase"}},
99+
{
100+
$lookup: {
101+
from: withoutDefaultCollationColl.getName(),
102+
localField: "str",
103+
foreignField: "str",
104+
as: "matched",
105+
},
106+
},
107+
{$unwind: "$matched"},
108+
],
109+
caseInsensitive)
110+
.toArray();
111+
assert.eq(2, res.length, tojson(res));
112+
113+
expected = [
114+
{_id: "lowercase", str: "abc", matched: {_id: "lowercase", str: "abc"}},
115+
{_id: "lowercase", str: "abc", matched: {_id: "uppercase", str: "ABC"}}
116+
];
117+
assert(arrayEq(expected, res),
118+
"Expected " + tojson(expected) + " to equal " + tojson(res) + " up to ordering");
119+
120+
// Test that the $lookup stage uses the "simple" collation if a collation isn't set on the
121+
// collection or the aggregation operation.
122+
res = withoutDefaultCollationColl
123+
.aggregate([
124+
{$match: {_id: "lowercase"}},
125+
{
126+
$lookup: {
127+
from: withDefaultCollationColl.getName(),
128+
localField: "str",
129+
foreignField: "str",
130+
as: "matched",
131+
},
132+
},
133+
])
134+
.toArray();
135+
assert.eq([{_id: "lowercase", str: "abc", matched: [{_id: "lowercase", str: "abc"}]}], res);
136+
})();

src/mongo/db/pipeline/document_source.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,8 +1664,6 @@ class DocumentSourceGeoNear : public DocumentSourceNeedsMongod, public Splittabl
16641664
/**
16651665
* Queries separate collection for equality matches with documents in the pipeline collection.
16661666
* Adds matching documents to a new array field in the input document.
1667-
*
1668-
* TODO SERVER-25139: Make $lookup respect the collation.
16691667
*/
16701668
class DocumentSourceLookUp final : public DocumentSourceNeedsMongod,
16711669
public SplittableDocumentSource {

0 commit comments

Comments
 (0)