summaryrefslogtreecommitdiff
path: root/jstests/sharding/collation_targeting.js
diff options
context:
space:
mode:
authorTess Avitabile <tess.avitabile@mongodb.com>2016-07-26 14:24:29 -0400
committerTess Avitabile <tess.avitabile@mongodb.com>2016-08-05 10:32:09 -0400
commite3c13d1bdafd2517248fffdb56f1f421e3e792ea (patch)
treea433e1a8ab5e1e54a2155a6bcf7022a87f230ae4 /jstests/sharding/collation_targeting.js
parent0a8a7fa7da1a8bbe31b9eb8a0ca8be6927d7139c (diff)
downloadmongo-e3c13d1bdafd2517248fffdb56f1f421e3e792ea.tar.gz
SERVER-24439 Make mongos commands over sharded collections inherit the collection default collation
Diffstat (limited to 'jstests/sharding/collation_targeting.js')
-rw-r--r--jstests/sharding/collation_targeting.js447
1 files changed, 447 insertions, 0 deletions
diff --git a/jstests/sharding/collation_targeting.js b/jstests/sharding/collation_targeting.js
new file mode 100644
index 00000000000..c4dc22641c7
--- /dev/null
+++ b/jstests/sharding/collation_targeting.js
@@ -0,0 +1,447 @@
+// Test shard targeting for queries with collation.
+(function() {
+ "use strict";
+
+ const caseInsensitive = {locale: "en_US", strength: 2};
+
+ var explain;
+ var writeRes;
+
+ // Create a cluster with 3 shards.
+ var st = new ShardingTest({shards: 3});
+ var testDB = st.s.getDB("test");
+ assert.commandWorked(testDB.adminCommand({enableSharding: testDB.getName()}));
+ st.ensurePrimaryShard(testDB.getName(), "shard0001");
+
+ // Create a collection sharded on {a: 1}. Add 2dsphere index to test geoNear.
+ var coll = testDB.getCollection("simple_collation");
+ coll.drop();
+ assert.commandWorked(coll.createIndex({a: 1}));
+ assert.commandWorked(coll.createIndex({geo: "2dsphere"}));
+ assert.commandWorked(testDB.adminCommand({shardCollection: coll.getFullName(), key: {a: 1}}));
+
+ // Split the collection.
+ // shard0000: { "a" : { "$minKey" : 1 } } -->> { "a" : 10 }
+ // shard0001: { "a" : 10 } -->> { "a" : "a"}
+ // shard0002: { "a" : "a" } -->> { "a" : { "$maxKey" : 1 }}
+ assert.commandWorked(testDB.adminCommand({split: coll.getFullName(), middle: {a: 10}}));
+ assert.commandWorked(testDB.adminCommand({split: coll.getFullName(), middle: {a: "a"}}));
+ assert.commandWorked(
+ testDB.adminCommand({moveChunk: coll.getFullName(), find: {a: 1}, to: "shard0000"}));
+ assert.commandWorked(
+ testDB.adminCommand({moveChunk: coll.getFullName(), find: {a: "FOO"}, to: "shard0001"}));
+ assert.commandWorked(
+ testDB.adminCommand({moveChunk: coll.getFullName(), find: {a: "foo"}, to: "shard0002"}));
+
+ // Put data on each shard.
+ // Note that the balancer is off by default, so the chunks will stay put.
+ // shard0000: {a: 1}
+ // shard0001: {a: 100}, {a: "FOO"}
+ // shard0002: {a: "foo"}
+ // Include geo field to test geoNear.
+ var a_1 = {_id: 0, a: 1, geo: {type: "Point", coordinates: [0, 0]}};
+ var a_100 = {_id: 1, a: 100, geo: {type: "Point", coordinates: [0, 0]}};
+ var a_FOO = {_id: 2, a: "FOO", geo: {type: "Point", coordinates: [0, 0]}};
+ var a_foo = {_id: 3, a: "foo", geo: {type: "Point", coordinates: [0, 0]}};
+ assert.writeOK(coll.insert(a_1));
+ assert.writeOK(coll.insert(a_100));
+ assert.writeOK(coll.insert(a_FOO));
+ assert.writeOK(coll.insert(a_foo));
+
+ // Aggregate.
+
+ // Test an aggregate command on strings with a non-simple collation. This should be
+ // scatter-gather.
+ assert.eq(2, coll.aggregate([{$match: {a: "foo"}}], {collation: caseInsensitive}).itcount());
+ explain = coll.explain().aggregate([{$match: {a: "foo"}}], {collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(3, Object.keys(explain.shards).length);
+
+ // Test an aggregate command with a simple collation. This should be single-shard.
+ assert.eq(1, coll.aggregate([{$match: {a: "foo"}}]).itcount());
+ explain = coll.explain().aggregate([{$match: {a: "foo"}}]);
+ assert.commandWorked(explain);
+ assert.eq(1, Object.keys(explain.shards).length);
+
+ // Test an aggregate command on numbers with a non-simple collation. This should be
+ // single-shard.
+ assert.eq(1, coll.aggregate([{$match: {a: 100}}], {collation: caseInsensitive}).itcount());
+ explain = coll.explain().aggregate([{$match: {a: 100}}], {collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, Object.keys(explain.shards).length);
+
+ // Count.
+
+ // Test a count command on strings with a non-simple collation. This should be scatter-gather.
+ assert.eq(2, coll.find({a: "foo"}).collation(caseInsensitive).count());
+ explain = coll.explain().find({a: "foo"}).collation(caseInsensitive).count();
+ assert.commandWorked(explain);
+ assert.eq(3, explain.queryPlanner.winningPlan.shards.length);
+
+ // Test a count command with a simple collation. This should be single-shard.
+ assert.eq(1, coll.find({a: "foo"}).count());
+ explain = coll.explain().find({a: "foo"}).count();
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
+ // Test a count command on numbers with a non-simple collation. This should be single-shard.
+ assert.eq(1, coll.find({a: 100}).collation(caseInsensitive).count());
+ explain = coll.explain().find({a: 100}).collation(caseInsensitive).count();
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
+ // Distinct.
+
+ // Test a distinct command on strings with a non-simple collation. This should be
+ // scatter-gather.
+ assert.eq(2, coll.distinct("_id", {a: "foo"}, {collation: caseInsensitive}).length);
+ explain = coll.explain().distinct("_id", {a: "foo"}, {collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(3, explain.queryPlanner.winningPlan.shards.length);
+
+ // Test that deduping respects the collation.
+ assert.eq(1, coll.distinct("a", {a: "foo"}, {collation: caseInsensitive}).length);
+
+ // Test a distinct command with a simple collation. This should be single-shard.
+ assert.eq(1, coll.distinct("_id", {a: "foo"}).length);
+ explain = coll.explain().distinct("_id", {a: "foo"});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
+ // Test a distinct command on numbers with a non-simple collation. This should be single-shard.
+ assert.eq(1, coll.distinct("_id", {a: 100}, {collation: caseInsensitive}).length);
+ explain = coll.explain().distinct("_id", {a: 100}, {collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
+ // Find.
+
+ // Test a find command on strings with a non-simple collation. This should be scatter-gather.
+ if (testDB.getMongo().useReadCommands()) {
+ assert.eq(2, coll.find({a: "foo"}).collation(caseInsensitive).itcount());
+ explain = coll.find({a: "foo"}).collation(caseInsensitive).explain();
+ assert.commandWorked(explain);
+ assert.eq(3, explain.queryPlanner.winningPlan.shards.length);
+ }
+
+ // Test a find command with a simple collation. This should be single-shard.
+ assert.eq(1, coll.find({a: "foo"}).itcount());
+ explain = coll.find({a: "foo"}).explain();
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
+ // Test a find command on numbers with a non-simple collation. This should be single-shard.
+ if (testDB.getMongo().useReadCommands()) {
+ assert.eq(1, coll.find({a: 100}).collation(caseInsensitive).itcount());
+ explain = coll.find({a: 100}).collation(caseInsensitive).explain();
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+ }
+
+ // FindAndModify.
+
+ // Sharded findAndModify on strings with non-simple collation should fail, because findAndModify
+ // must target a single shard.
+ assert.throws(function() {
+ coll.findAndModify({query: {a: "foo"}, update: {$set: {b: 1}}, collation: caseInsensitive});
+ });
+ assert.throws(function() {
+ coll.explain().findAndModify(
+ {query: {a: "foo"}, update: {$set: {b: 1}}, collation: caseInsensitive});
+ });
+
+ // Sharded findAndModify on strings with simple collation should succeed. This should be
+ // single-shard.
+ assert.eq("foo", coll.findAndModify({query: {a: "foo"}, update: {$set: {b: 1}}}).a);
+ explain = coll.explain().findAndModify({query: {a: "foo"}, update: {$set: {b: 1}}});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
+ // Sharded findAndModify on numbers with non-simple collation should succeed. This should be
+ // single-shard.
+ assert.eq(
+ 100,
+ coll.findAndModify({query: {a: 100}, update: {$set: {b: 1}}, collation: caseInsensitive})
+ .a);
+ explain = coll.explain().findAndModify(
+ {query: {a: 100}, update: {$set: {b: 1}}, collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
+ // GeoNear.
+
+ // Test geoNear on strings with a non-simple collation.
+ assert.eq(2,
+ assert
+ .commandWorked(testDB.runCommand({
+ geoNear: coll.getName(),
+ near: {type: "Point", coordinates: [0, 0]},
+ spherical: true,
+ query: {a: "foo"},
+ collation: caseInsensitive
+ }))
+ .results.length);
+
+ // Test geoNear on strings with a simple collation.
+ assert.eq(1,
+ assert
+ .commandWorked(testDB.runCommand({
+ geoNear: coll.getName(),
+ near: {type: "Point", coordinates: [0, 0]},
+ spherical: true,
+ query: {a: "foo"}
+ }))
+ .results.length);
+
+ // MapReduce.
+
+ // Test mapReduce on strings with a non-simple collation.
+ assert.eq(2,
+ assert
+ .commandWorked(coll.mapReduce(
+ function() {
+ emit(this.a, 1);
+ },
+ function(key, values) {
+ return Array.sum(values);
+ },
+ {out: {inline: 1}, query: {a: "foo"}, collation: caseInsensitive}))
+ .results.length);
+
+ // Test mapReduce on strings with a simple collation.
+ assert.eq(1,
+ assert
+ .commandWorked(coll.mapReduce(
+ function() {
+ emit(this.a, 1);
+ },
+ function(key, values) {
+ return Array.sum(values);
+ },
+ {out: {inline: 1}, query: {a: "foo"}}))
+ .results.length);
+
+ // Remove.
+
+ // Test a remove command on strings with non-simple collation. This should be scatter-gather.
+ if (testDB.getMongo().writeMode() === "commands") {
+ writeRes = coll.remove({a: "foo"}, {collation: caseInsensitive});
+ assert.writeOK(writeRes);
+ assert.eq(2, writeRes.nRemoved);
+ explain = coll.explain().remove({a: "foo"}, {collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(3, explain.queryPlanner.winningPlan.shards.length);
+ assert.writeOK(coll.insert(a_FOO));
+ assert.writeOK(coll.insert(a_foo));
+ }
+
+ // Test a remove command on strings with simple collation. This should be single-shard.
+ writeRes = coll.remove({a: "foo"});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nRemoved);
+ explain = coll.explain().remove({a: "foo"});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+ assert.writeOK(coll.insert(a_foo));
+
+ // Test a remove command on numbers with non-simple collation. This should be single-shard.
+ if (testDB.getMongo().writeMode() === "commands") {
+ writeRes = coll.remove({a: 100}, {collation: caseInsensitive});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nRemoved);
+ explain = coll.explain().remove({a: 100}, {collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+ assert.writeOK(coll.insert(a_100));
+ }
+
+ // A single remove (justOne: true) must be single-shard or an exact-ID query. A query is
+ // exact-ID if it contains an equality on _id and either has the collection default collation or
+ // _id is not a string/object/array.
+
+ // Single remove on string shard key with non-simple collation should fail, because it is not
+ // single-shard.
+ if (testDB.getMongo().writeMode() === "commands") {
+ assert.writeError(coll.remove({a: "foo"}, {justOne: true, collation: caseInsensitive}));
+ }
+
+ // Single remove on string shard key with simple collation should succeed, because it is
+ // single-shard.
+ writeRes = coll.remove({a: "foo"}, {justOne: true});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nRemoved);
+ explain = coll.explain().remove({a: "foo"}, {justOne: true});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+ assert.writeOK(coll.insert(a_foo));
+
+ // Single remove on number shard key with non-simple collation should succeed, because it is
+ // single-shard.
+ if (testDB.getMongo().writeMode() === "commands") {
+ writeRes = coll.remove({a: 100}, {justOne: true, collation: caseInsensitive});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nRemoved);
+ explain = coll.explain().remove({a: 100}, {justOne: true, collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+ assert.writeOK(coll.insert(a_100));
+ }
+
+ // Single remove on string _id with non-collection-default collation should fail, because it is
+ // not an exact-ID query.
+ if (testDB.getMongo().writeMode() === "commands") {
+ assert.writeError(coll.remove({_id: "foo"}, {justOne: true, collation: caseInsensitive}));
+ }
+
+ // Single remove on string _id with collection-default collation should succeed, because it is
+ // an exact-ID query.
+ assert.writeOK(coll.insert({_id: "foo", a: "bar"}));
+ writeRes = coll.remove({_id: "foo"}, {justOne: true});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nRemoved);
+
+ // Single remove on string _id with collection-default collation explicitly given should
+ // succeed, because it is an exact-ID query.
+ if (testDB.getMongo().writeMode() === "commands") {
+ assert.writeOK(coll.insert({_id: "foo", a: "bar"}));
+ writeRes = coll.remove({_id: "foo"}, {justOne: true, collation: {locale: "simple"}});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nRemoved);
+ }
+
+ // Single remove on number _id with non-collection-default collation should succeed, because it
+ // is an exact-ID query.
+ if (testDB.getMongo().writeMode() === "commands") {
+ writeRes = coll.remove({_id: a_100._id}, {justOne: true, collation: caseInsensitive});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nRemoved);
+ assert.writeOK(coll.insert(a_100));
+ }
+
+ // Update.
+
+ // Test an update command on strings with non-simple collation. This should be scatter-gather.
+ if (testDB.getMongo().writeMode() === "commands") {
+ writeRes =
+ coll.update({a: "foo"}, {$set: {b: 1}}, {multi: true, collation: caseInsensitive});
+ assert.writeOK(writeRes);
+ assert.eq(2, writeRes.nMatched);
+ explain = coll.explain().update(
+ {a: "foo"}, {$set: {b: 1}}, {multi: true, collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(3, explain.queryPlanner.winningPlan.shards.length);
+ }
+
+ // Test an update command on strings with simple collation. This should be single-shard.
+ writeRes = coll.update({a: "foo"}, {$set: {b: 1}}, {multi: true});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nMatched);
+ explain = coll.explain().update({a: "foo"}, {$set: {b: 1}}, {multi: true});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
+ // Test an update command on numbers with non-simple collation. This should be single-shard.
+ if (testDB.getMongo().writeMode() === "commands") {
+ writeRes = coll.update({a: 100}, {$set: {b: 1}}, {multi: true, collation: caseInsensitive});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nMatched);
+ explain = coll.explain().update(
+ {a: 100}, {$set: {b: 1}}, {multi: true, collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+ }
+
+ // A single (non-multi) update must be single-shard or an exact-ID query. A query is exact-ID if
+ // it contains an equality on _id and either has the collection default collation or _id is not
+ // a string/object/array.
+
+ // Single update on string shard key with non-simple collation should fail, because it is not
+ // single-shard.
+ if (testDB.getMongo().writeMode() === "commands") {
+ assert.writeError(coll.update({a: "foo"}, {$set: {b: 1}}, {collation: caseInsensitive}));
+ }
+
+ // Single update on string shard key with simple collation should succeed, because it is
+ // single-shard.
+ writeRes = coll.update({a: "foo"}, {$set: {b: 1}});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nMatched);
+ explain = coll.explain().update({a: "foo"}, {$set: {b: 1}});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
+ // Single update on number shard key with non-simple collation should succeed, because it is
+ // single-shard.
+ if (testDB.getMongo().writeMode() === "commands") {
+ writeRes = coll.update({a: 100}, {$set: {b: 1}}, {collation: caseInsensitive});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nMatched);
+ explain = coll.explain().update({a: 100}, {$set: {b: 1}}, {collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+ }
+
+ // Single update on string _id with non-collection-default collation should fail, because it is
+ // not an exact-ID query.
+ if (testDB.getMongo().writeMode() === "commands") {
+ assert.writeOK(coll.insert({_id: "foo", a: "bar"}));
+ assert.writeError(coll.update({_id: "foo"}, {$set: {b: 1}}, {collation: caseInsensitive}));
+ assert.writeOK(coll.remove({_id: "foo"}, {justOne: true}));
+ }
+
+ // Single update on string _id with collection-default collation should succeed, because it is
+ // an exact-ID query.
+ assert.writeOK(coll.insert({_id: "foo", a: "bar"}));
+ writeRes = coll.update({_id: "foo"}, {$set: {b: 1}});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nMatched);
+ assert.writeOK(coll.remove({_id: "foo"}, {justOne: true}));
+
+ // Single update on string _id with collection-default collation explicitly given should
+ // succeed, because it is an exact-ID query.
+ if (testDB.getMongo().writeMode() === "commands") {
+ assert.writeOK(coll.insert({_id: "foo", a: "bar"}));
+ writeRes = coll.update({_id: "foo"}, {$set: {b: 1}}, {collation: {locale: "simple"}});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nMatched);
+ assert.writeOK(coll.remove({_id: "foo"}, {justOne: true}));
+ }
+
+ // Single update on number _id with non-collection-default collation should succeed, because it
+ // is an exact-ID query.
+ if (testDB.getMongo().writeMode() === "commands") {
+ writeRes = coll.update({_id: a_foo._id}, {$set: {b: 1}}, {collation: caseInsensitive});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nMatched);
+ }
+
+ // Upsert must always be single-shard.
+
+ // Upsert on strings with non-simple collation should fail, because it is not single-shard.
+ if (testDB.getMongo().writeMode() === "commands") {
+ assert.writeError(coll.update(
+ {a: "foo"}, {$set: {b: 1}}, {multi: true, upsert: true, collation: caseInsensitive}));
+ }
+
+ // Upsert on strings with simple collation should succeed, because it is single-shard.
+ writeRes = coll.update({a: "foo"}, {$set: {b: 1}}, {multi: true, upsert: true});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nMatched);
+ explain = coll.explain().update({a: "foo"}, {$set: {b: 1}}, {multi: true, upsert: true});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+
+ // Upsert on numbers with non-simple collation should succeed, because it is single shard.
+ if (testDB.getMongo().writeMode() === "commands") {
+ writeRes = coll.update(
+ {a: 100}, {$set: {b: 1}}, {multi: true, upsert: true, collation: caseInsensitive});
+ assert.writeOK(writeRes);
+ assert.eq(1, writeRes.nMatched);
+ explain = coll.explain().update(
+ {a: 100}, {$set: {b: 1}}, {multi: true, upsert: true, collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
+ }
+
+ st.stop();
+})(); \ No newline at end of file