summaryrefslogtreecommitdiff
path: root/jstests
diff options
context:
space:
mode:
authorIvan Fefer <ivan.fefer@mongodb.com>2022-11-02 14:08:39 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-11-02 14:41:10 +0000
commit76e583ed12d536aba872920568b5324d1dcae713 (patch)
treebf548e3dd26a94e2d38b85924bdfd8293bdc9686 /jstests
parentf509f99f4e55e38397a7bed3070a715ad6a7dcf9 (diff)
downloadmongo-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.js16
-rw-r--r--jstests/core/profile_update.js27
-rw-r--r--jstests/core/timeseries/timeseries_index_partial.js25
-rw-r--r--jstests/core/timeseries/timeseries_index_use.js25
-rw-r--r--jstests/sharding/timeseries_query.js2
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));
}
});
});