diff options
author | Ivan Fefer <ivan.fefer@mongodb.com> | 2022-11-02 14:08:39 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-11-02 14:41:10 +0000 |
commit | 76e583ed12d536aba872920568b5324d1dcae713 (patch) | |
tree | bf548e3dd26a94e2d38b85924bdfd8293bdc9686 /jstests | |
parent | f509f99f4e55e38397a7bed3070a715ad6a7dcf9 (diff) | |
download | mongo-76e583ed12d536aba872920568b5324d1dcae713.tar.gz |
SERVER-58276 Add collscan plan if collection is clustered and collscan uses clustered index
Diffstat (limited to 'jstests')
-rw-r--r-- | jstests/core/cover_null_queries.js | 16 | ||||
-rw-r--r-- | jstests/core/profile_update.js | 27 | ||||
-rw-r--r-- | jstests/core/timeseries/timeseries_index_partial.js | 25 | ||||
-rw-r--r-- | jstests/core/timeseries/timeseries_index_use.js | 25 | ||||
-rw-r--r-- | jstests/sharding/timeseries_query.js | 2 |
5 files changed, 66 insertions, 29 deletions
diff --git a/jstests/core/cover_null_queries.js b/jstests/core/cover_null_queries.js index c17b9e71929..7054a16039d 100644 --- a/jstests/core/cover_null_queries.js +++ b/jstests/core/cover_null_queries.js @@ -11,6 +11,7 @@ load("jstests/aggregation/extras/utils.js"); // For arrayEq(). load("jstests/libs/analyze_plan.js"); // For getAggPlanStages() and getPlanStages(). +load("jstests/libs/clustered_collections/clustered_collection_util.js"); const coll = db.cover_null_queries; coll.drop(); @@ -95,6 +96,15 @@ function validateCountAggCmdOutputAndPlan({filter, expectedStages, expectedCount validateStages({cmdObj, expectedStages, isAgg: true}); } +function getExpectedStagesIndexScanAndFetch(extraStages) { + const clustered = ClusteredCollectionUtil.areAllCollectionsClustered(db.getMongo()); + const result = clustered ? {"CLUSTERED_IXSCAN": 1} : {"FETCH": 1, "IXSCAN": 1}; + for (const stage in extraStages) { + result[stage] = extraStages[stage]; + } + return result; +} + assert.commandWorked(coll.createIndex({a: 1, _id: 1})); // Verify count({a: null}) can be covered by an index. In the simplest case we can use two count @@ -272,18 +282,18 @@ validateFindCmdOutputAndPlan({ validateSimpleCountCmdOutputAndPlan({ filter: {a: null, _id: 3}, expectedCount: 1, - expectedStages: {"FETCH": 1, "IXSCAN": 1, "OR": 0, "COUNT_SCAN": 0} + expectedStages: getExpectedStagesIndexScanAndFetch({"OR": 0, "COUNT_SCAN": 0}), }); validateCountAggCmdOutputAndPlan({ filter: {a: null, _id: 3}, expectedCount: 1, - expectedStages: {"FETCH": 1, "IXSCAN": 1, "OR": 0, "COUNT_SCAN": 0}, + expectedStages: getExpectedStagesIndexScanAndFetch({"OR": 0, "COUNT_SCAN": 0}), }); validateFindCmdOutputAndPlan({ filter: {a: null, _id: 3}, projection: {_id: 1}, expectedOutput: [{_id: 3}], - expectedStages: {"IXSCAN": 1, "FETCH": 1, "PROJECTION_SIMPLE": 1}, + expectedStages: getExpectedStagesIndexScanAndFetch({"PROJECTION_SIMPLE": 1}), }); // Verify that if the index is multikey and the query searches for null and empty array values, then diff --git a/jstests/core/profile_update.js b/jstests/core/profile_update.js index deeeb8ee8a3..ebd0505e58d 100644 --- a/jstests/core/profile_update.js +++ b/jstests/core/profile_update.js @@ -100,12 +100,9 @@ assert.commandWorked(coll.update({_id: "new value", a: 4}, {$inc: {b: 1}}, {upse profileObj = getLatestProfilerEntry(testDB); const collectionIsClustered = ClusteredCollectionUtil.areAllCollectionsClustered(db.getMongo()); -// A clustered collection has no actual index on _id. While a bounded collection scan is in -// principle an efficient option, the query planner only defaults to collection scan if no suitable -// index is available. -const expectedPlan = collectionIsClustered ? "IXSCAN { a: 1 }" : "IXSCAN { _id: 1 }"; -const expectedKeysExamined = collectionIsClustered ? 1 : 0; -const expectedDocsExamined = expectedKeysExamined; +const expectedPlan = collectionIsClustered ? "CLUSTERED_IXSCAN" : "IXSCAN { _id: 1 }"; +const expectedKeysExamined = 0; +const expectedDocsExamined = collectionIsClustered ? 1 : 0; const expectedKeysInserted = collectionIsClustered ? 1 : 2; assert.eq(profileObj.command, @@ -149,13 +146,17 @@ for (var i = 0; i < indices.length; i++) { const profileObj = profiles[i]; const index = indices[i]; - // A clustered collection has no actual index on _id. While a bounded collection scan is in - // principle an efficient option, the query planner only defaults to collection scan if no - // suitable index is available. - const expectedPlan = collectionIsClustered ? "IXSCAN { a: 1 }" : "IXSCAN { _id: 1 }"; - const expectedKeysExamined = collectionIsClustered ? 1 : 0; - const expectedDocsExamined = expectedKeysExamined; - const expectedKeysInserted = collectionIsClustered ? 1 : 2; + let expectedPlan = "IXSCAN { _id: 1 }"; + let expectedKeysExamined = 0; + let expectedDocsExamined = 0; + let expectedKeysInserted = 2; + + if (collectionIsClustered) { + expectedPlan = "CLUSTERED_IXSCAN"; + expectedKeysExamined = 0; + expectedDocsExamined = (i + 1 == indices.length) ? 1 : 2; + expectedKeysInserted = 1; + } assert.eq( profileObj.command, diff --git a/jstests/core/timeseries/timeseries_index_partial.js b/jstests/core/timeseries/timeseries_index_partial.js index 55ece446457..7d97209173e 100644 --- a/jstests/core/timeseries/timeseries_index_partial.js +++ b/jstests/core/timeseries/timeseries_index_partial.js @@ -97,10 +97,24 @@ assert.commandFailedWithCode(coll.createIndex({a: 1}, {partialFilterExpression: // Test creating and using a partial index. { - // Make sure the query uses the {a: 1} index. + let ixscanInWinningPlan = 0; + + // Make sure the {a: 1} index was considered for this query. function checkPlan(predicate) { const explain = coll.find(predicate).explain(); - const scan = getAggPlanStage(explain, 'IXSCAN'); + let scan = getAggPlanStage(explain, 'IXSCAN'); + // If scan is not present, check rejected plans + if (scan === null) { + const rejectedPlans = getRejectedPlans(getAggPlanStage(explain, "$cursor")["$cursor"]); + if (rejectedPlans.length === 1) { + const scans = getPlanStages(getRejectedPlan(rejectedPlans[0]), "IXSCAN"); + if (scans.length === 1) { + scan = scans[0]; + } + } + } else { + ixscanInWinningPlan++; + } const indexes = buckets.getIndexes(); assert(scan, "Expected an index scan for predicate: " + tojson(predicate) + @@ -109,7 +123,7 @@ assert.commandFailedWithCode(coll.createIndex({a: 1}, {partialFilterExpression: } // Make sure the query results match a collection-scan plan. function checkResults(predicate) { - const result = coll.aggregate({$match: predicate}).toArray(); + const result = coll.aggregate([{$match: predicate}], {hint: {a: 1}}).toArray(); const unindexed = coll.aggregate([{$_internalInhibitOptimization: {}}, {$match: predicate}]).toArray(); assert.docEq(result, unindexed); @@ -152,10 +166,6 @@ assert.commandFailedWithCode(coll.createIndex({a: 1}, {partialFilterExpression: const t1 = ISODate('2000-01-01T00:00:01Z'); const t2 = ISODate('2000-01-01T00:00:02Z'); - // When the collection is sharded, there is an index on time that can win, instead of the - // partial index. So only check the results in that case, not the plan. - const check = FixtureHelpers.isSharded(buckets) ? checkResults : checkPlanAndResults; - assert.commandWorked(coll.dropIndex({a: 1})); assert.commandWorked( coll.createIndex({a: 1}, {partialFilterExpression: {[timeField]: {$lt: t1}}})); @@ -184,6 +194,7 @@ assert.commandFailedWithCode(coll.createIndex({a: 1}, {partialFilterExpression: assert.commandWorked(coll.dropIndex({a: 1})); assert.sameMembers(coll.getIndexes(), extraIndexes); assert.sameMembers(buckets.getIndexes(), extraBucketIndexes); + assert.gt(ixscanInWinningPlan, 0); } // Check that partialFilterExpression can use a mixture of metadata, time, and measurement fields, diff --git a/jstests/core/timeseries/timeseries_index_use.js b/jstests/core/timeseries/timeseries_index_use.js index b1106e4ead0..ebcea9d8e6b 100644 --- a/jstests/core/timeseries/timeseries_index_use.js +++ b/jstests/core/timeseries/timeseries_index_use.js @@ -58,8 +58,9 @@ const generateTest = (useHint) => { /** * Creates the index specified by the spec and options, then explains the query to ensure - * that the created index is used. Runs the query and verifies that the expected number of - * documents are matched. Finally, deletes the created index. + * that the created index is used or was considered by multi-planner. + * Runs the query and verifies that the expected number of documents are matched. + * Finally, deletes the created index. */ const testQueryUsesIndex = function( filter, numMatches, indexSpec, indexOpts = {}, queryOpts = {}) { @@ -75,9 +76,23 @@ const generateTest = (useHint) => { assert.eq(numMatches, query.itcount()); const explain = query.explain(); - const ixscan = getAggPlanStage(explain, "IXSCAN"); - assert.neq(null, ixscan, tojson(explain)); - assert.eq("testIndexName", ixscan.indexName, tojson(ixscan)); + if (useHint) { + const ixscan = getAggPlanStage(explain, "IXSCAN"); + assert.neq(null, ixscan, tojson(explain)); + assert.eq("testIndexName", ixscan.indexName, tojson(ixscan)); + } else { + let ixscan = getAggPlanStage(explain, "IXSCAN"); + // If ixscan is not present, check rejected plans + if (ixscan === null) { + const rejectedPlans = + getRejectedPlans(getAggPlanStage(explain, "$cursor")["$cursor"]); + assert.eq(1, rejectedPlans.length); + const ixscans = getPlanStages(getRejectedPlan(rejectedPlans[0]), "IXSCAN"); + assert.eq(1, ixscans.length); + ixscan = ixscans[0]; + } + assert.eq("testIndexName", ixscan.indexName, tojson(ixscan)); + } assert.commandWorked(coll.dropIndex("testIndexName")); }; diff --git a/jstests/sharding/timeseries_query.js b/jstests/sharding/timeseries_query.js index 4cf3b1c28c5..7a8b835c876 100644 --- a/jstests/sharding/timeseries_query.js +++ b/jstests/sharding/timeseries_query.js @@ -122,7 +122,7 @@ function runQuery( if (expectCollScan) { assert(isCollscan(sDB, winningPlan)); } else { - assert(isIxscan(sDB, winningPlan)); + assert(isIxscan(sDB, winningPlan) || isClusteredIxscan(sDB, winningPlan)); } }); }); |