-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Critical - P2
-
None
-
Affects Version/s: None
-
Component/s: None
-
Query Integration
-
ALL
-
None
-
None
-
None
-
None
-
None
-
None
-
None
Filed by the AI query bug hunter (qhunt), an agent that reviews query code for corner-case bugs. A differential oracle and the repro below confirmed the bug.
Problem
A view whose definition contains $unionWith on a second view returns the correct documents when it is read directly or through a top-level $unionWith. When the same view is the from of a $lookup, the second view's pipeline is not applied, so documents that the second view filters out are returned. Both $lookup forms (localField/foreignField and pipeline) are affected. The query returns no error, only extra rows. Default settings; no extensions.
Repro
Fails at master ba06f31d06c (2026-10-05). Self-contained jstests/noPassthrough-style test:
/** * F003: a $lookup with a pipeline over a view whose definition contains $unionWith on * another view does not apply the inner view's pipeline, so documents the inner view * filters out are returned. * * @tags: [ * requires_fcv_90, * ] */ const conn = MongoRunner.runMongod({setParameter: {internalQueryDisablePlanCache: true}}); const db = conn.getDB(jsTestName()); assert.commandWorked(db.local.insert({_id: 1, k: 1})); assert.commandWorked(db.foreign.insertMany([{_id: 1, x: 1, k: 1}, {_id: 2, x: 2, k: 1}])); assert.commandWorked(db.other.insertMany([{_id: 10, x: 1, k: 1}, {_id: 11, x: 2, k: 1}])); // innerV keeps only x: 1, so _id 11 must never appear through it. assert.commandWorked(db.createView("innerV", "other", [{$match: {x: 1}}])); assert.commandWorked(db.createView("outerV", "foreign", [{$unionWith: {coll: "innerV", pipeline: [{$set: {tag: "in"}}]}}])); const expectedIds = [1, 2, 10]; function ids(docs) { return docs.map((d) => d._id).sort((a, b) => a - b); } function run(label, fn) { const got = fn(); jsTest.log.info("F003 run", {label, ids: got}); return got; } const direct = run("aggregate on outerV", () => ids(db.outerV.aggregate([]).toArray())); run("top-level $unionWith outerV", () => ids(db.local.aggregate([{$match: {_id: -1}}, {$unionWith: {coll: "outerV", pipeline: []}}]).toArray())); run("$lookup localField/foreignField on outerV", () => ids(db.local.aggregate([{$lookup: {from: "outerV", localField: "k", foreignField: "k", as: "j"}}]).toArray()[0].j)); const lookupPipeline = run("$lookup pipeline on outerV", () => ids(db.local.aggregate([{$lookup: {from: "outerV", pipeline: [{$sort: {_id: 1}}], as: "j"}}]).toArray()[0].j)); // Control: reading the view directly applies both view pipelines. assert.eq(direct, expectedIds, "aggregate on outerV"); // Fails at ba06f31d06c: returns [1, 2, 10, 11]; innerV's $match is not applied. assert.eq(lookupPipeline, expectedIds, "$lookup pipeline on outerV"); MongoRunner.stopMongod(conn);
Results of the repro (innerV keeps only x: 1, so _id: 11 must never appear):
| Access to outerV | _id values returned |
|---|---|
| aggregate on outerV | 1, 2, 10 |
| top-level $unionWith of outerV | 1, 2, 10 |
| $lookup localField/foreignField | 1, 2, 10, 11 |
| $lookup with a pipeline | 1, 2, 10, 11 |
Possible cause (from the hunter's code reading, not verified)
The execution-time view binder for the $lookup subpipeline resolves nested levels with bindOnly=true, so the inner $unionWith is marked as targeting innerV without prepending innerV's pipeline. SERVER-131409 fixed nested view binding for the extension $search path; this looks like the same gap on the plain $unionWith path.
Impact
Silent wrong results for any $lookup whose from is a view built with $unionWith over another view.
Related
SERVER-131409: nested $unionWith/$lookup on views dropped results for extension $search.SERVER-129249: view filter dropped for $rankFusion (desugared $unionWith) in a localField/foreignField $lookup on a view. Different shape; its fix is in ba06f31d06c.
- is related to
-
SERVER-129249 Nested $unionWith inside of a $lookup drops view pipeline
-
- Closed
-
- related to
-
SERVER-136366 $lookup binds a view onto its own definition stages unless the view starts with a source stage
-
- Needs Scheduling
-
-
SERVER-131409 Extension $search in deeply-nested $unionWith/$lookup on views returns incorrect (dropped) results on an unsharded deployment
-
- Closed
-