Skip to content

Commit 9aa35d8

Browse files
craiggwilsonrstam
authored andcommitted
CSHARP-1662: added support for $reduce. Also included a new ImmutableTypeClassMapConvention which was needed to support anonymous types in LINQ's Aggregate method.
1 parent 9819e46 commit 9aa35d8

File tree

15 files changed

+521
-11
lines changed

15 files changed

+521
-11
lines changed

src/MongoDB.Bson/MongoDB.Bson.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
<Compile Include="Serialization\Conventions\MemberNameElementNameConvention.cs" />
177177
<Compile Include="Serialization\Conventions\NamedExtraElementsMemberConvention.cs" />
178178
<Compile Include="Serialization\Conventions\NamedIdMemberConvention.cs" />
179+
<Compile Include="Serialization\Conventions\ImmutableTypeClassMapConvention.cs" />
179180
<Compile Include="Serialization\Conventions\NoIdMemberConvention.cs" />
180181
<Compile Include="Serialization\Conventions\ObjectDiscriminatorConvention.cs" />
181182
<Compile Include="Serialization\Conventions\ReadWriteMemberFinderConvention.cs" />

src/MongoDB.Bson/Serialization/Conventions/DefaultConventionPack.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ private DefaultConventionPack()
4040
new NamedIdMemberConvention(new [] { "Id", "id", "_id" }),
4141
new NamedExtraElementsMemberConvention(new [] { "ExtraElements" }),
4242
new IgnoreExtraElementsConvention(false),
43+
new ImmutableTypeClassMapConvention(),
4344
new NamedParameterCreatorMapConvention(),
4445
new StringObjectIdIdGeneratorConvention(), // should be before LookupIdGeneratorConvention
4546
new LookupIdGeneratorConvention()
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* Copyright 2016 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+
17+
using System;
18+
using System.Linq;
19+
using System.Reflection;
20+
21+
namespace MongoDB.Bson.Serialization.Conventions
22+
{
23+
/// <summary>
24+
/// Maps a fully immutable type. This will include anonymous types.
25+
/// </summary>
26+
public class ImmutableTypeClassMapConvention : ConventionBase, IClassMapConvention
27+
{
28+
/// <inheritdoc />
29+
public void Apply(BsonClassMap classMap)
30+
{
31+
var typeInfo = classMap.ClassType.GetTypeInfo();
32+
if (typeInfo.IsAbstract)
33+
{
34+
return;
35+
}
36+
37+
if (typeInfo.GetConstructor(Type.EmptyTypes) != null)
38+
{
39+
return;
40+
}
41+
42+
foreach (var ctor in typeInfo.GetConstructors())
43+
{
44+
var parameters = ctor.GetParameters();
45+
var properties = typeInfo.GetProperties();
46+
if (parameters.Length != properties.Length)
47+
{
48+
continue;
49+
}
50+
51+
var matches = parameters
52+
.GroupJoin(properties,
53+
parameter => parameter.Name,
54+
property => property.Name,
55+
(parameter, props) => new { parameter, properties = props },
56+
StringComparer.OrdinalIgnoreCase);
57+
58+
if (matches.Any(m => m.properties.Count() != 1 || m.properties.ElementAt(0).CanWrite))
59+
{
60+
continue;
61+
}
62+
63+
classMap.MapConstructor(ctor);
64+
}
65+
}
66+
}
67+
}

src/MongoDB.Driver/Linq/Expressions/ExpressionComparer.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ public bool Compare(Expression a, Expression b)
132132
return CompareDocumentWrappedField((FieldAsDocumentExpression)a, (FieldAsDocumentExpression)b);
133133
case ExtensionExpressionType.Field:
134134
return CompareField((FieldExpression)a, (FieldExpression)b);
135+
case ExtensionExpressionType.SerializedConstant:
136+
return CompareSerializedConstant((SerializedConstantExpression)a, (SerializedConstantExpression)b);
135137
default:
136138
throw new MongoInternalException(string.Format("Unhandled mongo expression type: '{0}'", extensionA.ExtensionType));
137139
}
@@ -166,6 +168,11 @@ private bool CompareField(FieldExpression a, FieldExpression b)
166168
&& Compare(a.Original, b.Original);
167169
}
168170

171+
private bool CompareSerializedConstant(SerializedConstantExpression a, SerializedConstantExpression b)
172+
{
173+
return CompareConstantValues(a.Value, b.Value);
174+
}
175+
169176
private bool CompareUnary(UnaryExpression a, UnaryExpression b)
170177
{
171178
return a.NodeType == b.NodeType

src/MongoDB.Driver/Linq/Expressions/ExtensionExpressionType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ internal enum ExtensionExpressionType
4949
Field,
5050
GroupingKey,
5151
InjectedFilter,
52+
SerializedConstant,
5253

5354
// Temporary
5455
Correlated,

src/MongoDB.Driver/Linq/Expressions/ExtensionExpressionVisitor.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ protected internal virtual Expression VisitCollection(CollectionExpression node)
4343
return node;
4444
}
4545

