SERVER-118433 Desugar extensions in $graphLookup doc source and agg stage (#47188)

GitOrigin-RevId: 5dac51c09e6663d4e34f1da1612d9670ec3b801e
This commit is contained in:
Mariano Shaar 2026-01-30 10:20:33 -05:00 committed by MongoDB Bot
parent 8b146a6f57
commit ef6f2fc7b2
10 changed files with 38 additions and 36 deletions

View File

@ -67,7 +67,6 @@ selector:
- jstests/with_mongot/e2e/hybridSearch/score_fusion_on_view.js
- jstests/with_mongot/e2e/hybridSearch/score_fusion_score_details_test.js
- jstests/with_mongot/e2e/hybridSearch/score_multiple_stages.js
- jstests/with_mongot/e2e/views/mongot_stage_in_view_definition_graph_lookup.js
exclude_with_any_tags:
- requires_sharding
roots:

View File

@ -80,7 +80,6 @@ selector:
- jstests/with_mongot/e2e/hybridSearch/score_fusion_on_view.js
- jstests/with_mongot/e2e/hybridSearch/score_fusion_score_details_test.js
- jstests/with_mongot/e2e/hybridSearch/score_multiple_stages.js
- jstests/with_mongot/e2e/views/mongot_stage_in_view_definition_graph_lookup.js
- jstests/with_mongot/e2e/metadata/meta_dependency_validation.js
- jstests/with_mongot/e2e/views/vector_search/unionWith.js
- jstests/with_mongot/e2e/hybridSearch/ranked_fusion_verbose_replace_root_test.js

View File

@ -31,9 +31,6 @@
- jstests/with_mongot/e2e/hybridSearch/score_fusion_score_details_test.js
- jstests/with_mongot/e2e/hybridSearch/score_multiple_stages.js
# TODO SERVER-118433: Remove this exclusion.
- jstests/with_mongot/e2e/views/mongot_stage_in_view_definition_graph_lookup.js
# Exclude tests that are incompatible with sharded extension-enabled configuration.
# This includes all exclusions from exclude_incompatible_tests plus sharded-only failures.
- name: exclude_sharded_incompatible_tests
@ -63,9 +60,6 @@
- jstests/with_mongot/e2e/hybridSearch/score_fusion_score_details_test.js
- jstests/with_mongot/e2e/hybridSearch/score_multiple_stages.js
# TODO SERVER-118433: Remove this exclusion.
- jstests/with_mongot/e2e/views/mongot_stage_in_view_definition_graph_lookup.js
# TODO SERVER-118499: Remove this exclusion when sortKey is propogated correctly.
- jstests/with_mongot/e2e/metadata/meta_dependency_validation.js
- jstests/with_mongot/e2e/views/vector_search/unionWith.js

View File

@ -1,6 +1,6 @@
/**
* Test that $graphLookup can run on a view containing a $unionWith stage with an extension stage
* in its subpipeline.
* Test that $graphLookup can run on views containing extension stages, either directly in the
* view definition or within a $unionWith subpipeline.
*
* @tags: [featureFlagExtensionsAPI]
*/
@ -33,8 +33,8 @@ const numUsers = users.length;
const expectedUsersInView = numUsers - 1;
coll.insertMany(users);
function runGraphLookup(fromViewName, fromViewPipeline) {
assert.commandWorked(db.createView(fromViewName, collName, fromViewPipeline));
function runGraphLookup(fromViewName, fromViewPipeline, source = collName) {
assert.commandWorked(db.createView(fromViewName, source, fromViewPipeline));
// Sanity check to make sure the view returns what we expect.
const view = db[fromViewName];
@ -66,6 +66,25 @@ function runGraphLookup(fromViewName, fromViewPipeline) {
assert.commandWorked(coll.getDB().runCommand({drop: fromViewName}));
}
describe("$graphLookup with extension stages in view definition", function () {
it("should run $graphLookup on a view with a desugar/source stage in definition", function () {
runGraphLookup(collName + "_read_n_docs_view", [{$readNDocuments: {numDocs: expectedUsersInView}}]);
});
it("should run $graphLookup on a view with a 'transform' stage in definition", function () {
runGraphLookup(collName + "_extension_limit_view", [{$sort: {_id: 1}}, {$extensionLimit: expectedUsersInView}]);
});
it("should run $graphLookup on a nested view with extension stage in inner view definition", function () {
const nestedViewName = collName + "_nested_extension_view";
assert.commandWorked(
db.createView(nestedViewName, collName, [{$readNDocuments: {numDocs: expectedUsersInView}}]),
);
runGraphLookup(collName + "_nested_view", [], nestedViewName);
assert.commandWorked(coll.getDB().runCommand({drop: nestedViewName}));
});
});
describe("$graphLookup with $unionWith and extension stages", function () {
it("should run $graphLookup on a view with a desugar/source stage in subpipeline", function () {
runGraphLookup(collName + "_union_with_read_n_docs_view", [
@ -109,5 +128,6 @@ describe("$graphLookup with $unionWith and extension stages", function () {
},
},
]);
assert.commandWorked(coll.getDB().runCommand({drop: nestedViewName}));
});
});

View File

@ -549,7 +549,7 @@ std::unique_ptr<mongo::Pipeline> GraphLookUpStage::makePipeline(BSONObj match,
_fromExpCtx->setQuerySettingsIfNotPresent(pExpCtx->getQuerySettings());
std::unique_ptr<mongo::Pipeline> pipeline = mongo::pipeline_factory::makePipeline(
_fromPipeline, _fromExpCtx, pipeline_factory::kOptionsMinimal);
_fromPipeline, _fromExpCtx, pipeline_factory::kDesugarOnly);
try {
return pExpCtx->getMongoProcessInterface()->finalizeAndMaybePreparePipelineForExecution(
_fromExpCtx,
@ -596,7 +596,7 @@ std::unique_ptr<mongo::Pipeline> GraphLookUpStage::makePipeline(BSONObj match,
// We can now safely optimize and reattempt attaching the cursor source.
pipeline = mongo::pipeline_factory::makePipeline(
_fromPipeline, _fromExpCtx, pipeline_factory::kOptionsMinimal);
_fromPipeline, _fromExpCtx, pipeline_factory::kDesugarOnly);
return pExpCtx->getMongoProcessInterface()->finalizeAndMaybePreparePipelineForExecution(
_fromExpCtx,

View File

@ -286,12 +286,8 @@ TEST_F(LoadExtensionsTest, LoadMatchTopNDesugarExtensionSucceeds) {
// Full Parse expansion
{
auto parsedPipeline = pipeline_factory::makePipeline(pipeline,
expCtx,
{.optimize = false,
.alreadyOptimized = false,
.attachCursorSource = false,
.desugar = true});
auto parsedPipeline =
pipeline_factory::makePipeline(pipeline, expCtx, pipeline_factory::kDesugarOnly);
ASSERT_EQ(parsedPipeline->size(), 4U);
auto it = parsedPipeline->getSources().begin();

View File

@ -108,12 +108,7 @@ protected:
static std::vector<BSONObj> desugarAndSerialize(
const boost::intrusive_ptr<ExpressionContext>& expCtx, const BSONObj& stageSpec) {
std::vector<BSONObj> spec{stageSpec};
auto pipe = pipeline_factory::makePipeline(spec,
expCtx,
{.optimize = false,
.alreadyOptimized = false,
.attachCursorSource = false,
.desugar = true});
auto pipe = pipeline_factory::makePipeline(spec, expCtx, pipeline_factory::kDesugarOnly);
ASSERT_TRUE(pipe);
return pipe->serializeToBson();
}

View File

@ -486,8 +486,8 @@ boost::intrusive_ptr<DocumentSource> DocumentSourceGraphLookUp::clone(
void DocumentSourceGraphLookUp::addInvolvedCollections(
stdx::unordered_set<NamespaceString>* collectionNames) const {
collectionNames->insert(_fromExpCtx->getNamespaceString());
auto introspectionPipeline = pipeline_factory::makePipeline(
_fromPipeline, _fromExpCtx, pipeline_factory::kOptionsMinimal);
auto introspectionPipeline =
pipeline_factory::makePipeline(_fromPipeline, _fromExpCtx, pipeline_factory::kDesugarOnly);
for (auto&& stage : introspectionPipeline->getSources()) {
stage->addInvolvedCollections(collectionNames);
}

View File

@ -470,10 +470,9 @@ Value DocumentSourceUnionWith::serialize(const SerializationOptions& opts) const
std::move(recoveredPipeline),
_userNss);
} else {
pipeline_factory::MakePipelineOptions opts = pipeline_factory::kOptionsMinimal;
opts.desugar = true;
pipeCopy = pipeline_factory::makePipeline(
recoveredPipeline, _sharedState->_pipeline->getContext(), opts);
pipeCopy = pipeline_factory::makePipeline(recoveredPipeline,
_sharedState->_pipeline->getContext(),
pipeline_factory::kDesugarOnly);
}
} else {
// The plan does not require reading from the sub-pipeline, so just include the
@ -671,12 +670,9 @@ std::unique_ptr<Pipeline> DocumentSourceUnionWith::parsePipelineWithMaybeViewDef
src->constraints().isAllowedInUnionPipeline());
}
};
pipeline_factory::MakePipelineOptions opts;
opts.attachCursorSource = false;
// We will call optimize() when finalizing the pipeline in 'doGetNext()'.
opts.optimize = false;
auto opts = pipeline_factory::kDesugarOnly;
opts.validator = validatorCallback;
opts.desugar = true;
boost::intrusive_ptr<ExpressionContext> subExpCtx = makeCopyForSubPipelineFromExpressionContext(
expCtx, resolvedNs.ns, resolvedNs.uuid, userNss);

View File

@ -58,6 +58,9 @@ struct MakePipelineOptions {
static const MakePipelineOptions kOptionsMinimal{
.optimize = false, .alreadyOptimized = false, .attachCursorSource = false, .desugar = false};
static const MakePipelineOptions kDesugarOnly{
.optimize = false, .alreadyOptimized = false, .attachCursorSource = false, .desugar = true};
/**
* Factory functions for creating Pipeline objects from various input formats.
*