-
Type:
Task
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: None
-
Component/s: None
-
Query Integration
-
None
-
None
-
None
-
None
-
None
-
None
-
None
h3. Problem
LiteParsedDocumentSourceNestedPipelines currently offers two constructor
families: one taking const LiteParserOptions& and one not. The options-taking form is the only one that populates the base class's {{_ifrContext}} member, read via {{getIfrContext()}}.
Every IFR kickback guard is written in the form:
{code:cpp}
if (const auto& ifrContext = getIfrContext(); ifrContext &&
!ifrContext->getSavedFlagValue(flag)) { ...raise kickback... }
The problem is that a stage can end up with a null context, and we may make the wrong decision based on this missing context wrt ifr flag features.
It was the root cause of the $lookup failure investigated under SERVER-132516: LiteParsedLookUp::getIfrContext() was always
null because LiteParsedLookUp's constructor used the no-options single-pipeline
overload. Fixed there by adding an options-taking variant and threading options
through, but only for $lookup.
Why the other stages were missed
Not a deliberate choice. The no-options constructors predate LiteParserOptions
entirely (5177f04b2af8). The options-taking overloads were added in exactly two
commits, each covering only the stage its author was working on at the time:
- 7dd2520d1e82 (
SERVER-125517, view infrastructure) – added the base
LiteParsedDocumentSource(originalBson, options) ctor and the
vector<OwnedLiteParsedPipeline> variant, and switched $graphLookup onto it. - faec23deb8f4 (
SERVER-132516) – added the single-pipeline variant for $lookup.
Everything else simply kept the older constructor. Notably, 7dd2520d1e82 edited
lite_parsed_union_with.cpp (in getStageParams()) without the constructor ever
coming into scope.
Scope
Stages still using a no-options constructor, i.e. getIfrContext() is null by
construction:
| Stage | Location |
|---|---|
| $unionWith | src/mongo/db/pipeline/lite_parsed_union_with.cpp:35 |
| $merge | src/mongo/db/pipeline/document_source_merge.h:87 |
| $rankFusion | src/mongo/db/pipeline/lite_parsed_rank_fusion.h:87 |
| $scoreFusion | src/mongo/db/pipeline/lite_parsed_score_fusion.h:87 |
| $facet | src/mongo/db/pipeline/document_source_facet.h:112 |
| $out | src/mongo/db/pipeline/document_source_out.h:70 |
| InternalDocumentResultsAndMetadata | src/mongo/db/pipeline/lite_parsed_internal_document_results_and_metadata.h:89 |
| LiftSubpipeline (test stage) | src/mongo/db/pipeline/lite_parsed_desugarer_test.cpp:63 |
All eight parse() functions already receive a const LiteParserOptions&, so
threading it through is mechanical.
Proposed work
As part of this ticket, investigate feasibility of removing the no-options constructors from:
lite_parsed_document_source_nested_pipelines.h outright, rather than
converting call sites and leaving the overloads in place. Since the only
behavioural difference is whether _ifrContext is populated – and no stage
relies on a deliberate null – keeping them preserves a silent-wrong-answer
footgun for no benefit. Deleting them also resolves the boost::none foreignNss
overload-resolution ambiguity, because const LiteParserOptions& then always
occupies parameter position 2.
Thread options through the eight constructors and their parse() call sites.
buildUnionWithLPDS() (lite_parsed_hybrid_search_desugarer_utils.cpp:225) is the
one non-mechanical case: it synthesizes a $unionWith during hybrid-search
LP-desugaring and has no options in scope. Once $rankFusion/$scoreFusion carry a
real context, the desugarer can construct
{{LiteParserOptions
}} and thread it through
appendInputPipeline().
Add a regression test asserting getIfrContext() is non-null for every
nested-pipeline stage parsed with options carrying an IFR context, recursing
into sub-pipelines. This is the guard that would have caught the original
$lookup bug.
Risk
Behaviour-neutral. The stored _ifrContext has exactly one reader in the tree
today, LiteParsedLookUp::validate() (lite_parsed_lookup.cpp:267), added by
SERVER-132516. DocumentSourceExtensionOptimizable::LiteParsedExpanded declares its
own _ifrContext member (document_source_extension_optimizable.h:406) populated via
an explicit constructor argument, so it is unaffected. $graphLookup passes options
but never reads the result.
The desugarer change in item 3 is also neutral: the sub-pipeline stages inside a
synthesized $unionWith are already parsed with the real options by
LiteParsedRankFusion::parse. Only the synthetic $unionWith wrapper itself gains a
context, and nothing reads it.
Out of scope
Leaf stages remain exposed to the same failure mode. LiteParsedDocumentSource(originalBson)
and the DEFINE_LITE_PARSED_STAGE_DEFAULT_* macros construct without options and
have hundreds of call sites. Anyone adding an IFR kickback guard to a stage without
sub-pipelines will hit this bug again. Closing that gap should be its own ticket.
- is related to
-
SERVER-132516 $$SEARCH_META $search sub-pipeline inside $unionWith/$lookup fail on getMore with with_mongot_extension_hybridSearch_disabled_sharded_cluster
-
- Closed
-
-
SERVER-125517 Update view policy infrastructure to handle subpipelines
-
- Closed
-