mirror of
https://github.com/mongodb/mongo.git
synced 2024-12-01 01:21:03 +01:00
460 lines
18 KiB
JavaScript
460 lines
18 KiB
JavaScript
/**
|
|
* Tests that the $lookup stage respects the collation when the local and/or foreign collections
|
|
* are sharded.
|
|
*
|
|
* The comparison of string values between the 'localField' and 'foreignField' should use the
|
|
* collation either explicitly set on the aggregation operation, or the collation inherited from the
|
|
* collection the "aggregate" command was performed on.
|
|
*/
|
|
(function() {
|
|
"use strict";
|
|
|
|
load("jstests/aggregation/extras/utils.js"); // for arrayEq
|
|
load("jstests/noPassthrough/libs/server_parameter_helpers.js"); // For setParameterOnAllHosts.
|
|
load("jstests/libs/discover_topology.js"); // For findDataBearingNodes.
|
|
|
|
function runTests(withDefaultCollationColl, withoutDefaultCollationColl, collation) {
|
|
// Test that the $lookup stage respects the inherited collation.
|
|
let res = withDefaultCollationColl
|
|
.aggregate([{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
localField: "str",
|
|
foreignField: "str",
|
|
as: "matched",
|
|
},
|
|
}])
|
|
.toArray();
|
|
assert.eq(1, res.length, tojson(res));
|
|
|
|
let expected = [{_id: "lowercase", str: "abc"}, {_id: "uppercase", str: "ABC"}];
|
|
assert(arrayEq(expected, res[0].matched),
|
|
"Expected " + tojson(expected) + " to equal " + tojson(res[0].matched) +
|
|
" up to ordering");
|
|
|
|
res = withDefaultCollationColl
|
|
.aggregate([{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
let : {str1: "$str"},
|
|
pipeline: [
|
|
{$match: {$expr: {$eq: ["$str", "$$str1"]}}},
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
let : {str2: "$str"},
|
|
pipeline: [{$match: {$expr: {$eq: ["$str", "$$str1"]}}}],
|
|
as: "matched2"
|
|
}
|
|
}
|
|
],
|
|
as: "matched1",
|
|
},
|
|
}])
|
|
.toArray();
|
|
assert.eq(1, res.length, tojson(res));
|
|
|
|
expected = [
|
|
{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched2": [{"_id": "lowercase", "str": "abc"}, {"_id": "uppercase", "str": "ABC"}]
|
|
},
|
|
{
|
|
"_id": "uppercase",
|
|
"str": "ABC",
|
|
"matched2":
|
|
[{"_id": "lowercase", "str": "abc"}, {"_id": "uppercase", "str": "ABC"}]
|
|
}
|
|
];
|
|
assert(arrayEq(expected, res[0].matched1),
|
|
"Expected " + tojson(expected) + " to equal " + tojson(res[0].matched1) +
|
|
" up to ordering. " + tojson(res));
|
|
|
|
// Test that the $lookup stage respects the inherited collation when it optimizes with an
|
|
// $unwind stage.
|
|
res = withDefaultCollationColl
|
|
.aggregate([
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
localField: "str",
|
|
foreignField: "str",
|
|
as: "matched",
|
|
},
|
|
},
|
|
{$unwind: "$matched"},
|
|
])
|
|
.toArray();
|
|
assert.eq(2, res.length, tojson(res));
|
|
|
|
expected = [
|
|
{_id: "lowercase", str: "abc", matched: {_id: "lowercase", str: "abc"}},
|
|
{_id: "lowercase", str: "abc", matched: {_id: "uppercase", str: "ABC"}}
|
|
];
|
|
assert(arrayEq(expected, res),
|
|
"Expected " + tojson(expected) + " to equal " + tojson(res) + " up to ordering");
|
|
|
|
res = withDefaultCollationColl
|
|
.aggregate([
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
let : {str1: "$str"},
|
|
pipeline: [
|
|
{$match: {$expr: {$eq: ["$str", "$$str1"]}}},
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
let : {str2: "$str"},
|
|
pipeline: [{$match: {$expr: {$eq: ["$str", "$$str1"]}}}],
|
|
as: "matched2"
|
|
}
|
|
},
|
|
{$unwind: "$matched2"},
|
|
],
|
|
as: "matched1",
|
|
},
|
|
},
|
|
{$unwind: "$matched1"},
|
|
])
|
|
.toArray();
|
|
assert.eq(4, res.length, tojson(res));
|
|
|
|
expected = [
|
|
{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched1": {
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched2": {"_id": "lowercase", "str": "abc"}
|
|
}
|
|
},
|
|
{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched1": {
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched2": {"_id": "uppercase", "str": "ABC"}
|
|
}
|
|
},
|
|
{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched1": {
|
|
"_id": "uppercase",
|
|
"str": "ABC",
|
|
"matched2": {"_id": "lowercase", "str": "abc"}
|
|
}
|
|
},
|
|
{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched1": {
|
|
"_id": "uppercase",
|
|
"str": "ABC",
|
|
"matched2": {"_id": "uppercase", "str": "ABC"}
|
|
}
|
|
}
|
|
];
|
|
assert(arrayEq(expected, res),
|
|
"Expected " + tojson(expected) + " to equal " + tojson(res) + " up to ordering");
|
|
|
|
// Test that the $lookup stage respects an explicit collation on the aggregation operation.
|
|
res = withoutDefaultCollationColl
|
|
.aggregate(
|
|
[
|
|
{$match: {_id: "lowercase"}},
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
localField: "str",
|
|
foreignField: "str",
|
|
as: "matched",
|
|
},
|
|
},
|
|
],
|
|
collation)
|
|
.toArray();
|
|
assert.eq(1, res.length, tojson(res));
|
|
|
|
expected = [{_id: "lowercase", str: "abc"}, {_id: "uppercase", str: "ABC"}];
|
|
assert(arrayEq(expected, res[0].matched),
|
|
"Expected " + tojson(expected) + " to equal " + tojson(res[0].matched) +
|
|
" up to ordering");
|
|
|
|
res = withoutDefaultCollationColl
|
|
.aggregate(
|
|
[
|
|
{$match: {_id: "lowercase"}},
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
let : {str1: "$str"},
|
|
pipeline: [
|
|
{$match: {$expr: {$eq: ["$str", "$$str1"]}}},
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
let : {str2: "$str"},
|
|
pipeline: [{$match: {$expr: {$eq: ["$str", "$$str1"]}}}],
|
|
as: "matched2"
|
|
}
|
|
}
|
|
],
|
|
as: "matched1",
|
|
},
|
|
}
|
|
],
|
|
collation)
|
|
.toArray();
|
|
assert.eq(1, res.length, tojson(res));
|
|
|
|
expected = [
|
|
{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched2": [{"_id": "lowercase", "str": "abc"}, {"_id": "uppercase", "str": "ABC"}]
|
|
},
|
|
{
|
|
"_id": "uppercase",
|
|
"str": "ABC",
|
|
"matched2":
|
|
[{"_id": "lowercase", "str": "abc"}, {"_id": "uppercase", "str": "ABC"}]
|
|
}
|
|
];
|
|
assert(arrayEq(expected, res[0].matched1),
|
|
"Expected " + tojson(expected) + " to equal " + tojson(res[0].matched1) +
|
|
" up to ordering");
|
|
|
|
// Test that the $lookup stage respects an explicit collation on the aggregation operation
|
|
// when
|
|
// it optimizes with an $unwind stage.
|
|
res = withoutDefaultCollationColl
|
|
.aggregate(
|
|
[
|
|
{$match: {_id: "lowercase"}},
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
localField: "str",
|
|
foreignField: "str",
|
|
as: "matched",
|
|
},
|
|
},
|
|
{$unwind: "$matched"},
|
|
],
|
|
collation)
|
|
.toArray();
|
|
assert.eq(2, res.length, tojson(res));
|
|
|
|
expected = [
|
|
{_id: "lowercase", str: "abc", matched: {_id: "lowercase", str: "abc"}},
|
|
{_id: "lowercase", str: "abc", matched: {_id: "uppercase", str: "ABC"}}
|
|
];
|
|
assert(arrayEq(expected, res),
|
|
"Expected " + tojson(expected) + " to equal " + tojson(res) + " up to ordering");
|
|
|
|
res = withoutDefaultCollationColl
|
|
.aggregate(
|
|
[
|
|
{$match: {_id: "lowercase"}},
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
let : {str1: "$str"},
|
|
pipeline: [
|
|
{$match: {$expr: {$eq: ["$str", "$$str1"]}}},
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
let : {str2: "$str"},
|
|
pipeline: [{$match: {$expr: {$eq: ["$str", "$$str1"]}}}],
|
|
as: "matched2"
|
|
}
|
|
},
|
|
{$unwind: "$matched2"},
|
|
],
|
|
as: "matched1",
|
|
},
|
|
},
|
|
{$unwind: "$matched1"},
|
|
],
|
|
collation)
|
|
.toArray();
|
|
assert.eq(4, res.length, tojson(res));
|
|
|
|
expected = [
|
|
{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched1": {
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched2": {"_id": "lowercase", "str": "abc"}
|
|
}
|
|
},
|
|
{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched1": {
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched2": {"_id": "uppercase", "str": "ABC"}
|
|
}
|
|
},
|
|
{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched1": {
|
|
"_id": "uppercase",
|
|
"str": "ABC",
|
|
"matched2": {"_id": "lowercase", "str": "abc"}
|
|
}
|
|
},
|
|
{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched1": {
|
|
"_id": "uppercase",
|
|
"str": "ABC",
|
|
"matched2": {"_id": "uppercase", "str": "ABC"}
|
|
}
|
|
}
|
|
];
|
|
assert(arrayEq(expected, res),
|
|
"Expected " + tojson(expected) + " to equal " + tojson(res) + " up to ordering");
|
|
|
|
// Test that the $lookup stage uses the "simple" collation if a collation isn't set on the
|
|
// collection or the aggregation operation.
|
|
res = withoutDefaultCollationColl
|
|
.aggregate([
|
|
{$match: {_id: "lowercase"}},
|
|
{
|
|
$lookup: {
|
|
from: withDefaultCollationColl.getName(),
|
|
localField: "str",
|
|
foreignField: "str",
|
|
as: "matched",
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
assert.eq([{_id: "lowercase", str: "abc", matched: [{_id: "lowercase", str: "abc"}]}], res);
|
|
|
|
res = withoutDefaultCollationColl
|
|
.aggregate([
|
|
{$match: {_id: "lowercase"}},
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
let : {str1: "$str"},
|
|
pipeline: [
|
|
{$match: {$expr: {$eq: ["$str", "$$str1"]}}},
|
|
{
|
|
$lookup: {
|
|
from: withoutDefaultCollationColl.getName(),
|
|
let : {str2: "$str"},
|
|
pipeline: [{$match: {$expr: {$eq: ["$str", "$$str1"]}}}],
|
|
as: "matched2"
|
|
}
|
|
},
|
|
{$unwind: "$matched2"},
|
|
],
|
|
as: "matched1",
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
assert.eq([{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched1": [{
|
|
"_id": "lowercase",
|
|
"str": "abc",
|
|
"matched2": {"_id": "lowercase", "str": "abc"}
|
|
}]
|
|
}],
|
|
res);
|
|
}
|
|
|
|
const st = new ShardingTest({shards: 2, config: 1});
|
|
setParameterOnAllHosts(
|
|
DiscoverTopology.findNonConfigNodes(st.s), "internalQueryAllowShardedLookup", true);
|
|
|
|
const testName = "collation_lookup";
|
|
const caseInsensitive = {collation: {locale: "en_US", strength: 2}};
|
|
|
|
const mongosDB = st.s0.getDB(testName);
|
|
const withDefaultCollationColl = mongosDB[testName + "_with_default"];
|
|
const withoutDefaultCollationColl = mongosDB[testName + "_without_default"];
|
|
|
|
assert.commandWorked(
|
|
mongosDB.createCollection(withDefaultCollationColl.getName(), caseInsensitive));
|
|
assert.writeOK(withDefaultCollationColl.insert({_id: "lowercase", str: "abc"}));
|
|
|
|
assert.writeOK(withoutDefaultCollationColl.insert({_id: "lowercase", str: "abc"}));
|
|
assert.writeOK(withoutDefaultCollationColl.insert({_id: "uppercase", str: "ABC"}));
|
|
assert.writeOK(withoutDefaultCollationColl.insert({_id: "unmatched", str: "def"}));
|
|
|
|
//
|
|
// Sharded collection with default collation and unsharded collection without a default
|
|
// collation.
|
|
//
|
|
assert.commandWorked(
|
|
withDefaultCollationColl.createIndex({str: 1}, {collation: {locale: "simple"}}));
|
|
|
|
// Enable sharding on the test DB and ensure its primary is shard0000.
|
|
assert.commandWorked(mongosDB.adminCommand({enableSharding: mongosDB.getName()}));
|
|
st.ensurePrimaryShard(mongosDB.getName(), st.shard0.shardName);
|
|
|
|
// Shard the collection with a default collation.
|
|
assert.commandWorked(mongosDB.adminCommand({
|
|
shardCollection: withDefaultCollationColl.getFullName(),
|
|
key: {str: 1},
|
|
collation: {locale: "simple"}
|
|
}));
|
|
|
|
// Split the collection into 2 chunks.
|
|
assert.commandWorked(mongosDB.adminCommand(
|
|
{split: withDefaultCollationColl.getFullName(), middle: {str: "abc"}}));
|
|
|
|
// Move the chunk containing {str: "abc"} to shard0001.
|
|
assert.commandWorked(mongosDB.adminCommand({
|
|
moveChunk: withDefaultCollationColl.getFullName(),
|
|
find: {str: "abc"},
|
|
to: st.shard1.shardName
|
|
}));
|
|
|
|
runTests(withDefaultCollationColl, withoutDefaultCollationColl, caseInsensitive);
|
|
|
|
// TODO: Enable the following tests once SERVER-32536 is fixed.
|
|
//
|
|
// Sharded collection with default collation and sharded collection without a default
|
|
// collation.
|
|
//
|
|
|
|
// Shard the collection without a default collation.
|
|
// assert.commandWorked(mongosDB.adminCommand({
|
|
// shardCollection: withoutDefaultCollationColl.getFullName(),
|
|
// key: {_id: 1},
|
|
// }));
|
|
|
|
// // Split the collection into 2 chunks.
|
|
// assert.commandWorked(mongosDB.adminCommand(
|
|
// {split: withoutDefaultCollationColl.getFullName(), middle: {_id: "unmatched"}}));
|
|
|
|
// // Move the chunk containing {_id: "lowercase"} to shard0001.
|
|
// assert.commandWorked(mongosDB.adminCommand({
|
|
// moveChunk: withoutDefaultCollationColl.getFullName(),
|
|
// find: {_id: "lowercase"},
|
|
// to: st.shard1.shardName
|
|
// }));
|
|
|
|
// runTests(withDefaultCollationColl, withoutDefaultCollationColl, caseInsensitive);
|
|
|
|
st.stop();
|
|
})();
|