Skip to content

Commit 74fba13

Browse files
committed
CSHARP-4957: Fix issue with new array expression.
1 parent 46f39d5 commit 74fba13

File tree

2 files changed

+179
-2
lines changed

2 files changed

+179
-2
lines changed

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System;
1617
using System.Collections.Generic;
1718
using System.Linq.Expressions;
19+
using MongoDB.Bson.Serialization;
20+
using MongoDB.Bson.Serialization.Serializers;
1821
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
1922

2023
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators
@@ -24,14 +27,29 @@ internal static class NewArrayInitExpressionToAggregationExpressionTranslator
2427
public static AggregationExpression Translate(TranslationContext context, NewArrayExpression expression)
2528
{
2629
var items = new List<AstExpression>();
30+
IBsonSerializer itemSerializer = null;
2731
foreach (var itemExpression in expression.Expressions)
2832
{
2933
var itemTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, itemExpression);
3034
items.Add(itemTranslation.Ast);
35+
itemSerializer ??= itemTranslation.Serializer;
36+
37+
// make sure all items are serialized using the same serializer
38+
if (!itemTranslation.Serializer.Equals(itemSerializer))
39+
{
40+
throw new ExpressionNotSupportedException(expression, because: "all items in the array must be serialized using the same serializer");
41+
}
3142
}
43+
3244
var ast = AstExpression.ComputedArray(items);
33-
var serializer = context.KnownSerializersRegistry.GetSerializer(expression);
34-
return new AggregationExpression(expression, ast, serializer);
45+
46+
var arrayType = expression.Type;
47+
var itemType = arrayType.GetElementType();
48+
itemSerializer ??= BsonSerializer.LookupSerializer(itemType); // if the array is empty itemSerializer will be null
49+
var arraySerializerType = typeof(ArraySerializer<>).MakeGenericType(itemType);
50+
var arraySerializer = (IBsonSerializer)Activator.CreateInstance(arraySerializerType, itemSerializer);
51+
52+
return new AggregationExpression(expression, ast, arraySerializer);
3553
}
3654
}
3755
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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 FluentAssertions;
19+
using MongoDB.Bson.Serialization;
20+
using MongoDB.Bson.Serialization.Attributes;
21+
using MongoDB.Bson.Serialization.Serializers;
22+
using MongoDB.Driver.Linq;
23+
using MongoDB.TestHelpers.XunitExtensions;
24+
using Xunit;
25+
26+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
27+
{
28+
public class CSharp4957Tests : Linq3IntegrationTest
29+
{
30+
[Theory]
31+
[ParameterAttributeData]
32+
public void New_array_with_zero_items_should_work(
33+
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
34+
{
35+
var collection = GetCollection(linqProvider);
36+
37+
var queryable = collection.AsQueryable()
38+
.Select(x => (new int[] { }));
39+
40+
var stages = Translate(collection, queryable);
41+
if (linqProvider == LinqProvider.V2)
42+
{
43+
AssertStages(stages, "{ $project : { __fld0 : [], _id : 0 } }");
44+
}
45+
else
46+
{
47+
AssertStages(stages, "{ $project : { _v : [], _id : 0 } }");
48+
49+
}
50+
51+
var results = queryable.ToArray();
52+
results.Should().HaveCount(1);
53+
results[0].Should().Equal();
54+
}
55+
56+
[Theory]
57+
[ParameterAttributeData]
58+
public void New_array_with_one_items_should_work(
59+
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
60+
{
61+
var collection = GetCollection(linqProvider);
62+
63+
var queryable = collection.AsQueryable()
64+
.Select(x => (new[] { x.X }));
65+
66+
var stages = Translate(collection, queryable);
67+
if (linqProvider == LinqProvider.V2)
68+
{
69+
AssertStages(stages, "{ $project : { __fld0 : ['$X'], _id : 0 } }");
70+
}
71+
else
72+
{
73+
AssertStages(stages, "{ $project : { _v : ['$X'], _id : 0 } }");
74+
75+
}
76+
77+
var results = queryable.ToArray();
78+
results.Should().HaveCount(1);
79+
results[0].Should().Equal(1);
80+
}
81+
82+
[Theory]
83+
[ParameterAttributeData]
84+
public void New_array_with_two_items_should_work(
85+
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
86+
{
87+
var collection = GetCollection(linqProvider);
88+
89+
var queryable = collection.AsQueryable()
90+
.Select(x => (new[] { x.X, x.X + 1 }));
91+
92+
var stages = Translate(collection, queryable);
93+
if (linqProvider == LinqProvider.V2)
94+
{
95+
AssertStages(stages, "{ $project : { __fld0 : ['$X', { $add : ['$X', 1] }], _id : 0 } }");
96+
}
97+
else
98+
{
99+
AssertStages(stages, "{ $project : { _v : ['$X', { $add : ['$X', 1] }], _id : 0 } }");
100+
101+
}
102+
103+
var results = queryable.ToArray();
104+
results.Should().HaveCount(1);
105+
results[0].Should().Equal(1, 2);
106+
}
107+
108+
[Theory]
109+
[ParameterAttributeData]
110+
public void New_array_with_two_items_with_different_serializers_should_throw(
111+
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
112+
{
113+
var collection = GetCollection(linqProvider);
114+
115+
var queryable = collection.AsQueryable()
116+
.Select(x => (new[] { x.X, x.Y }));
117+
118+
if (linqProvider == LinqProvider.V2)
119+
{
120+
var stages = Translate(collection, queryable);
121+
AssertStages(stages, "{ $project : { __fld0 : ['$X', '$Y'], _id : 0 } }");
122+
123+
var exception = Record.Exception(() => queryable.ToArray());
124+
exception.Should().BeOfType<FormatException>(); // LINQ2 doesn't fail until deserialization
125+
}
126+
else
127+
{
128+
var exception = Record.Exception(() => Translate(collection, queryable));
129+
exception.Should().BeOfType<ExpressionNotSupportedException>();
130+
exception.Message.Should().Contain("all items in the array must be serialized using the same serializer");
131+
}
132+
}
133+
134+
private IMongoCollection<C> GetCollection(LinqProvider linqProvider)
135+
{
136+
var collection = GetCollection<C>("test", linqProvider);
137+
CreateCollection(
138+
collection,
139+
new C { Id = 1, X = 1, Y = 2 });
140+
return collection;
141+
}
142+
143+
private class C
144+
{
145+
public int Id { get; set; }
146+
public int X { get; set; }
147+
[BsonSerializer(typeof(YSerializer))] public int Y { get; set; }
148+
}
149+
150+
private class YSerializer : StructSerializerBase<int>
151+
{
152+
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, int value)
153+
{
154+
var writer = context.Writer;
155+
writer.WriteString($"<{value}>"); // not parsable by int.Parse
156+
}
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)