@@ -364,6 +364,180 @@ assertGraphLookupExecution(
364364 }
365365 ] ) ;
366366
367+ // Test sharded local collection where the foreign namespace is a sharded view with another
368+ // $graphLookup against a sharded collection. Note that the $graphLookup in the view should be
369+ // treated as a "nested" $graphLookup and should execute on the merging node.
370+ st . shardColl ( airportsColl , { airport : 1 } , { airport : "JFK" } , { airport : "JFK" } , mongosDB . getName ( ) ) ;
371+ st . shardColl (
372+ travelersColl , { firstName : 1 } , { firstName : "Bob" } , { firstName : "Bob" } , mongosDB . getName ( ) ) ;
373+ st . shardColl (
374+ airfieldsColl , { airfield : 1 } , { airfield : "LHR" } , { airfield : "LHR" } , mongosDB . getName ( ) ) ;
375+
376+ assert . commandWorked ( mongosDB . createView ( "airportsView" , airportsColl . getName ( ) ,
377+ [ { $graphLookup : {
378+ from : "airfields" ,
379+ startWith : "$airport" ,
380+ connectFromField : "connects" ,
381+ connectToField : "airfield" ,
382+ maxDepth : 1 ,
383+ as : "nearbyAirfields"
384+ } } ]
385+ ) ) ;
386+ pipeline = [
387+ { $graphLookup : {
388+ from : "airportsView" ,
389+ startWith : "$nearestAirport" ,
390+ connectFromField : "connects" ,
391+ connectToField : "airport" ,
392+ maxDepth : 0 ,
393+ as : "destinations"
394+ } } ,
395+ { $unwind : "$destinations" } ,
396+ { $project : { firstName : 1 , 'destinations.airport' : 1 , 'destinations.nearbyAirfields.airfield' : 1 } }
397+ ] ;
398+
399+ assertGraphLookupExecution ( pipeline , { comment : "sharded_to_sharded_view_to_sharded" } , expectedRes , [
400+ {
401+ // The 'travelers' collection is sharded, so the $graphLookup stage is executed in parallel
402+ // on every shard that contains the local collection.
403+ toplevelExec : [ 1 , 1 ] ,
404+ // Each node executing the $graphLookup will, for every document that flows through the
405+ // stage, target the shard(s) that holds the relevant data for the sharded foreign view.
406+ recursiveMatchExec : [ 0 , 3 ] ,
407+ } ,
408+ {
409+ collName : airportsColl . getName ( ) ,
410+ fromCollName : airfieldsColl . getName ( ) ,
411+ // When executing the subpipeline, the "nested" $graphLookup stage contained in the view
412+ // pipeline will stay on the merging half of the pipeline and execute on the merging node,
413+ // targeting shards to execute the nested $matches.
414+ toplevelExec : [ 0 , 0 ] ,
415+ recursiveMatchExec : [ 1 , 5 ]
416+ }
417+ ] ) ;
418+ mongosDB . airportsView . drop ( ) ;
419+
420+ // Test top-level $lookup on a sharded local collection where the foreign namespace is a sharded
421+ // view with a $graphLookup against a sharded collection. Note that the $graphLookup in the view
422+ // should be treated as a "nested" $graphLookup and should execute on the merging node.
423+ st . shardColl ( airportsColl , { airport : 1 } , { airport : "JFK" } , { airport : "JFK" } , mongosDB . getName ( ) ) ;
424+ st . shardColl (
425+ travelersColl , { firstName : 1 } , { firstName : "Bob" } , { firstName : "Bob" } , mongosDB . getName ( ) ) ;
426+ st . shardColl (
427+ airfieldsColl , { airfield : 1 } , { airfield : "LHR" } , { airfield : "LHR" } , mongosDB . getName ( ) ) ;
428+
429+ assert . commandWorked ( mongosDB . createView ( "airportsView" , airportsColl . getName ( ) ,
430+ [ { $graphLookup : {
431+ from : "airfields" ,
432+ startWith : "$airport" ,
433+ connectFromField : "connects" ,
434+ connectToField : "airfield" ,
435+ maxDepth : 1 ,
436+ as : "nearbyAirfields"
437+ } } ]
438+ ) ) ;
439+ pipeline = [
440+ { $lookup : {
441+ from : "airportsView" ,
442+ localField : "nearestAirport" ,
443+ foreignField : "airport" ,
444+ as : "destinations"
445+ } } ,
446+ { $unwind : "$destinations" } ,
447+ { $project : { firstName : 1 , 'destinations.airport' : 1 , 'destinations.nearbyAirfields.airfield' : 1 } }
448+ ] ;
449+
450+ assertGraphLookupExecution (
451+ pipeline , { comment : "sharded_lookup_to_sharded_view_to_sharded" } , expectedRes , [
452+ {
453+ // The 'travelers' collection is sharded, so the $lookup stage is executed in parallel
454+ // on every shard that contains the local collection.
455+ toplevelExec : [ 1 , 1 ] ,
456+ // Each node executing the $lookup will, for every document that flows through the stage
457+ // target the shard(s) that holds the relevant data for the sharded foreign view.
458+ subpipelineExec : [ 0 , 3 ] ,
459+ } ,
460+ {
461+ collName : airportsColl . getName ( ) ,
462+ fromCollName : airfieldsColl . getName ( ) ,
463+ // When executing the subpipeline, the "nested" $graphLookup stage contained in the view
464+ // pipeline will stay on the merging half of the pipeline and execute on the merging
465+ // node, targeting shards to execute the nested $matches.
466+ toplevelExec : [ 0 , 0 ] ,
467+ recursiveMatchExec : [ 1 , 5 ]
468+ }
469+ ] ) ;
470+ mongosDB . airportsView . drop ( ) ;
471+
472+ // Test sharded local collection where the foreign namespace is a sharded view with a $lookup
473+ // against a sharded collection. Note that the $lookup in the view should be treated as a "nested"
474+ // $lookup and should execute on the merging node.
475+ st . shardColl ( airportsColl , { airport : 1 } , { airport : "JFK" } , { airport : "JFK" } , mongosDB . getName ( ) ) ;
476+ st . shardColl (
477+ travelersColl , { firstName : 1 } , { firstName : "Bob" } , { firstName : "Bob" } , mongosDB . getName ( ) ) ;
478+ st . shardColl (
479+ airfieldsColl , { airfield : 1 } , { airfield : "LHR" } , { airfield : "LHR" } , mongosDB . getName ( ) ) ;
480+
481+ assert . commandWorked ( mongosDB . createView ( "airportsView" , airportsColl . getName ( ) ,
482+ [ { $lookup : {
483+ from : "airfields" ,
484+ localField : "airport" ,
485+ foreignField : "airfield" ,
486+ as : "nearbyAirfields"
487+ } } ]
488+ ) ) ;
489+ pipeline = [
490+ { $graphLookup : {
491+ from : "airportsView" ,
492+ startWith : "$nearestAirport" ,
493+ connectFromField : "connects" ,
494+ connectToField : "airport" ,
495+ maxDepth : 0 ,
496+ as : "destinations"
497+ } } ,
498+ { $unwind : "$destinations" } ,
499+ { $project : { firstName : 1 , 'destinations.airport' : 1 , 'destinations.nearbyAirfields.airfield' : 1 } }
500+ ] ;
501+ expectedRes = [
502+ {
503+ _id : 1 ,
504+ firstName : "Alice" ,
505+ destinations : { airport : "LHR" , nearbyAirfields : [ { airfield : "LHR" } ] } ,
506+ } ,
507+ {
508+ _id : 2 ,
509+ firstName : "Alice" ,
510+ destinations : { airport : "ORD" , nearbyAirfields : [ { airfield : "ORD" } ] } ,
511+ } ,
512+ {
513+ _id : 3 ,
514+ firstName : "Bob" ,
515+ destinations : { airport : "JFK" , nearbyAirfields : [ { airfield : "JFK" } ] } ,
516+ }
517+ ] ;
518+
519+ assertGraphLookupExecution (
520+ pipeline , { comment : "sharded_to_sharded_lookup_view_to_sharded" } , expectedRes , [
521+ {
522+ // The 'travelers' collection is sharded, so the $graphLookup stage is executed in
523+ // parallel on every shard that contains the local collection.
524+ toplevelExec : [ 1 , 1 ] ,
525+ // Each node executing the $graphLookup will, for every document that flows through the
526+ // stage, target the shard(s) that holds the relevant data for the sharded foreign view.
527+ recursiveMatchExec : [ 0 , 3 ] ,
528+ } ,
529+ {
530+ collName : airportsColl . getName ( ) ,
531+ fromCollName : airfieldsColl . getName ( ) ,
532+ // When executing the subpipeline, the "nested" $lookup stage contained in the view
533+ // pipeline will stay on the merging half of the pipeline and execute on the merging
534+ // node, targeting shards to execute the nested subpipelines.
535+ toplevelExec : [ 0 , 0 ] ,
536+ subpipelineExec : [ 1 , 2 ]
537+ }
538+ ] ) ;
539+ mongosDB . airportsView . drop ( ) ;
540+
367541// Test that a targeted $graphLookup on a sharded collection can execute correctly on mongos.
368542st . shardColl ( airportsColl , { airport : 1 } , { airport : "LHR" } , { airport : "LHR" } , mongosDB . getName ( ) ) ;
369543st . shardColl (
0 commit comments