46-
internal Expression VisitInjectedFilter(InjectedFilterExpression node)
46+
protected internal virtual Expression VisitInjectedFilter(InjectedFilterExpression node)
4747
{
4848
return node;
4949
}
@@ -197,6 +197,11 @@ protected internal virtual Expression VisitSelectMany(SelectManyExpression node)
197197
Visit(node.ResultSelector));
198198
}
199199

200+
protected internal virtual Expression VisitSerializedConstant(SerializedConstantExpression node)
201+
{
202+
return node;
203+
}
204+
200205
protected internal virtual Expression VisitSkip(SkipExpression node)
201206
{
202207
return node.Update(
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/* Copyright 2016 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.Collections.Generic;
18+
using System.Linq;
19+
using System.Linq.Expressions;
20+
using System.Text;
21+
using System.Threading.Tasks;
22+
using MongoDB.Bson.Serialization;
23+
using MongoDB.Driver.Core.Misc;
24+
25+
namespace MongoDB.Driver.Linq.Expressions.ResultOperators
26+
{
27+
internal sealed class AggregateResultOperator : ResultOperator
28+
{
29+
private readonly Expression _finalizer;
30+
private readonly string _itemName;
31+
private readonly Expression _reducer;
32+
private readonly Expression _seed;
33+
private readonly IBsonSerializer _serializer;
34+
35+
public AggregateResultOperator(Expression seed, Expression reducer, Expression finalizer, string itemName, IBsonSerializer serializer)
36+
{
37+
_seed = Ensure.IsNotNull(seed, nameof(seed));
38+
_reducer = Ensure.IsNotNull(reducer, nameof(reducer));
39+
_serializer = Ensure.IsNotNull(serializer, nameof(serializer));
40+
41+
_finalizer = finalizer;
42+
_itemName = itemName;
43+
}
44+
45+
public Expression Finalizer
46+
{
47+
get { return _finalizer; }
48+
}
49+
50+
public string ItemName
51+
{
52+
get { return _itemName; }
53+
}
54+
55+
public override string Name
56+
{
57+
get { return "Aggregate"; }
58+
}
59+
60+
public Expression Reducer
61+
{
62+
get { return _reducer; }
63+
}
64+
65+
public Expression Seed
66+
{
67+
get { return _seed; }
68+
}
69+
70+
public override IBsonSerializer Serializer
71+
{
72+
get { return _serializer; }
73+
}
74+
75+
public override Type Type
76+
{
77+
get { return _serializer.ValueType; }
78+
}
79+
80+
protected internal override ResultOperator Update(ExtensionExpressionVisitor visitor)
81+
{
82+
var seed = visitor.Visit(_seed);
83+
var reducer = visitor.Visit(_reducer);
84+
Expression finalizer = null;
85+
if (_finalizer != null)
86+
{
87+
finalizer = visitor.Visit(_finalizer);
88+
}
89+
if (seed != _seed || reducer != _reducer || finalizer != _finalizer)
90+
{
91+
return new AggregateResultOperator(seed, reducer, finalizer, _itemName, _serializer);
92+
}
93+
94+
return this;
95+
}
96+
}
97+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* Copyright 2016 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.Expressions;
18+
using MongoDB.Bson.Serialization;
19+
using MongoDB.Driver.Core.Misc;
20+
21+
namespace MongoDB.Driver.Linq.Expressions
22+
{
23+
internal sealed class SerializedConstantExpression : SerializationExpression
24+
{
25+
private readonly object _value;
26+
private readonly IBsonSerializer _serializer;
27+
28+
public SerializedConstantExpression(object value, IBsonSerializer serializer)
29+
{
30+
_value = Ensure.IsNotNull(value, nameof(value));
31+
_serializer = Ensure.IsNotNull(serializer, nameof(serializer));
32+
}
33+
34+
public override ExtensionExpressionType ExtensionType
35+
{
36+
get { return ExtensionExpressionType.SerializedConstant; }
37+
}
38+
39+
public override IBsonSerializer Serializer
40+
{
41+
get { return _serializer; }
42+
}
43+
44+
public override Type Type
45+
{
46+
get { return _serializer.ValueType; }
47+
}
48+
49+
public object Value
50+
{
51+
get { return _value; }
52+
}
53+
54+
public override string ToString()
55+
{
56+
return $"Constant({_value.ToString()})";
57+
}
58+
59+
protected internal override Expression Accept(ExtensionExpressionVisitor visitor)
60+
{
61+
return visitor.VisitSerializedConstant(this);
62+
}
63+
}
64+
}

src/MongoDB.Driver/Linq/Processors/EmbeddedPipeline/EmbeddedPipelineBinder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ internal sealed class EmbeddedPipelineBinder : PipelineBinderBase<EmbeddedPipeli
2929
static EmbeddedPipelineBinder()
3030
{
3131
var infoBinder = new MethodInfoMethodCallBinder<EmbeddedPipelineBindingContext>();
32+
infoBinder.Register(new AggregateBinder(), AggregateBinder.GetSupportedMethods());
3233
infoBinder.Register(new AllBinder(), AllBinder.GetSupportedMethods());
3334
infoBinder.Register(new AnyBinder(), AnyBinder.GetSupportedMethods());
3435
infoBinder.Register(new AsQueryableBinder(), AsQueryableBinder.GetSupportedMethods());
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/* Copyright 2016 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.Collections.Generic;
17+
using System.Linq;
18+
using System.Linq.Expressions;
19+
using System.Reflection;
20+
using MongoDB.Driver.Linq.Expressions;
21+
using MongoDB.Driver.Linq.Expressions.ResultOperators;
22+
using MongoDB.Driver.Support;
23+
24+
namespace MongoDB.Driver.Linq.Processors.EmbeddedPipeline.MethodCallBinders
25+
{
26+
internal sealed class AggregateBinder : IMethodCallBinder<EmbeddedPipelineBindingContext>
27+
{
28+
public static IEnumerable<MethodInfo> GetSupportedMethods()
29+
{
30+
yield return MethodHelper.GetMethodDefinition(() => Enumerable.Aggregate<object>(null, null));
31+
yield return MethodHelper.GetMethodDefinition(() => Enumerable.Aggregate<object, object>(null, null, null));
32+
yield return MethodHelper.GetMethodDefinition(() => Enumerable.Aggregate<object, object, object>(null, null, null, null));
33+
yield return MethodHelper.GetMethodDefinition(() => Queryable.Aggregate<object>(null, null));
34+
yield return MethodHelper.GetMethodDefinition(() => Queryable.Aggregate<object, object>(null, null, null));
35+
yield return MethodHelper.GetMethodDefinition(() => Queryable.Aggregate<object, object, object>(null, null, null, null));
36+
}
37+
38+
public Expression Bind(PipelineExpression pipeline, EmbeddedPipelineBindingContext bindingContext, MethodCallExpression node, IEnumerable<Expression> arguments)
39+
{
40+
var sourceSerializer = pipeline.Projector.Serializer;
41+
var accumulatorSerializer = sourceSerializer;
42+
Expression seed;
43+
LambdaExpression lambda;
44+
var argumentCount = arguments.Count();
45+
if (argumentCount == 1)
46+
{
47+
lambda = ExpressionHelper.GetLambda(arguments.Single());
48+
seed = Expression.Constant(accumulatorSerializer.ValueType.GetDefaultValue());
49+
}
50+
else
51+
{
52+
seed = arguments.First();
53+
accumulatorSerializer = bindingContext.GetSerializer(seed.Type, seed);
54+
lambda = ExpressionHelper.GetLambda(arguments.ElementAt(1));
55+
}
56+
57+
if (seed.NodeType == ExpressionType.Constant)
58+
{
59+
seed = new SerializedConstantExpression(((ConstantExpression)seed).Value, accumulatorSerializer);
60+
}
61+
62+
var valueField = new FieldExpression("$value", accumulatorSerializer);
63+
var thisField = new FieldExpression("$this", sourceSerializer);
64+
65+
bindingContext.AddExpressionMapping(lambda.Parameters[0], valueField);
66+
bindingContext.AddExpressionMapping(lambda.Parameters[1], thisField);
67+
68+
var reducer = bindingContext.Bind(lambda.Body);
69+
var serializer = bindingContext.GetSerializer(reducer.Type, reducer);
70+
71+
Expression finalizer = null;
72+
string itemName = null;
73+
if (argumentCount == 3)
74+
{
75+
lambda = ExpressionHelper.GetLambda(arguments.Last());
76+
itemName = lambda.Parameters[0].Name;
77+
var variable = new FieldExpression("$" + itemName, serializer);
78+
bindingContext.AddExpressionMapping(lambda.Parameters[0], variable);
79+
finalizer = bindingContext.Bind(lambda.Body);
80+
serializer = bindingContext.GetSerializer(finalizer.Type, finalizer);
81+
}
82+
83+
return new PipelineExpression(
84+
pipeline.Source,
85+
new DocumentExpression(serializer),
86+
new AggregateResultOperator(seed, reducer, finalizer, itemName, serializer));
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)