mirror of https://github.com/mongodb/mongo
SERVER-106643 Expose cursorType in explain for $cursor (#40592)
GitOrigin-RevId: 2da874fc9ce72c97227e40088bc10bd160ec9b65
This commit is contained in:
parent
f91be9182d
commit
ac174efbaf
|
|
@ -0,0 +1,64 @@
|
|||
// Tests the behavior of explain() when used with aggregation pipeline to
|
||||
// verify cursorType field. This test verifies that the cursorType field
|
||||
// is correctly exposed in the $cursor.queryPlanner stage explain output.
|
||||
//
|
||||
// @tags: [
|
||||
// assumes_unsharded_collection,
|
||||
// do_not_wrap_aggregations_in_facets,
|
||||
// ]
|
||||
import {checkSbeRestrictedOrFullyEnabled} from "jstests/libs/query/sbe_util.js";
|
||||
import {getAggPlanStage} from "jstests/libs/query/analyze_plan.js";
|
||||
|
||||
if (checkSbeRestrictedOrFullyEnabled(db)) {
|
||||
jsTest.log.info("Skipping test because $count queries don't use the emptyDocuments cursor in SBE");
|
||||
quit();
|
||||
}
|
||||
|
||||
const coll = db.explain_cursor;
|
||||
coll.drop();
|
||||
|
||||
const kNumDocs = 10;
|
||||
|
||||
for (let i = 0; i < kNumDocs; i++) {
|
||||
assert.commandWorked(coll.insert({_id: i, a: i, b: i % 2}));
|
||||
}
|
||||
|
||||
function getCursorType(explainOutput) {
|
||||
const cursorStage = getAggPlanStage(explainOutput, "$cursor");
|
||||
assert.neq(null, cursorStage, "No $cursor stage present");
|
||||
assert(cursorStage.$cursor.hasOwnProperty("queryPlanner"), "No $cursor.queryPlanner present");
|
||||
assert(cursorStage.$cursor.queryPlanner.hasOwnProperty("cursorType"), "No $cursor.queryPlanner.cursorType present");
|
||||
return cursorStage.$cursor.queryPlanner.cursorType;
|
||||
}
|
||||
|
||||
// Normal aggregation queries should use the regular cursor.
|
||||
const groupExplain = coll.explain().aggregate([{$match: {b: 1}}, {$group: {_id: "$a", count: {$sum: 1}}}]);
|
||||
assert.eq("regular", getCursorType(groupExplain));
|
||||
|
||||
const multiStageExplain = coll
|
||||
.explain()
|
||||
.aggregate([
|
||||
{$match: {a: {$lt: 8}}},
|
||||
{$project: {a: 1, category: {$cond: [{$gt: ["$a", 5]}, "high", "low"]}}},
|
||||
{$sort: {category: 1, a: -1}},
|
||||
]);
|
||||
assert.eq("regular", getCursorType(multiStageExplain));
|
||||
|
||||
// Count-like queries should use the emptyDocuments cursor.
|
||||
const countExplain = coll.explain().aggregate([{$match: {a: {$gte: 5}}}, {$count: "filtered"}]);
|
||||
assert.eq("emptyDocuments", getCursorType(countExplain));
|
||||
|
||||
const multiStageCountExplain = coll.explain().aggregate([
|
||||
{
|
||||
"$addFields": {
|
||||
"c": NumberInt(0),
|
||||
},
|
||||
},
|
||||
{
|
||||
"$project": {
|
||||
"_id": 0,
|
||||
"c": 1,
|
||||
},
|
||||
},
|
||||
]);
|
||||
assert.eq("emptyDocuments", getCursorType(multiStageCountExplain));
|
||||
|
|
@ -43,7 +43,11 @@ const mongosIgnoredFields = ["works", "needTime", "queryHash", "planCacheShapeHa
|
|||
stagesIgnoredFields,
|
||||
);
|
||||
|
||||
const queryPlannerIgnoredFields = ["optimizedPipeline", "optimizationTimeMillis"].concat(stagesIgnoredFields);
|
||||
// We ignore `cursorType` because it's only set when there's a $cursor stage, which could be
|
||||
// the case for the union but not for the regular query or vice versa.
|
||||
const queryPlannerIgnoredFields = ["optimizedPipeline", "optimizationTimeMillis", "cursorType"].concat(
|
||||
stagesIgnoredFields,
|
||||
);
|
||||
|
||||
function buildErrorString(unionExplain, realExplain, field) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -79,13 +79,15 @@ Value DocumentSourceCursor::serialize(const SerializationOptions& opts) const {
|
|||
BSONObjBuilder explainStatsBuilder;
|
||||
tassert(
|
||||
10769400, "Expected the plannerContext to be set for explain", _plannerContext.has_value());
|
||||
|
||||
BSONObj extraInfo = BSON("cursorType" << toString(_cursorType));
|
||||
Explain::explainStages(
|
||||
_sharedState->exec.get(),
|
||||
*_plannerContext,
|
||||
opts.verbosity.value(),
|
||||
_sharedState->execStatus,
|
||||
_winningPlanTrialStats,
|
||||
BSONObj(),
|
||||
extraInfo,
|
||||
SerializationContext::stateCommandReply(getExpCtx()->getSerializationContext()),
|
||||
BSONObj(),
|
||||
&explainStatsBuilder);
|
||||
|
|
|
|||
|
|
@ -203,6 +203,16 @@ private:
|
|||
friend boost::intrusive_ptr<exec::agg::Stage> documentSourceGeoNearCursorToStageFn(
|
||||
const boost::intrusive_ptr<DocumentSource>&);
|
||||
|
||||
static constexpr StringData toString(CursorType type) {
|
||||
switch (type) {
|
||||
case CursorType::kRegular:
|
||||
return "regular"_sd;
|
||||
case CursorType::kEmptyDocuments:
|
||||
return "emptyDocuments"_sd;
|
||||
}
|
||||
MONGO_UNREACHABLE;
|
||||
}
|
||||
|
||||
// Handle to catalog state.
|
||||
boost::intrusive_ptr<CatalogResourceHandle> _catalogResourceHandle;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue