Skip to content

Commit 6747c43

Browse files
janiavdvrstam
andauthored
CSHARP-5163: Translate fails when serializing different sized numbers (#1369)
* Create unit tests for CSHARP-5163 * CSHARP-5163: Support widening converts in arithmetic expressions. * CSHARP-5163: Use explicit short in test (instead of implied int). --------- Co-authored-by: rstam <robert@robertstam.org>
1 parent d09a511 commit 6747c43

File tree

4 files changed

+123
-16
lines changed

4 files changed

+123
-16
lines changed

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

+26-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
using System;
1717
using System.Linq;
1818
using System.Linq.Expressions;
19+
using MongoDB.Bson;
20+
using MongoDB.Bson.Serialization;
21+
using MongoDB.Bson.Serialization.Options;
22+
using MongoDB.Bson.Serialization.Serializers;
1923

2024
namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
2125
{
@@ -76,6 +80,28 @@ private readonly static (Type SourceType, Type TargetType)[] __wideningConverts
7680
(typeof(double), typeof(decimal))
7781
};
7882

83+
public static IBsonSerializer CreateWiderSerializer(Type narrowerType, Type widerType)
84+
{
85+
if (IsWideningConvert(narrowerType, widerType))
86+
{
87+
return widerType switch
88+
{
89+
_ when widerType == typeof(int) => new Int32Serializer(BsonType.Int32),
90+
_ when widerType == typeof(long) => new Int64Serializer(BsonType.Int64),
91+
_ when widerType == typeof(decimal) => new DecimalSerializer(BsonType.Decimal128),
92+
_ when widerType == typeof(double) => new DoubleSerializer(BsonType.Double),
93+
_ => throw new ArgumentException($"Cannot create a wider serializer of type {widerType}.", nameof(widerType))
94+
};
95+
}
96+
97+
throw new ArgumentException($"{widerType} is not a wider type for {narrowerType}", nameof(widerType));
98+
}
99+
100+
public static bool IsWideningConvert(Type sourceType, Type targetType)
101+
{
102+
return __wideningConverts.Contains((sourceType, targetType));
103+
}
104+
79105
public static Expression RemoveConvertToMongoQueryable(Expression expression)
80106
{
81107
if (expression.NodeType == ExpressionType.Convert)
@@ -154,11 +180,6 @@ public static Expression RemoveWideningConvert(Expression expression)
154180
}
155181

156182
return expression;
157-
158-
static bool IsWideningConvert(Type sourceType, Type targetType)
159-
{
160-
return __wideningConverts.Contains((sourceType, targetType));
161-
}
162183
}
163184
}
164185
}

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,6 @@ public static void EnsureRepresentationIsNumeric(Expression expression, IBsonSer
4242
{
4343
throw new ExpressionNotSupportedException(expression, because: $"serializer for type {serializer.ValueType} uses a non-numeric representation: {representation}");
4444
}
45-
46-
static bool IsNumericRepresentation(BsonType representation)
47-
{
48-
return representation switch
49-
{
50-
BsonType.Decimal128 or BsonType.Double or BsonType.Int32 or BsonType.Int64 => true,
51-
_ => false
52-
};
53-
}
5445
}
5546

5647
public static BsonType GetRepresentation(IBsonSerializer serializer)
@@ -106,6 +97,15 @@ public static BsonType GetRepresentation(IBsonSerializer serializer)
10697
return BsonType.Undefined;
10798
}
10899

100+
public static bool IsNumericRepresentation(BsonType representation)
101+
{
102+
return representation switch
103+
{
104+
BsonType.Decimal128 or BsonType.Double or BsonType.Int32 or BsonType.Int64 => true,
105+
_ => false
106+
};
107+
}
108+
109109
public static bool IsRepresentedAsDocument(IBsonSerializer serializer)
110110
{
111111
return SerializationHelper.GetRepresentation(serializer) == BsonType.Document;

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

+20-2
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,23 @@ public static bool AreOperandTypesCompatible(Expression expression, Expression l
155155
return false;
156156
}
157157

158+
private static IBsonSerializer GetConstantSerializer(BinaryExpression containingExpression, IBsonSerializer otherSerializer, Type constantType)
159+
{
160+
if (
161+
IsArithmeticExpression(containingExpression) &&
162+
otherSerializer.ValueType != constantType &&
163+
ConvertHelper.IsWideningConvert(otherSerializer.ValueType, constantType) &&
164+
otherSerializer is IRepresentationConfigurable otherRepresentationConfigurableSerializer &&
165+
SerializationHelper.IsNumericRepresentation(otherRepresentationConfigurableSerializer.Representation))
166+
{
167+
return ConvertHelper.CreateWiderSerializer(otherSerializer.ValueType, constantType);
168+
}
169+
else
170+
{
171+
return otherSerializer;
172+
}
173+
}
174+
158175
private static bool IsAddOrSubtractExpression(Expression expression)
159176
{
160177
return expression.NodeType switch
@@ -242,9 +259,10 @@ private static AstBinaryOperator ToBinaryOperator(ExpressionType nodeType)
242259

243260
private static AggregationExpression TranslateConstant(BinaryExpression containingExpression, ConstantExpression constantExpression, IBsonSerializer otherSerializer)
244261
{
245-
var serializedValue = SerializationHelper.SerializeValue(otherSerializer, constantExpression, containingExpression);
262+
var constantSerializer = GetConstantSerializer(containingExpression, otherSerializer, constantExpression.Type);
263+
var serializedValue = SerializationHelper.SerializeValue(constantSerializer, constantExpression, containingExpression);
246264
var ast = AstExpression.Constant(serializedValue);
247-
return new AggregationExpression(constantExpression, ast, otherSerializer);
265+
return new AggregationExpression(constantExpression, ast, constantSerializer);
248266
}
249267

250268
private static AggregationExpression TranslateEnumExpression(TranslationContext context, BinaryExpression expression)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 FluentAssertions;
17+
using MongoDB.Driver.Linq;
18+
using Xunit;
19+
20+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira;
21+
22+
public class CSharp5163Tests : Linq3IntegrationTest
23+
{
24+
[Fact]
25+
public void Select_muliply_int_long_should_work()
26+
{
27+
var collection = GetCollection();
28+
29+
var queryable = collection.AsQueryable()
30+
.Select(x => x.Int * 36000000000L);
31+
32+
var stages = Translate(collection, queryable);
33+
AssertStages(stages, "{ $project : { _v : { $multiply : ['$Int', NumberLong('36000000000')] }, _id : 0 } }");
34+
35+
var result = queryable.ToList();
36+
result[0].Should().Be(36000000000L);
37+
}
38+
39+
[Fact]
40+
public void Select_muliply_byte_short_should_work()
41+
{
42+
var collection = GetCollection();
43+
44+
var queryable = collection.AsQueryable()
45+
.Select(x => x.Byte * (short)256);
46+
47+
var stages = Translate(collection, queryable);
48+
AssertStages(stages, "{ $project : { _v : { $multiply : ['$Byte', 256] }, _id : 0 } }");
49+
50+
var result = queryable.ToList();
51+
result[0].Should().Be(256);
52+
}
53+
54+
private IMongoCollection<C> GetCollection()
55+
{
56+
var collection = GetCollection<C>("test");
57+
CreateCollection(
58+
collection,
59+
new C { Int = 1, Byte = 1});
60+
return collection;
61+
}
62+
63+
private class C
64+
{
65+
public int Int { get; set; }
66+
public byte Byte { get; set; }
67+
}
68+
}

0 commit comments

Comments
 (0)