Skip to content

Commit 0e47c16

Browse files
committed
CSHARP-3529: Support $topN and similar accumulators in $group.
1 parent c4ee32b commit 0e47c16

25 files changed

+3139
-103
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/AstNodeType.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Ast
1717
{
1818
internal enum AstNodeType
1919
{
20-
AccumulatorExpression,
2120
AccumulatorField,
2221
AddFieldsStage,
2322
AllFilterOperation,
@@ -105,6 +104,8 @@ internal enum AstNodeType
105104
OrExpression,
106105
OrFilter,
107106
OutStage,
107+
PickAccumulatorExpression,
108+
PickExpression,
108109
Pipeline,
109110
PlanCacheStatsStage,
110111
ProjectStage,
@@ -137,6 +138,7 @@ internal enum AstNodeType
137138
TextFilter,
138139
TrimExpression,
139140
TypeFilterOperation,
141+
UnaryAccumulatorExpression,
140142
UnaryExpression,
141143
UnaryWindowExpression,
142144
UnionWithStage,

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstAccumulatorExpression.cs

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,55 +13,9 @@
1313
* limitations under the License.
1414
*/
1515

16-
using MongoDB.Bson;
17-
using MongoDB.Driver.Core.Misc;
18-
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Visitors;
19-
2016
namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions
2117
{
22-
internal sealed class AstAccumulatorExpression : AstExpression
18+
internal abstract class AstAccumulatorExpression : AstExpression
2319
{
24-
private readonly AstExpression _arg;
25-
private readonly AstAccumulatorOperator _operator;
26-
27-
public AstAccumulatorExpression(AstAccumulatorOperator @operator, AstExpression arg)
28-
{
29-
_operator = @operator;
30-
_arg = Ensure.IsNotNull(arg, nameof(arg));
31-
}
32-
33-
public AstExpression Arg => _arg;
34-
public override AstNodeType NodeType => AstNodeType.AccumulatorExpression;
35-
public AstAccumulatorOperator Operator => _operator;
36-
37-
public override AstNode Accept(AstNodeVisitor visitor)
38-
{
39-
return visitor.VisitAccumulatorExpression(this);
40-
}
41-
42-
public override BsonValue Render()
43-
{
44-
return new BsonDocument(_operator.Render(), RenderArg());
45-
}
46-
47-
public AstAccumulatorExpression Update(AstExpression arg)
48-
{
49-
if (arg == _arg)
50-
{
51-
return this;
52-
}
53-
54-
return new AstAccumulatorExpression(_operator, arg);
55-
}
56-
57-
private BsonValue RenderArg()
58-
{
59-
var rendered = _arg.Render();
60-
if (rendered.IsBsonArray)
61-
{
62-
rendered = new BsonArray { rendered };
63-
}
64-
return rendered;
65-
}
6620
}
6721
}

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstExpression.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,9 @@ public static AstExpression Abs(AstExpression arg)
6161
return new AstUnaryExpression(AstUnaryOperator.Abs, arg);
6262
}
6363

64-
public static AstAccumulatorExpression AccumulatorExpression(AstAccumulatorOperator @operator, AstExpression arg)
64+
public static AstAccumulatorField AccumulatorField(string name, AstUnaryAccumulatorOperator @operator, AstExpression arg)
6565
{
66-
return new AstAccumulatorExpression(@operator, arg);
67-
}
68-
69-
public static AstAccumulatorField AccumulatorField(string name, AstAccumulatorOperator @operator, AstExpression arg)
70-
{
71-
var value = new AstAccumulatorExpression(@operator, arg);
66+
var value = new AstUnaryAccumulatorExpression(@operator, arg);
7267
return new AstAccumulatorField(name, value);
7368
}
7469

@@ -612,6 +607,16 @@ public static AstExpression Or(params AstExpression[] args)
612607
return new AstOrExpression(args);
613608
}
614609

610+
public static AstExpression PickExpression(AstPickOperator @operator, AstExpression source, AstSortFields sortBy, AstVarExpression @as, AstExpression selector, AstExpression n)
611+
{
612+
return new AstPickExpression(@operator, source, sortBy, @as, selector, n);
613+
}
614+
615+
public static AstExpression PickAccumulatorExpression(AstPickAccumulatorOperator @operator, AstSortFields sortBy, AstExpression selector, AstExpression n)
616+
{
617+
return new AstPickAccumulatorExpression(@operator, sortBy, selector, n);
618+
}
619+
615620
public static AstExpression Pow(AstExpression arg, AstExpression exponent)
616621
{
617622
return new AstBinaryExpression(AstBinaryOperator.Pow, arg, exponent);
@@ -814,6 +819,11 @@ public static AstExpression Trunc(AstExpression arg)
814819
return new AstUnaryExpression(AstUnaryOperator.Trunc, arg);
815820
}
816821

