diff options
Diffstat (limited to 'jstests/noPassthrough/clustered_collection_sorted_scan.js')
| -rw-r--r-- | jstests/noPassthrough/clustered_collection_sorted_scan.js | 236 |
1 files changed, 46 insertions, 190 deletions
diff --git a/jstests/noPassthrough/clustered_collection_sorted_scan.js b/jstests/noPassthrough/clustered_collection_sorted_scan.js index 8163375fa62..103f7094b9a 100644 --- a/jstests/noPassthrough/clustered_collection_sorted_scan.js +++ b/jstests/noPassthrough/clustered_collection_sorted_scan.js @@ -17,9 +17,9 @@ Random.setRandomSeed(); const testConnection = MongoRunner.runMongod({setParameter: {supportArbitraryClusterKeyIndex: true}}); const testDb = testConnection.getDB('local'); -const collectionSize = 10; +const collectionSize = 200; const clusteredCollName = "clustered_index_sorted_scan_coll"; -const clusterField = "_id"; +const clusterField = "clusterKey"; let nonClusteredCollName = clusteredCollName + "_nc"; @@ -30,6 +30,7 @@ let clusteredColl = testDb[clusteredCollName]; // Generate a non-clustered collection for comparison assert.commandWorked(testDb.createCollection(nonClusteredCollName)); +assert.commandWorked(testDb[nonClusteredCollName].createIndex({[clusterField]: 1}, {unique: true})); let nonClusteredColl = testDb[nonClusteredCollName]; // Put something in the collections so the planner has something to chew on. @@ -39,20 +40,20 @@ for (let i = 0; i < collectionSize; ++i) { assert.commandWorked(nonClusteredColl.insert({[clusterField]: i, a: a})); } -function runTest(isClustered, hasFilter, hasHint, direction) { +function runTest(isClustered, hasFilter, direction) { let tsColl = isClustered ? clusteredColl : nonClusteredColl; - const filter = hasFilter ? {[clusterField]: {$gt: -1}} : {}; - const sort = {[clusterField]: direction}; - const hint = hasHint ? {[clusterField]: 1} : {}; - - let query = tsColl.find(filter).sort(sort).hint(hint); + let query; + if (hasFilter) { + query = tsColl.find({[clusterField]: {$gt: -1}}).sort({[clusterField]: direction}); + } else { + query = tsColl.find().sort({[clusterField]: direction}); + } function formatParamsAndPlan(plan) { let params = { isClustered: isClustered ? "true" : "false", hasFilter: hasFilter ? "true" : "false", - hasHint: hasHint ? "true" : "false", direction: direction ? "forward" : "backward", }; @@ -75,16 +76,12 @@ function runTest(isClustered, hasFilter, hasHint, direction) { assert(!planHasStage(testDb, plan, "SORT"), "Unexpected sort in " + formatParamsAndPlan(plan)); } -function testCollations(collectionCollation, queryCollation, direction) { - const collationsMatch = collectionCollation == queryCollation; - +function testCollations() { let strCollName = clusteredCollName + "_str"; // Generate a clustered collection for the remainder of the testing - assert.commandWorked(testDb.createCollection(strCollName, { - clusteredIndex: {key: {[clusterField]: 1}, unique: true}, - collation: collectionCollation - })); + assert.commandWorked(testDb.createCollection( + strCollName, {clusteredIndex: {key: {[clusterField]: 1}, unique: true}})); let tsColl = testDb[strCollName]; @@ -93,136 +90,36 @@ function testCollations(collectionCollation, queryCollation, direction) { assert.commandWorked(tsColl.insert({[clusterField]: i.toString(), a: Math.random()})); } - function runExplain(filter) { - return tsColl.find(filter) - .sort({[clusterField]: direction}) - .collation(queryCollation) - .explain(); - } - - // - // Some queries need an explicit sort only when the query/collection collations do not match. - // - function assertPlanOnlyHasSortIfCollationsDontMatch(plan) { - if (collationsMatch) { - assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - } else { - assert(planHasStage(testDb, plan, "SORT"), "Expected sort in " + tojson(plan)); - } - } - - // Empty match. - let plan = runExplain({}); - assertPlanOnlyHasSortIfCollationsDontMatch(plan); - - // Comparison against a field other than the cluster field. - plan = runExplain({a: {$lt: 2}}); - assertPlanOnlyHasSortIfCollationsDontMatch(plan); - - // Query which contains an unsupported match expression. - plan = runExplain({$or: [{[clusterField]: {$lt: 2}}, {[clusterField]: {$gt: 5}}]}); - assertPlanOnlyHasSortIfCollationsDontMatch(plan); - - // Conjunction with one child which is an unsupported match expression and another which is a - // comparison against a field other than the cluster field. - plan = runExplain( - {$and: [{$or: [{[clusterField]: {$lt: 2}}, {[clusterField]: {$gt: 5}}]}, {a: {$gt: -1}}]}); - assertPlanOnlyHasSortIfCollationsDontMatch(plan); - - // Match which compares the cluster field to a string. - plan = runExplain({[clusterField]: {$gt: "1"}}); - assertPlanOnlyHasSortIfCollationsDontMatch(plan); - - // Match which compares the cluster field to an object containing a string. - plan = runExplain({[clusterField]: {$eq: {a: "str"}}}); - assertPlanOnlyHasSortIfCollationsDontMatch(plan); - - // Match which compares the cluster field to an array containing a string. - plan = runExplain({[clusterField]: {$eq: [1, 2, "str"]}}); - assertPlanOnlyHasSortIfCollationsDontMatch(plan); - - // $in query where one of the elements is a string. - plan = runExplain({[clusterField]: {$in: [1, "2", 3]}}); - assertPlanOnlyHasSortIfCollationsDontMatch(plan); - - // Conjunction with one child which compares the cluster field to a string and another which - // is a comparison against a field other than the cluster field. - plan = runExplain({$and: [{[clusterField]: "str"}, {a: 5}]}); - assertPlanOnlyHasSortIfCollationsDontMatch(plan); - - // Conjunction with one $in child which compares the cluster field to a string and another - // which is a comparison against a field other than the cluster field. - plan = runExplain({$and: [{[clusterField]: {$in: [1, "2", 3]}}, {a: 5}]}); - assertPlanOnlyHasSortIfCollationsDontMatch(plan); - - // - // Some queries can omit the explicit sort regardless of collations. This is the case when - // we can exclude string values of the cluster key in the output. - // - - // Simple comparison on cluster key which omits strings. - plan = runExplain({[clusterField]: {$gt: -1}}); + // Run query with Faroese collation, just to choose something unlikely. + // Because the collations don't match, we can't use the clustered index + // to provide a sort + let plan = tsColl.find() + .sort({[clusterField]: 1}) + .collation({locale: "fo", caseLevel: true}) + .explain(); + assert(planHasStage(testDb, plan, "SORT"), "Expected sort in " + tojson(plan)); + + // However, if we can exclude strings, we don't need an explicit sort even + // if the collations don't match + plan = tsColl.find({[clusterField]: {$gt: -1}}) + .sort({[clusterField]: 1}) + .collation({locale: "fo", caseLevel: true}) + .explain(); assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - plan = runExplain({[clusterField]: {$eq: {a: 5}}}); - assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - plan = runExplain({[clusterField]: {$eq: [1, 2, 3]}}); - assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - - // Conjunction with multiple comparisons on cluster key which omits strings. - plan = runExplain({$and: [{[clusterField]: {$gt: -1}}, {[clusterField]: {$lt: 10}}]}); - assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - - // $in query against cluster key which omits strings. - plan = runExplain({[clusterField]: {$in: [1, 2, 3]}}); - assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - - // Conjunction of $in query against cluster key and another comparison on a field other than - // the cluster key. The first conjunct omits strings. - plan = runExplain({$and: [{[clusterField]: {$in: [1, 2, 3]}}, {a: 5}]}); - assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - - // Conjunction with one comparison against the cluster key and one against another field. The - // second conjunct omits strings. - plan = runExplain({$and: [{a: {$lt: 2}}, {[clusterField]: {$gt: -1}}]}); - assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - - // Conjunction with one child which is an unsupported match expression and another which is - // a comparison against the cluster field. The second conjunct omits strings. - plan = runExplain({ - $and: [ - {$or: [{[clusterField]: {$lt: 2}}, {[clusterField]: {$gt: 5}}]}, - {[clusterField]: {$gt: -1}} - ] - }); - assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - - // Conjunction which contains a comparison of the cluster field to a string and a comparison - // of the cluster field to a number. The second conjunct omits strings. - plan = runExplain({$and: [{[clusterField]: {$lt: "1"}}, {[clusterField]: {$gt: 2}}]}); - assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - - // Conjunction which contains a $in comparison of the cluster field to a string and a $in - // comparison of the cluster field to a number. The second conjunct omits strings. - plan = runExplain( - {$and: [{[clusterField]: {$in: [1, "2", 3]}}, {[clusterField]: {$in: [1, 3, 4]}}]}); - assert(!planHasStage(testDb, plan, "SORT"), "Unxpected sort in " + tojson(plan)); - tsColl.drop(); } -function testMinMax() { - // Min and max are only supported on forward collection scans. - const direction = 1; +function testMinMax(direction) { // Min and max should be between 0 and collection size - const minResult = 5; // inclusive - const maxResult = 8; // not inclusive + const minResult = 10; // inclusive + const maxResult = 20; // not inclusive const resultCount = maxResult - minResult; let normalCursor = nonClusteredColl.find() .hint({[clusterField]: 1}) .min({[clusterField]: minResult}) .max({[clusterField]: maxResult}) - .sort({[clusterField]: direction}); + .sort({[clusterField]: 1}); let normalResult = normalCursor.toArray(); assert.eq(normalResult.length, resultCount, @@ -232,7 +129,7 @@ function testMinMax() { .hint({[clusterField]: 1}) .min({[clusterField]: minResult}) .max({[clusterField]: maxResult}) - .sort({[clusterField]: direction}); + .sort({[clusterField]: 1}); let clusterResult = clusterCursor.toArray(); assert.eq(clusterResult.length, resultCount, @@ -244,38 +141,24 @@ function testMinMax() { } // Ensure that the plan gets cached correctly -function testPlanCache(direction) { +function testPlanCache() { clusteredColl.getPlanCache().clear(); const indexName = "_a"; assert.commandWorked(clusteredColl.createIndex({a: 1}, {name: indexName})); - const filter = {a: {$gt: -1}}; - const projection = {[clusterField]: 1}; - const sort = {[clusterField]: direction}; - // Because of the _a index above, we should have two alternatves -- filter via the // index then a blocking sort, or filter during a collection scan. Because of the blocking // sort and the fact that "a" doesnt actually filter anything, we expect the // collection scan to win. - let plan = clusteredColl.find(filter, projection).sort(sort).explain(); + let query = clusteredColl.find({a: {$gt: -1}}).sort({[clusterField]: -1}); + + let plan = query.explain(); assert(plan.queryPlanner.rejectedPlans.length > 0, tojson(plan)); assert(planHasStage(testDb, plan, "COLLSCAN"), "Expected COLLSCAN in " + tojson(plan)); - let nonClusteredResults = nonClusteredColl.find(filter, projection).sort(sort).toArray(); - assert.eq(nonClusteredResults.length, collectionSize); - - // Now run the query and verify that the results are expected. Run it a few times so that the - // cached plan will be used. - assert.eq(nonClusteredResults, - clusteredColl.find(filter, projection).sort(sort).toArray(), - tojson(plan)); - assert.eq(nonClusteredResults, - clusteredColl.find(filter, projection).sort(sort).toArray(), - tojson(plan)); - assert.eq(nonClusteredResults, - clusteredColl.find(filter, projection).sort(sort).toArray(), - tojson(plan)); + // Run the query to ensure it's cached + query.toArray(); // Verify that there's a cache entry for this query let cacheEntries = clusteredColl.getPlanCache().list(); @@ -288,43 +171,16 @@ function testPlanCache(direction) { // Actually run all the tests: for (let isClustered = 0; isClustered <= 1; isClustered++) { for (let hasFilter = 0; hasFilter <= 1; hasFilter++) { - for (let hasHint = 0; hasHint <= 1; hasHint++) { - runTest(isClustered, hasFilter, hasHint, /* direction = */ 1); - runTest(isClustered, hasFilter, hasHint, /* direction = */ -1); - } + runTest(isClustered, hasFilter, /* direction = */ 1); + runTest(isClustered, hasFilter, /* direction = */ -1); } } -// -// Show that the direction of the sort does not affect the plans we are able to provide. Also show -// the collation conditions under which we can avoid explicit sorts in the final plan. -// - -const defaultCollation = { - locale: "simple", -}; -const faroeseCollation = { - locale: "fo", - caseLevel: true -}; - -testCollations( - defaultCollation /* for collection */, faroeseCollation /* for query */, /* direction = */ 1); -testCollations( - defaultCollation /* for collection */, faroeseCollation /* for query */, /* direction = */ -1); -testCollations( - faroeseCollation /* for collection */, faroeseCollation /* for query */, /* direction = */ 1); -testCollations( - faroeseCollation /* for collection */, faroeseCollation /* for query */, /* direction = */ -1); -testCollations( - defaultCollation /* for collection */, defaultCollation /* for query */, /* direction = */ 1); -testCollations( - defaultCollation /* for collection */, defaultCollation /* for query */, /* direction = */ -1); - -testMinMax(); - -testPlanCache(/* direction = */ 1); -testPlanCache(/* direction = */ -1); +testCollations(); +testMinMax(/* direction = */ 1); +testMinMax(/* direction = */ -1); + +testPlanCache(); // If we're sorting on multiple columns, we still need an explicit sort let plan = clusteredColl.find().sort({[clusterField]: 1, a: 1}).explain(); |
