Skip to content

Commit 9b1c9ae

Browse files
committed
CSHARP-5190: Support Select new BsonDocument.
1 parent efaba58 commit 9b1c9ae

12 files changed

+722
-32
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Misc/PartialEvaluator.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,24 @@ public override Expression Visit(Expression expression)
187187
return expression;
188188
}
189189

190+
protected override Expression VisitListInit(ListInitExpression node)
191+
{
192+
// Initializers must be visited before NewExpression
193+
Visit(node.Initializers, VisitElementInit);
194+
195+
if (_cannotBeEvaluated)
196+
{
197+
// visit only the arguments if any Initializers cannot be partially evaluated
198+
Visit(node.NewExpression.Arguments);
199+
}
200+
else
201+
{
202+
Visit(node.NewExpression);
203+
}
204+
205+
return node;
206+
}
207+
190208
protected override Expression VisitMemberInit(MemberInitExpression node)
191209
{
192210
// Bindings must be visited before NewExpression

src/MongoDB.Driver/Linq/Linq3Implementation/Misc/ProjectionHelper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ bool ValueNeedsToBeQuoted(BsonValue value)
154154
{
155155
case BsonType.Boolean:
156156
case BsonType.Decimal128:
157+
case BsonType.Document:
157158
case BsonType.Double:
158159
case BsonType.Int32:
159160
case BsonType.Int64:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.Reflection;
17+
using MongoDB.Bson;
18+
19+
namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection
20+
{
21+
internal static class BsonDocumentConstructor
22+
{
23+
private static readonly ConstructorInfo __withNoParameters;
24+
private static readonly ConstructorInfo __withNameAndValue;
25+
26+
static BsonDocumentConstructor()
27+
{
28+
__withNoParameters = ReflectionInfo.Constructor(() => new BsonDocument());
29+
__withNameAndValue = ReflectionInfo.Constructor((string name, BsonValue value) => new BsonDocument(name, value));
30+
}
31+
32+
public static ConstructorInfo WithNoParameters => __withNoParameters;
33+
public static ConstructorInfo WithNameAndValue => __withNameAndValue;
34+
}
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.Reflection;
17+
using MongoDB.Bson;
18+
19+
namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection
20+
{
21+
internal static class BsonDocumentMethod
22+
{
23+
// private static fields
24+
private static readonly MethodInfo __addWithNameAndValue;
25+
26+
// static constructor
27+
static BsonDocumentMethod()
28+
{
29+
__addWithNameAndValue = ReflectionInfo.Method((BsonDocument document, string name, BsonValue value) => document.Add(name, value));
30+
}
31+
32+
// public static properties
33+
public static MethodInfo AddWithNameAndValue => __addWithNameAndValue;
34+
}
35+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ExpressionToAggregationExpressionTranslator.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ public static AggregationExpression Translate(TranslationContext context, Expres
6969
return ConstantExpressionToAggregationExpressionTranslator.Translate(context, (ConstantExpression)expression);
7070
case ExpressionType.Index:
7171
return IndexExpressionToAggregationExpressionTranslator.Translate(context, (IndexExpression)expression);
72+
case ExpressionType.ListInit:
73+
return ListInitExpressionToAggregationExpressionTranslator.Translate(context, (ListInitExpression)expression);
7274
case ExpressionType.MemberAccess:
7375
return MemberExpressionToAggregationExpressionTranslator.Translate(context, (MemberExpression)expression);
7476
case ExpressionType.MemberInit:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.Linq.Expressions;
17+
using MongoDB.Bson;
18+
19+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators
20+
{
21+
internal static class ListInitExpressionToAggregationExpressionTranslator
22+
{
23+
public static AggregationExpression Translate(
24+
TranslationContext context,
25+
ListInitExpression expression)
26+
{
27+
if (expression.Type == typeof(BsonDocument))
28+
{
29+
return NewBsonDocumentExpressionToAggregationExpressionTranslator.Translate(context, expression);
30+
}
31+
32+
throw new ExpressionNotSupportedException(expression);
33+
}
34+
}
35+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberInitExpressionToAggregationExpressionTranslator.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using System.Linq;
1919
using System.Linq.Expressions;
2020
using System.Reflection;
21+
using MongoDB.Bson;
2122
using MongoDB.Bson.Serialization;
2223
using MongoDB.Driver.Linq.Linq3Implementation.Ast;
2324
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
@@ -28,7 +29,14 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggreg
2829
internal static class MemberInitExpressionToAggregationExpressionTranslator
2930
{
3031
public static AggregationExpression Translate(TranslationContext context, MemberInitExpression expression)
31-
=> Translate(context, expression, expression.NewExpression, expression.Bindings);
32+
{
33+
if (expression.Type == typeof(BsonDocument))
34+
{
35+
return NewBsonDocumentExpressionToAggregationExpressionTranslator.Translate(context, expression);
36+
}
37+
38+
return Translate(context, expression, expression.NewExpression, expression.Bindings);
39+
}
3240

3341
public static AggregationExpression Translate(
3442
TranslationContext context,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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.Collections.Generic;
18+
using System.Linq.Expressions;
19+
using MongoDB.Bson.Serialization.Serializers;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Ast;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
22+
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
23+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
24+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
25+
26+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators
27+
{
28+
internal static class NewBsonDocumentExpressionToAggregationExpressionTranslator
29+
{
30+
public static AggregationExpression Translate(TranslationContext context, NewExpression expression)
31+
{
32+
return Translate(context, expression, newExpression: expression, initializers: Array.Empty<(Expression, Expression)>());
33+
}
34+
35+
public static AggregationExpression Translate(TranslationContext context, ListInitExpression expression)
36+
{
37+
var initializers = new List<(Expression, Expression)>();
38+
foreach (var initializer in expression.Initializers)
39+
{
40+
if (!initializer.AddMethod.Is(BsonDocumentMethod.AddWithNameAndValue))
41+
{
42+
throw new ExpressionNotSupportedException(expression, because: "it uses an unsupported Add method");
43+
}
44+
45+
var nameExpression = initializer.Arguments[0];
46+
var valueExpresssion = initializer.Arguments[1];
47+
48+
initializers.Add((nameExpression, valueExpresssion));
49+
}
50+
51+
return Translate(context, expression, expression.NewExpression, initializers);
52+
}
53+
54+
public static AggregationExpression Translate(TranslationContext context, MemberInitExpression expression)
55+
{
56+
if (expression.Bindings.Count > 0)
57+
{
58+
throw new ExpressionNotSupportedException(expression, because: "it uses an unsupported initializer syntax");
59+
}
60+
61+
return Translate(context, expression, expression.NewExpression, initializers: Array.Empty<(Expression, Expression)>());
62+
}
63+
64+
private static AggregationExpression Translate(
65+
TranslationContext context,
66+
Expression expression,
67+
NewExpression newExpression,
68+
IEnumerable<(Expression FieldName, Expression Value)> initializers)
69+
{
70+
var computedFields = new List<AstComputedField>();
71+
72+
if (newExpression != null)
73+
{
74+
var constructorInfo = newExpression.Constructor;
75+
if (constructorInfo.Is(BsonDocumentConstructor.WithNoParameters))
76+
{
77+
// nothing to do
78+
}
79+
else if (constructorInfo.Is(BsonDocumentConstructor.WithNameAndValue))
80+
{
81+
var arguments = newExpression.Arguments;
82+
var nameExpression = arguments[0];
83+
var valueExpresssion = arguments[1];
84+
computedFields.Add(CreateComputedField(context, expression, nameExpression, valueExpresssion));
85+
}
86+
else
87+
{
88+
throw new ExpressionNotSupportedException(newExpression, expression, because: "it uses an unsupported constructor");
89+
}
90+
}
91+
92+
foreach (var (nameExpression, valueExpression) in initializers)
93+
{
94+
computedFields.Add(CreateComputedField(context, expression, nameExpression, valueExpression));
95+
}
96+
97+
var ast = AstExpression.ComputedDocument(computedFields);
98+
return new AggregationExpression(expression, ast, BsonDocumentSerializer.Instance);
99+
100+
static AstComputedField CreateComputedField(TranslationContext context, Expression expression, Expression fieldNameExpression, Expression valueExpression)
101+
{
102+
var fieldName = fieldNameExpression.GetConstantValue<string>(expression);
103+
var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression);
104+
return AstExpression.ComputedField(fieldName, valueTranslation.Ast);
105+
}
106+
}
107+
}
108+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewExpressionToAggregationExpressionTranslator.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System;
1717
using System.Collections.Generic;
1818
using System.Linq.Expressions;
19+
using MongoDB.Bson;
1920

2021
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators
2122
{
@@ -25,6 +26,10 @@ public static AggregationExpression Translate(TranslationContext context, NewExp
2526
{
2627
var expressionType = expression.Type;
2728

29+
if (expressionType == typeof(BsonDocument))
30+
{
31+
return NewBsonDocumentExpressionToAggregationExpressionTranslator.Translate(context, expression);
32+
}
2833
if (expressionType == typeof(DateTime))
2934
{
3035
return NewDateTimeExpressionToAggregationExpressionTranslator.Translate(context, expression);

tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4062Tests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public void Project_anonymous_class_constant_should_work()
3030

3131
var stages = Translate(collection, aggregate);
3232

33-
AssertStages(stages, "{ $project : { _v : { R : true }, _id : 0 } }");
33+
AssertStages(stages, "{ $project : { _v : { $literal : { R : true } }, _id : 0 } }");
3434
}
3535

3636
[Fact]
@@ -42,7 +42,7 @@ public void Project_non_anonymous_class_constant_should_work()
4242

4343
var stages = Translate(collection, aggregate);
4444

45-
AssertStages(stages, "{ $project : { _v : { R : true }, _id : 0 } }");
45+
AssertStages(stages, "{ $project : { _v : { $literal : { R : true } }, _id : 0 } }");
4646
}
4747

4848
[Fact]
@@ -78,7 +78,7 @@ public void Select_anonymous_class_constant_should_work()
7878

7979
var stages = Translate(collection, queryable);
8080

81-
AssertStages(stages, "{ $project : { _v : { R : true }, _id : 0 } }");
81+
AssertStages(stages, "{ $project : { _v : { $literal : { R : true } }, _id : 0 } }");
8282
}
8383

8484
[Fact]
@@ -90,7 +90,7 @@ public void Select_non_anonymous_class_constant_should_work()
9090

9191
var stages = Translate(collection, queryable);
9292

93-
AssertStages(stages, "{ $project : { _v : { R : true }, _id : 0 } }");
93+
AssertStages(stages, "{ $project : { _v : { $literal : { R : true } }, _id : 0 } }");
9494
}
9595

9696
[Fact]

0 commit comments

Comments
 (0)