822+
public static AstAccumulatorExpression UnaryAccumulator(AstUnaryAccumulatorOperator @operator, AstExpression arg)
823+
{
824+
return new AstUnaryAccumulatorExpression(@operator, arg);
825+
}
826+
817827
public static AstExpression UnaryWindowExpression(AstUnaryWindowOperator @operator, AstExpression arg, AstWindow window)
818828
{
819829
return new AstUnaryWindowExpression(@operator, arg, window);
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Linq;
18+
using MongoDB.Bson;
19+
using MongoDB.Driver.Core.Misc;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Visitors;
21+
22+
namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions
23+
{
24+
internal sealed class AstPickAccumulatorExpression : AstAccumulatorExpression
25+
{
26+
#region static
27+
private static AstExpression EnsureNIsValid(AstPickAccumulatorOperator @operator, AstExpression n)
28+
{
29+
switch (@operator)
30+
{
31+
case AstPickAccumulatorOperator.Bottom:
32+
case AstPickAccumulatorOperator.Top:
33+
return Ensure.IsNull(n, nameof(n));
34+
35+
case AstPickAccumulatorOperator.BottomN:
36+
case AstPickAccumulatorOperator.FirstN:
37+
case AstPickAccumulatorOperator.LastN:
38+
case AstPickAccumulatorOperator.MaxN:
39+
case AstPickAccumulatorOperator.MinN:
40+
case AstPickAccumulatorOperator.TopN:
41+
return Ensure.IsNotNull(n, nameof(n));
42+
43+
default:
44+
throw new InvalidOperationException($"Invalid operator: {@operator}.");
45+
}
46+
}
47+
48+
private static AstSortFields EnsureSortByIsValid(AstPickAccumulatorOperator @operator, AstSortFields sortBy)
49+
{
50+
switch (@operator)
51+
{
52+
case AstPickAccumulatorOperator.Bottom:
53+
case AstPickAccumulatorOperator.BottomN:
54+
case AstPickAccumulatorOperator.Top:
55+
case AstPickAccumulatorOperator.TopN:
56+
return Ensure.IsNotNull(sortBy, nameof(sortBy));
57+
58+
case AstPickAccumulatorOperator.FirstN:
59+
case AstPickAccumulatorOperator.LastN:
60+
case AstPickAccumulatorOperator.MaxN:
61+
case AstPickAccumulatorOperator.MinN:
62+
return Ensure.IsNull(sortBy, nameof(sortBy));
63+
64+
default:
65+
throw new InvalidOperationException($"Invalid operator: {@operator}.");
66+
}
67+
}
68+
#endregion
69+
70+
private readonly AstExpression _n;
71+
private readonly AstPickAccumulatorOperator _operator;
72+
private readonly AstExpression _selector;
73+
private readonly AstSortFields _sortBy;
74+
75+
public AstPickAccumulatorExpression(
76+
AstPickAccumulatorOperator @operator,
77+
AstSortFields sortBy,
78+
AstExpression selector,
79+
AstExpression n)
80+
{
81+
_operator = @operator;
82+
_sortBy = EnsureSortByIsValid(@operator, sortBy);
83+
_selector = Ensure.IsNotNull(selector, nameof(selector));
84+
_n = EnsureNIsValid(@operator, n);
85+
}
86+
87+
public AstExpression N => _n;
88+
public override AstNodeType NodeType => AstNodeType.PickAccumulatorExpression;
89+
public AstPickAccumulatorOperator Operator => _operator;
90+
public AstExpression Selector => _selector;
91+
public AstSortFields SortBy => _sortBy;
92+
93+
public override AstNode Accept(AstNodeVisitor visitor)
94+
{
95+
return visitor.VisitPickAccumulatorExpression(this);
96+
}
97+
98+
public override BsonValue Render()
99+
{
100+
switch (_operator)
101+
{
102+
case AstPickAccumulatorOperator.Bottom:
103+
case AstPickAccumulatorOperator.BottomN:
104+
case AstPickAccumulatorOperator.Top:
105+
case AstPickAccumulatorOperator.TopN:
106+
return new BsonDocument
107+
{
108+
{ _operator.Render(), new BsonDocument
109+
{
110+
{ "sortBy", new BsonDocument(_sortBy.Select(f => f.RenderAsElement())) },
111+
{ "output", _selector.Render() },
112+
{ "n", _n?.Render(), _n != null }
113+
}
114+
}
115+
};
116+
117+
case AstPickAccumulatorOperator.FirstN:
118+
case AstPickAccumulatorOperator.LastN:
119+
case AstPickAccumulatorOperator.MaxN:
120+
case AstPickAccumulatorOperator.MinN:
121+
return new BsonDocument
122+
{
123+
{ _operator.Render(), new BsonDocument
124+
{
125+
{ "input", _selector.Render() },
126+
{ "n", _n?.Render(), _n != null }
127+
}
128+
}
129+
};
130+
131+
default:
132+
throw new InvalidOperationException($"Invalid operator: {_operator}.");
133+
}
134+
}
135+
136+
public AstPickAccumulatorExpression Update(
137+
AstPickAccumulatorOperator @operator,
138+
AstSortFields sortBy,
139+
AstExpression selector,
140+
AstExpression n)
141+
{
142+
if (@operator == _operator && sortBy == _sortBy && selector == _selector && n == _n)
143+
{
144+
return this;
145+
}
146+
147+
return new AstPickAccumulatorExpression(@operator, sortBy, selector, n);
148+
}
149+
}
150+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
18+
namespace MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions
19+
{
20+
internal enum AstPickAccumulatorOperator
21+
{
22+
Bottom,
23+
BottomN,
24+
FirstN,
25+
LastN,
26+
MaxN,
27+
MinN,
28+
Top,
29+
TopN
30+
}
31+
32+
internal static class AstPickAccumulatorOperatorExtensions
33+
{
34+
public static string Render(this AstPickAccumulatorOperator @operator)
35+
{
36+
return @operator switch
37+
{
38+
AstPickAccumulatorOperator.Bottom => "$bottom",
39+
AstPickAccumulatorOperator.BottomN => "$bottomN",
40+
AstPickAccumulatorOperator.FirstN => "$firstN",
41+
AstPickAccumulatorOperator.LastN => "$lastN",
42+
AstPickAccumulatorOperator.MaxN => "$maxN",
43+
AstPickAccumulatorOperator.MinN => "$minN",
44+
AstPickAccumulatorOperator.Top => "$top",
45+
AstPickAccumulatorOperator.TopN => "$topN",
46+
_ => throw new InvalidOperationException($"Invalid operator: {@operator}.")
47+
};
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)