SERVER-121851 Handle empty pipelines on views in $rankFusion and $scoreFusion (#49909)

GitOrigin-RevId: 0f13b61d08f4e8206752b1ed0fe6d5cdec4900fd
This commit is contained in:
Daniel Segel 2026-03-19 07:00:38 -04:00 committed by MongoDB Bot
parent 8eafdcf18d
commit 36eb45c51b
3 changed files with 59 additions and 16 deletions

View File

@ -254,3 +254,19 @@ const runRankFusionViewTest =
testHybridSearchViewWithSubsequentUnionOnDifferentView(rankFusionInputPipelines,
createRankFusionPipeline);
})();
// $rankFusion with an empty input pipeline should trigger the correct error.
(function testRankFusionEmptyPipelineOnView() {
const collName = jsTestName() + "_empty_pipeline_coll";
const viewName = jsTestName() + "_empty_pipeline_view";
db[collName].drop();
assert.commandWorked(db.createView(viewName, collName, []));
assert.commandFailedWithCode(
db.runCommand({
aggregate: viewName,
pipeline: [{$rankFusion: {input: {pipelines: {p: []}}}}],
cursor: {},
}),
9834300,
);
})();

View File

@ -351,3 +351,19 @@ const runScoreFusionViewTest =
testHybridSearchViewWithSubsequentUnionOnDifferentView(scoreFusionInputPipelines,
createScoreFusionPipeline);
})();
// $scoreFusion with an empty input pipeline should trigger the correct error.
(function testScoreFusionEmptyPipelineOnView() {
const collName = jsTestName() + "_empty_pipeline_coll";
const viewName = jsTestName() + "_empty_pipeline_view";
db[collName].drop();
assert.commandWorked(db.createView(viewName, collName, []));
assert.commandFailedWithCode(
db.runCommand({
aggregate: viewName,
pipeline: [{$scoreFusion: {input: {pipelines: {p: []}, normalization: "none"}}}],
cursor: {},
}),
9402503,
);
})();

View File

@ -59,28 +59,39 @@ inline bool isMongotPipeline(const std::vector<BSONObj> pipeline) {
// }
//}) will become the first stage in the final desugared ouput. Thus, there is an extra recursive
// call to check for this.
if (pipeline.size() >= 1 &&
(pipeline[0][DocumentSourceSearch::kStageName] ||
pipeline[0][DocumentSourceVectorSearch::kStageName] ||
pipeline[0][DocumentSourceSearchMeta::kStageName] ||
pipeline[0][DocumentSourceListSearchIndexes::kStageName] ||
(pipeline[0][DocumentSourceRankFusion::kStageName] &&
isMongotPipeline(std::vector<BSONObj>{
pipeline[0][DocumentSourceRankFusion::kStageName][RankFusionSpec::kInputFieldName]
if (pipeline.empty()) {
return false;
}
const auto& firstStage = pipeline[0];
if (firstStage[DocumentSourceSearch::kStageName] ||
firstStage[DocumentSourceVectorSearch::kStageName] ||
firstStage[DocumentSourceSearchMeta::kStageName] ||
firstStage[DocumentSourceListSearchIndexes::kStageName]) {
return true;
}
if (firstStage[DocumentSourceRankFusion::kStageName]) {
auto rankFusionFirstPipeline =
firstStage[DocumentSourceRankFusion::kStageName][RankFusionSpec::kInputFieldName]
[RankFusionInputSpec::kPipelinesFieldName]
.Obj()
.firstElement()
.Array()[0]
.Obj()})) ||
(pipeline[0][DocumentSourceScoreFusion::kStageName] &&
isMongotPipeline(std::vector<BSONObj>{
pipeline[0][DocumentSourceScoreFusion::kStageName][ScoreFusionSpec::kInputFieldName]
.Array();
if (rankFusionFirstPipeline.empty()) {
return false;
}
return isMongotPipeline(std::vector<BSONObj>{rankFusionFirstPipeline[0].Obj()});
}
if (firstStage[DocumentSourceScoreFusion::kStageName]) {
auto scoreFusionFirstPipeline =
firstStage[DocumentSourceScoreFusion::kStageName][ScoreFusionSpec::kInputFieldName]
[ScoreFusionInputsSpec::kPipelinesFieldName]
.Obj()
.firstElement()
.Array()[0]
.Obj()})))) {
return true;
.Array();
if (scoreFusionFirstPipeline.empty()) {
return false;
}
return isMongotPipeline(std::vector<BSONObj>{scoreFusionFirstPipeline[0].Obj()});
}
return false;
}