// Tests user queries over the config servers. (function() { 'use strict'; load("jstests/sharding/libs/find_chunks_util.js"); var getListCollectionsCursor = function(database, options, subsequentBatchSize) { return new DBCommandCursor( database, database.runCommand("listCollections", options), subsequentBatchSize); }; var getListIndexesCursor = function(coll, options, subsequentBatchSize) { return new DBCommandCursor( coll.getDB(), coll.runCommand("listIndexes", options), subsequentBatchSize); }; var arrayGetNames = function(array) { return array.map(function(spec) { return spec.name; }); }; var cursorGetCollectionNames = function(cursor) { return arrayGetNames(cursor.toArray()); }; var sortArrayByName = function(array) { return array.sort(function(a, b) { return a.name > b.name; }); }; var cursorGetIndexNames = function(cursor) { return arrayGetNames(sortArrayByName(cursor.toArray())); }; var sortArrayById = function(array) { return array.sort(function(a, b) { return a._id > b._id; }); }; var dropCollectionIfExists = function(coll) { try { coll.drop(); } catch (err) { assert.eq(err.code, ErrorCodes.NamespaceNotFound); } }; /** * Sets up the test database with with several sharded collections. * * @return The list of collection namespaces that were added to the test database. */ var setupTestCollections = function(st) { // testKeys and testCollNames are parallel arrays, testKeys contains the shard key of the // corresponding collection whose name is in testCollNames. var testCollNames = ["4a1", "1a12", "3a1b1", "2a1b1c1", "b1", "b1c1", "d1"]; var testKeys = [{a: 1}, {a: 1}, {a: 1, b: 1}, {a: 1, b: 1, c: 1}, {b: 1}, {b: 1, c: 1}, {d: 1}]; var testDB = st.s.getDB("test"); assert.commandWorked(st.s.adminCommand({enablesharding: testDB.getName()})); var testNamespaces = testCollNames.map(function(e) { return testDB.getName() + "." + e; }); for (var i = 0; i < testKeys.length; i++) { assert.commandWorked( st.s.adminCommand({shardcollection: testNamespaces[i], key: testKeys[i]})); } return testNamespaces; }; /** * Test that a list collections query works on the config database. This test cannot detect * whether list collections lists extra collections. */ var testListConfigCollections = function(st) { // This test depends on all the collections in the configCollList being in the config // database. var configCollList = ["chunks", "collections", "databases", "lockpings", "locks", "shards", "tags", "version"]; var configDB = st.s.getDB("config"); var userAddedColl = configDB.userAddedColl; var cursor; var cursorArray; // Dropping collections under config is not allowed via mongos. let configPrimary = st.configRS.getPrimary(); dropCollectionIfExists(configPrimary.getDB("config").userAddedColl); configDB.createCollection(userAddedColl.getName()); configCollList.push(userAddedColl.getName()); // wait for config.mongos to be created by ShardingUptimeReporter assert.soon(() => configDB.mongos.exists()); cursor = getListCollectionsCursor(configDB); cursorArray = cursorGetCollectionNames(cursor); for (var i = 0; i < configCollList.length; i++) { assert(cursorArray.indexOf(configCollList[i]) > -1, "Missing " + configCollList[i]); } cursor = getListCollectionsCursor(configDB, {cursor: {batchSize: 1}}, 1); assert.eq(cursor.objsLeftInBatch(), 1); assert(cursorArray.indexOf(cursor.next().name) > -1); assert(cursor.hasNext()); assert.eq(cursor.objsLeftInBatch(), 1); assert(cursorArray.indexOf(cursor.next().name) > -1); }; /** * Test that a list indexes query works on the chunks collection of the config database. */ var testListConfigChunksIndexes = function(st) { // This test depends on all the indexes in the configChunksIndexes being the exact indexes // in the config chunks collection. var configDB = st.s.getDB("config"); var configChunksIndexes = (function() { if (configDB.collections.findOne({_id: "config.system.sessions"}).timestamp) { return ["_id_", "uuid_1_lastmod_1", "uuid_1_min_1", "uuid_1_shard_1_min_1"]; } else { return ["_id_", "ns_1_lastmod_1", "ns_1_min_1", "ns_1_shard_1_min_1"]; } }()); var cursor; var cursorArray = []; cursor = getListIndexesCursor(configDB.chunks); assert.eq(cursorGetIndexNames(cursor), configChunksIndexes); cursor = getListIndexesCursor(configDB.chunks, {cursor: {batchSize: 2}}, 2); assert.eq(cursor.objsLeftInBatch(), 2); cursorArray.push(cursor.next()); cursorArray.push(cursor.next()); assert(cursor.hasNext()); assert.eq(cursor.objsLeftInBatch(), 2); cursorArray.push(cursor.next()); cursorArray.push(cursor.next()); assert(!cursor.hasNext()); assert.eq(arrayGetNames(sortArrayByName(cursorArray)), configChunksIndexes); }; /** * Test queries over the collections collection of the config database. */ var queryConfigCollections = function(st, testNamespaces) { var configDB = st.s.getDB("config"); var cursor; // Find query. cursor = configDB.collections.find({"key.a": 1}, {"key.a": 1, "key.c": 1}) .sort({"_id": 1}) .batchSize(2); assert.eq(cursor.objsLeftInBatch(), 2); assert.eq(cursor.next(), {_id: testNamespaces[1], key: {a: 1}}); assert.eq(cursor.next(), {_id: testNamespaces[3], key: {a: 1, c: 1}}); assert(cursor.hasNext()); assert.eq(cursor.objsLeftInBatch(), 2); assert.eq(cursor.next(), {_id: testNamespaces[2], key: {a: 1}}); assert.eq(cursor.next(), {_id: testNamespaces[0], key: {a: 1}}); assert(!cursor.hasNext()); // Aggregate query. cursor = configDB.collections.aggregate( [ {$match: {"key.b": 1}}, {$sort: {"_id": 1}}, {$project: {"keyb": "$key.b", "keyc": "$key.c"}} ], {cursor: {batchSize: 2}}); assert.eq(cursor.objsLeftInBatch(), 2); assert.eq(cursor.next(), {_id: testNamespaces[3], keyb: 1, keyc: 1}); assert.eq(cursor.next(), {_id: testNamespaces[2], keyb: 1}); assert(cursor.hasNext()); assert.eq(cursor.objsLeftInBatch(), 2); assert.eq(cursor.next(), {_id: testNamespaces[4], keyb: 1}); assert.eq(cursor.next(), {_id: testNamespaces[5], keyb: 1, keyc: 1}); assert(!cursor.hasNext()); }; /** * Test queries over the chunks collection of the config database. */ var queryConfigChunks = function(st) { var configDB = st.s.getDB("config"); var testDB = st.s.getDB("test2"); var testColl = testDB.testColl; var testCollData = [{e: 1}, {e: 3}, {e: 4}, {e: 5}, {e: 7}, {e: 9}, {e: 10}, {e: 12}]; var cursor; var result; // Get shard names. cursor = configDB.shards.find().sort({_id: 1}); var shard1 = cursor.next()._id; var shard2 = cursor.next()._id; assert(!cursor.hasNext()); assert.commandWorked(st.s.adminCommand({enablesharding: testDB.getName()})); st.ensurePrimaryShard(testDB.getName(), shard1); // Setup. assert.commandWorked(st.s.adminCommand({shardcollection: testColl.getFullName(), key: {e: 1}})); for (var i = 0; i < testCollData.length; i++) { assert.commandWorked(testColl.insert(testCollData[i])); } assert.commandWorked(st.s.adminCommand({split: testColl.getFullName(), middle: {e: 2}})); assert.commandWorked(st.s.adminCommand({split: testColl.getFullName(), middle: {e: 6}})); assert.commandWorked(st.s.adminCommand({split: testColl.getFullName(), middle: {e: 8}})); assert.commandWorked(st.s.adminCommand({split: testColl.getFullName(), middle: {e: 11}})); assert.commandWorked( st.s.adminCommand({movechunk: testColl.getFullName(), find: {e: 1}, to: shard2})); assert.commandWorked( st.s.adminCommand({movechunk: testColl.getFullName(), find: {e: 9}, to: shard2})); assert.commandWorked( st.s.adminCommand({movechunk: testColl.getFullName(), find: {e: 12}, to: shard2})); // Find query. cursor = findChunksUtil .findChunksByNs( configDB, testColl.getFullName(), null, {_id: 0, min: 1, max: 1, shard: 1}) .sort({"min.e": 1}); assert.eq(cursor.next(), {min: {e: {"$minKey": 1}}, "max": {"e": 2}, shard: shard2}); assert.eq(cursor.next(), {min: {e: 2}, max: {e: 6}, shard: shard1}); assert.eq(cursor.next(), {min: {e: 6}, max: {e: 8}, shard: shard1}); assert.eq(cursor.next(), {min: {e: 8}, max: {e: 11}, shard: shard2}); assert.eq(cursor.next(), {min: {e: 11}, max: {e: {"$maxKey": 1}}, shard: shard2}); assert(!cursor.hasNext()); // Count query with filter. assert.eq(findChunksUtil.countChunksForNs(configDB, testColl.getFullName()), 5); // Distinct query. assert.eq(configDB.chunks.distinct("shard").sort(), [shard1, shard2]); // Map reduce query. const coll = configDB.collections.findOne({_id: testColl.getFullName()}); var mapFunction = function() { if (xx.timestamp) { if (this.uuid.toString() == xx.uuid.toString()) { emit(this.shard, 1); } } else { if (this.ns == "test2.testColl") { emit(this.shard, 1); } } }; var reduceFunction = function(key, values) { return {chunks: values.length}; }; result = configDB.chunks.mapReduce( mapFunction, reduceFunction, {out: {inline: 1}, scope: {xx: {timestamp: coll.timestamp, uuid: coll.uuid}}}); assert.eq(result.ok, 1); assert.eq(sortArrayById(result.results), [{_id: shard1, value: {chunks: 2}}, {_id: shard2, value: {chunks: 3}}]); }; /** * Test queries over a user created collection of an arbitrary database on the config servers. */ var queryUserCreated = function(database) { var userColl = database.userColl; var userCollData = [ {_id: 1, g: 1, c: 4, s: "c", u: [1, 2]}, {_id: 2, g: 1, c: 5, s: "b", u: [1]}, {_id: 3, g: 2, c: 16, s: "g", u: [3]}, {_id: 4, g: 2, c: 1, s: "a", u: [2, 4]}, {_id: 5, g: 2, c: 18, s: "d", u: [3]}, {_id: 6, g: 3, c: 11, s: "e", u: [2, 3]}, {_id: 7, g: 3, c: 2, s: "f", u: [1]} ]; var userCollIndexes = ["_id_", "s_1"]; var cursor; var cursorArray; var result; // Dropping collections under config is not allowed via mongos. let configPrimary = st.configRS.getPrimary(); dropCollectionIfExists(configPrimary.getDB(database.getName()).userColl); for (var i = 0; i < userCollData.length; i++) { assert.commandWorked(userColl.insert(userCollData[i])); } assert.commandWorked(userColl.createIndex({s: 1})); // List indexes. cursorArray = []; cursor = getListIndexesCursor(userColl, {cursor: {batchSize: 1}}, 1); assert.eq(cursor.objsLeftInBatch(), 1); cursorArray.push(cursor.next()); assert(cursor.hasNext()); assert.eq(cursor.objsLeftInBatch(), 1); cursorArray.push(cursor.next()); assert(!cursor.hasNext()); assert.eq(arrayGetNames(sortArrayByName(cursorArray)), userCollIndexes); // Find query. cursor = userColl.find({g: {$gte: 2}}, {_id: 0, c: 1}).sort({s: 1}).batchSize(2); assert.eq(cursor.objsLeftInBatch(), 2); assert.eq(cursor.next(), {c: 1}); assert.eq(cursor.objsLeftInBatch(), 1); assert.eq(cursor.next(), {c: 18}); assert(cursor.hasNext()); assert.eq(cursor.objsLeftInBatch(), 2); assert.eq(cursor.next(), {c: 11}); assert.eq(cursor.objsLeftInBatch(), 1); assert.eq(cursor.next(), {c: 2}); assert(cursor.hasNext()); assert.eq(cursor.objsLeftInBatch(), 1); assert.eq(cursor.next(), {c: 16}); assert(!cursor.hasNext()); // Aggregate query. cursor = userColl.aggregate( [ {$match: {c: {$gt: 1}}}, {$unwind: "$u"}, {$group: {_id: "$u", sum: {$sum: "$c"}}}, {$sort: {_id: 1}} ], {cursor: {batchSize: 2}}); assert.eq(cursor.objsLeftInBatch(), 2); assert.eq(cursor.next(), {_id: 1, sum: 11}); assert.eq(cursor.next(), {_id: 2, sum: 15}); assert(cursor.hasNext()); assert.eq(cursor.objsLeftInBatch(), 1); assert.eq(cursor.next(), {_id: 3, sum: 45}); assert(!cursor.hasNext()); // Count query without filter. assert.eq(userColl.count(), userCollData.length); // Count query with filter. assert.eq(userColl.count({g: 2}), 3); // Distinct query. assert.eq(userColl.distinct("g").sort(), [1, 2, 3]); // Map reduce query. var mapFunction = function() { emit(this.g, 1); }; var reduceFunction = function(key, values) { return {count: values.length}; }; result = userColl.mapReduce(mapFunction, reduceFunction, {out: {inline: 1}}); assert.eq(result.ok, 1); assert.eq( sortArrayById(result.results), [{_id: 1, value: {count: 2}}, {_id: 2, value: {count: 3}}, {_id: 3, value: {count: 2}}]); }; var st = new ShardingTest({shards: 2, mongos: 1}); var testNamespaces = setupTestCollections(st); var configDB = st.s.getDB("config"); var adminDB = st.s.getDB("admin"); testListConfigCollections(st); testListConfigChunksIndexes(st); queryConfigCollections(st, testNamespaces); queryConfigChunks(st); queryUserCreated(configDB); queryUserCreated(adminDB); st.stop(); })();