In the repro, we have a sharded collection, and define a view that sets b=0. This document should get matched in our lookup query, but does not for some reason. This bug repros on master and 9.0, but not 8.3.7
The $lookup executes in classic
A quick claude session says the following:
Root-caused. It's a router→shard serialization gap, not a bug in $lookup execution itself.
What happens
The failing shape is: $lookup with localField/foreignField only (no user pipeline) against a view whose pipeline computes the foreign field.
Standalone works (verified — returns the joined doc). Sharded fails. I profiled the shard and captured exactly what mongos sends on the view-resolution retry:
{$lookup: {from: "foreign", as: "c", localField: "a", foreignField: "b",
$_internalFromIsAView: true, let: {},
pipeline: [\{$addFields: {b: {$const: 0}}}]}}Note what's missing: $_internalFieldMatchPipelineIdx. The router prepends the view's stages into pipeline (document_source_lookup.cpp:1191) but the guard that emits the placeholder index requires _userPipeline to be set (document_source_lookup.cpp:1110), which it isn't for lf/ff-only syntax. So the shard is told what the view stages are, but not where the equality $match belongs.
On the shard, from is now the concrete collection, so _fromNsIsAView is false at construction and resolvedPipelineHelper (:363-373) computes _fieldMatchPipelineIdx = 0. The subpipeline becomes:
[\{$match: {b: 0}}, \{$addFields: {b: 0}}] // should be the reverse
The join predicate runs before the view computes b, so nothing matches → c: [].
Two secondary bugs found while confirming this
I tried hand-feeding the missing index to a standalone mongod ($_internalFieldMatchPipelineIdx: 1 and 2, with $_internalFromIsAView: true). Both still returned c.length = 0, because of:
1. createFromStageParams:634 — when internalFromIsAView && hasLocalFieldForeignFieldJoin() && internalFieldMatchPipelineIdx, it discards the router's index and recomputes via computeDesugaredMongotFieldMatchIdx(). For a non-mongot pipeline there is no source/support stage, so it hits the !lastPrefixIdx fallback at :556 returning size() - 1. This branch is only correct for mongot subpipelines; it has no non-mongot guard. (Active here — featureFlagExtensionsInsideHybridSearch is on in the shard command.)
2. relocateFieldMatchPlaceholder:517-518 — the if (newIdx > oldIdx) --newIdx; decrement. The router's index is a position in the final placeholder-containing pipeline, so decrementing after the erase lands the $match one slot too early: target 1 on [$match{}, $addFields] yields [$match, $addFields] again.So even the pipeline-syntax-plus-lf/ff variant on a computed-field view is suspect, not just the no-pipeline case.
- is related to
-
SERVER-132963 Create more thorough testing for $lookup w/ varying view and user pipeline sizes
-
- Backlog
-