Skip to content

Commit

Permalink
CSHARP-5163: Translate fails when serializing different sized numbers (
Browse files Browse the repository at this point in the history
…#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>
  • Loading branch information
janiavdv and rstam authored Jun 27, 2024
1 parent d09a511 commit 6747c43
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 16 deletions.
31 changes: 26 additions & 5 deletions src/MongoDB.Driver/Linq/Linq3Implementation/Misc/ConvertHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Bson.Serialization.Serializers;

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

public static IBsonSerializer CreateWiderSerializer(Type narrowerType, Type widerType)
{
if (IsWideningConvert(narrowerType, widerType))
{
return widerType switch
{
_ when widerType == typeof(int) => new Int32Serializer(BsonType.Int32),
_ when widerType == typeof(long) => new Int64Serializer(BsonType.Int64),
_ when widerType == typeof(decimal) => new DecimalSerializer(BsonType.Decimal128),
_ when widerType == typeof(double) => new DoubleSerializer(BsonType.Double),
_ => throw new ArgumentException($"Cannot create a wider serializer of type {widerType}.", nameof(widerType))
};
}

throw new ArgumentException($"{widerType} is not a wider type for {narrowerType}", nameof(widerType));
}

public static bool IsWideningConvert(Type sourceType, Type targetType)
{
return __wideningConverts.Contains((sourceType, targetType));
}

public static Expression RemoveConvertToMongoQueryable(Expression expression)
{
if (expression.NodeType == ExpressionType.Convert)
Expand Down Expand Up @@ -154,11 +180,6 @@ public static Expression RemoveWideningConvert(Expression expression)
}

return expression;

static bool IsWideningConvert(Type sourceType, Type targetType)
{
return __wideningConverts.Contains((sourceType, targetType));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,6 @@ public static void EnsureRepresentationIsNumeric(Expression expression, IBsonSer
{
throw new ExpressionNotSupportedException(expression, because: $"serializer for type {serializer.ValueType} uses a non-numeric representation: {representation}");
}

static bool IsNumericRepresentation(BsonType representation)
{
return representation switch
{
BsonType.Decimal128 or BsonType.Double or BsonType.Int32 or BsonType.Int64 => true,
_ => false
};
}
}

public static BsonType GetRepresentation(IBsonSerializer serializer)
Expand Down Expand Up @@ -106,6 +97,15 @@ public static BsonType GetRepresentation(IBsonSerializer serializer)
return BsonType.Undefined;
}

public static bool IsNumericRepresentation(BsonType representation)
{
return representation switch
{
BsonType.Decimal128 or BsonType.Double or BsonType.Int32 or BsonType.Int64 => true,
_ => false
};
}

public static bool IsRepresentedAsDocument(IBsonSerializer serializer)
{
return SerializationHelper.GetRepresentation(serializer) == BsonType.Document;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,23 @@ public static bool AreOperandTypesCompatible(Expression expression, Expression l
return false;
}

private static IBsonSerializer GetConstantSerializer(BinaryExpression containingExpression, IBsonSerializer otherSerializer, Type constantType)
{
if (
IsArithmeticExpression(containingExpression) &&
otherSerializer.ValueType != constantType &&
ConvertHelper.IsWideningConvert(otherSerializer.ValueType, constantType) &&
otherSerializer is IRepresentationConfigurable otherRepresentationConfigurableSerializer &&
SerializationHelper.IsNumericRepresentation(otherRepresentationConfigurableSerializer.Representation))
{
return ConvertHelper.CreateWiderSerializer(otherSerializer.ValueType, constantType);
}
else
{
return otherSerializer;
}
}

private static bool IsAddOrSubtractExpression(Expression expression)
{
return expression.NodeType switch
Expand Down Expand Up @@ -242,9 +259,10 @@ private static AstBinaryOperator ToBinaryOperator(ExpressionType nodeType)

private static AggregationExpression TranslateConstant(BinaryExpression containingExpression, ConstantExpression constantExpression, IBsonSerializer otherSerializer)
{
var serializedValue = SerializationHelper.SerializeValue(otherSerializer, constantExpression, containingExpression);
var constantSerializer = GetConstantSerializer(containingExpression, otherSerializer, constantExpression.Type);
var serializedValue = SerializationHelper.SerializeValue(constantSerializer, constantExpression, containingExpression);
var ast = AstExpression.Constant(serializedValue);
return new AggregationExpression(constantExpression, ast, otherSerializer);
return new AggregationExpression(constantExpression, ast, constantSerializer);
}

private static AggregationExpression TranslateEnumExpression(TranslationContext context, BinaryExpression expression)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using FluentAssertions;
using MongoDB.Driver.Linq;
using Xunit;

namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira;

public class CSharp5163Tests : Linq3IntegrationTest
{
[Fact]
public void Select_muliply_int_long_should_work()
{
var collection = GetCollection();

var queryable = collection.AsQueryable()
.Select(x => x.Int * 36000000000L);

var stages = Translate(collection, queryable);
AssertStages(stages, "{ $project : { _v : { $multiply : ['$Int', NumberLong('36000000000')] }, _id : 0 } }");

var result = queryable.ToList();
result[0].Should().Be(36000000000L);
}

[Fact]
public void Select_muliply_byte_short_should_work()
{
var collection = GetCollection();

var queryable = collection.AsQueryable()
.Select(x => x.Byte * (short)256);

var stages = Translate(collection, queryable);
AssertStages(stages, "{ $project : { _v : { $multiply : ['$Byte', 256] }, _id : 0 } }");

var result = queryable.ToList();
result[0].Should().Be(256);
}

private IMongoCollection<C> GetCollection()
{
var collection = GetCollection<C>("test");
CreateCollection(
collection,
new C { Int = 1, Byte = 1});
return collection;
}

private class C
{
public int Int { get; set; }
public byte Byte { get; set; }
}
}

0 comments on commit 6747c43

Please sign in to comment.