2424
2525namespace MongoDB . Driver . Linq . Linq3Implementation . Ast . Optimizers
2626{
27- internal class AstGroupPipelineOptimizer
27+ internal class AstGroupingPipelineOptimizer
2828 {
2929 #region static
3030 public static AstPipeline Optimize ( AstPipeline pipeline )
3131 {
32- var optimizer = new AstGroupPipelineOptimizer ( ) ;
32+ var optimizer = new AstGroupingPipelineOptimizer ( ) ;
3333 for ( var i = 0 ; i < pipeline . Stages . Count ; i ++ )
3434 {
3535 var stage = pipeline . Stages [ i ] ;
36- if ( stage is AstGroupStage groupStage )
36+ if ( IsGroupingStage ( stage ) )
3737 {
38- pipeline = optimizer . OptimizeGroupStage ( pipeline , i , groupStage ) ;
38+ pipeline = optimizer . OptimizeGroupingStage ( pipeline , i , stage ) ;
3939 }
4040 }
4141
4242 return pipeline ;
43+
44+ static bool IsGroupingStage ( AstStage stage )
45+ {
46+ return stage . NodeType switch
47+ {
48+ AstNodeType . GroupStage or AstNodeType . BucketStage or AstNodeType . BucketAutoStage => true ,
49+ _ => false
50+ } ;
51+ }
4352 }
4453 #endregion
4554
4655 private readonly AccumulatorSet _accumulators = new AccumulatorSet ( ) ;
4756 private AstExpression _element ; // normally either "$$ROOT" or "$_v"
4857
49- private AstPipeline OptimizeGroupStage ( AstPipeline pipeline , int i , AstGroupStage groupStage )
58+ private AstPipeline OptimizeGroupingStage ( AstPipeline pipeline , int i , AstStage groupingStage )
5059 {
5160 try
5261 {
53- if ( IsOptimizableGroupStage ( groupStage , out _element ) )
62+ if ( IsOptimizableGroupingStage ( groupingStage , out _element ) )
5463 {
5564 var followingStages = GetFollowingStagesToOptimize ( pipeline , i + 1 ) ;
5665 if ( followingStages == null )
5766 {
5867 return pipeline ;
5968 }
6069
61- var mappings = OptimizeGroupAndFollowingStages ( groupStage , followingStages ) ;
70+ var mappings = OptimizeGroupingAndFollowingStages ( groupingStage , followingStages ) ;
6271 if ( mappings . Length > 0 )
6372 {
6473 return ( AstPipeline ) AstNodeReplacer . Replace ( pipeline , mappings ) ;
@@ -72,23 +81,57 @@ private AstPipeline OptimizeGroupStage(AstPipeline pipeline, int i, AstGroupStag
7281
7382 return pipeline ;
7483
75- static bool IsOptimizableGroupStage ( AstGroupStage groupStage , out AstExpression element )
84+ static bool IsOptimizableGroupingStage ( AstStage groupingStage , out AstExpression element )
7685 {
77- // { $group : { _id : ?, _elements : { $push : element } } }
78- if ( groupStage . Fields . Count == 1 )
86+ if ( groupingStage is AstGroupStage groupStage )
87+ {
88+ // { $group : { _id : ?, _elements : { $push : element } } }
89+ if ( groupStage . Fields . Count == 1 )
90+ {
91+ var field = groupStage . Fields [ 0 ] ;
92+ return IsElementsPush ( field , out element ) ;
93+ }
94+ }
95+
96+ if ( groupingStage is AstBucketStage bucketStage )
97+ {
98+ // { $bucket : { groupBy : ?, boundaries : ?, default : ?, output : { _elements : { $push : element } } } }
99+ if ( bucketStage . Output . Count == 1 )
100+ {
101+ var output = bucketStage . Output [ 0 ] ;
102+ return IsElementsPush ( output , out element ) ;
103+ }
104+ }
105+
106+ if ( groupingStage is AstBucketAutoStage bucketAutoStage )
79107 {
80- var field = groupStage . Fields [ 0 ] ;
81- if ( field . Path == "_elements" &&
108+ // { $bucketAuto : { groupBy : ?, buckets : ?, granularity : ?, output : { _elements : { $push : element } } } }
109+ if ( bucketAutoStage . Output . Count == 1 )
110+ {
111+ var output = bucketAutoStage . Output [ 0 ] ;
112+ return IsElementsPush ( output , out element ) ;
113+ }
114+ }
115+
116+ element = null ;
117+ return false ;
118+
119+ static bool IsElementsPush ( AstAccumulatorField field , out AstExpression element )
120+ {
121+ if (
122+ field . Path == "_elements" &&
82123 field . Value is AstUnaryAccumulatorExpression unaryAccumulatorExpression &&
83124 unaryAccumulatorExpression . Operator == AstUnaryAccumulatorOperator . Push )
84125 {
85126 element = unaryAccumulatorExpression . Arg ;
86127 return true ;
87128 }
129+ else
130+ {
131+ element = null ;
132+ return false ;
133+ }
88134 }
89-
90- element = null ;
91- return false ;
92135 }
93136
94137 static List < AstStage > GetFollowingStagesToOptimize ( AstPipeline pipeline , int from )
@@ -135,7 +178,7 @@ static bool IsLastStageThatCanBeOptimized(AstStage stage)
135178 }
136179 }
137180
138- private ( AstNode , AstNode ) [ ] OptimizeGroupAndFollowingStages ( AstGroupStage groupStage , List < AstStage > followingStages )
181+ private ( AstNode , AstNode ) [ ] OptimizeGroupingAndFollowingStages ( AstStage groupingStage , List < AstStage > followingStages )
139182 {
140183 var mappings = new List < ( AstNode , AstNode ) > ( ) ;
141184
@@ -148,10 +191,21 @@ static bool IsLastStageThatCanBeOptimized(AstStage stage)
148191 }
149192 }
150193
151- var newGroupStage = AstStage . Group ( groupStage . Id , _accumulators ) ;
152- mappings . Add ( ( groupStage , newGroupStage ) ) ;
194+ var newGroupingStage = CreateNewGroupingStage ( groupingStage , _accumulators ) ;
195+ mappings . Add ( ( groupingStage , newGroupingStage ) ) ;
153196
154197 return mappings . ToArray ( ) ;
198+
199+ static AstStage CreateNewGroupingStage ( AstStage groupingStage , AccumulatorSet accumulators )
200+ {
201+ return groupingStage switch
202+ {
203+ AstGroupStage groupStage => AstStage . Group ( groupStage . Id , accumulators ) ,
204+ AstBucketStage bucketStage => AstStage . Bucket ( bucketStage . GroupBy , bucketStage . Boundaries , bucketStage . Default , accumulators ) ,
205+ AstBucketAutoStage bucketAutoStage => AstStage . BucketAuto ( bucketAutoStage . GroupBy , bucketAutoStage . Buckets , bucketAutoStage . Granularity , accumulators ) ,
206+ _ => throw new Exception ( $ "Unexpected { nameof ( groupingStage ) } node type: { groupingStage . NodeType } .")
207+ } ;
208+ }
155209 }
156210
157211 private AstStage OptimizeFollowingStage ( AstStage stage )
0 commit comments