Skip to content

Commit

Permalink
Query: Assign correct type/typemapping to result of Round on SqlServer
Browse files Browse the repository at this point in the history
Resolves #27124
  • Loading branch information
smitpatel committed May 20, 2022
1 parent d69ba3c commit 543fa95
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ static void UpdateLimit(SelectExpression selectExpression)

var innerExpression = RemoveConvert(innerShaperExpression);
if (!(innerExpression is EntityShaperExpression
|| innerExpression is IncludeExpression))
|| innerExpression is IncludeExpression))
{
var sentinelExpression = innerSelectExpression.Limit!;
var sentinelNullableType = sentinelExpression.Type.MakeNullable();
Expand Down
22 changes: 18 additions & 4 deletions src/EFCore.SqlServer/Query/Internal/SqlServerMathTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,20 @@ public SqlServerMathTranslator(ISqlExpressionFactory sqlExpressionFactory)
if (TruncateMethodInfos.Contains(method))
{
var argument = arguments[0];
// Result of ROUND for float/double is always double in server side
// C# has Round over decimal/double/float only so our argument will be one of those types (compiler puts convert node)
// In database result will be same type except for float which returns double which we need to cast back to float.
var resultType = argument.Type;
if (resultType == typeof(float))
{
resultType = typeof(double);
}

var result = (SqlExpression)_sqlExpressionFactory.Function(
"ROUND",
new[] { argument, _sqlExpressionFactory.Constant(0), _sqlExpressionFactory.Constant(1) },
nullable: true,
argumentsPropagateNullability: new[] { true, false, false },
typeof(double));
resultType);

if (argument.Type == typeof(float))
{
Expand All @@ -154,13 +161,20 @@ public SqlServerMathTranslator(ISqlExpressionFactory sqlExpressionFactory)
{
var argument = arguments[0];
var digits = arguments.Count == 2 ? arguments[1] : _sqlExpressionFactory.Constant(0);
// Result of ROUND for float/double is always double in server side
// C# has Round over decimal/double/float only so our argument will be one of those types (compiler puts convert node)
// In database result will be same type except for float which returns double which we need to cast back to float.
var resultType = argument.Type;
if (resultType == typeof(float))
{
resultType = typeof(double);
}

var result = (SqlExpression)_sqlExpressionFactory.Function(
"ROUND",
new[] { argument, digits },
nullable: true,
argumentsPropagateNullability: new[] { true, true },
typeof(double));
resultType);

if (argument.Type == typeof(float))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,18 @@ FROM root c
WHERE (((c[""Discriminator""] = ""OrderDetail"") AND (c[""Quantity""] < 5)) AND (ROUND(c[""UnitPrice""]) > 10.0))");
}

public override Task Sum_over_round_works_correctly_in_projection(bool async)
=> AssertTranslationFailed(() => base.Sum_over_round_works_correctly_in_projection(async));

public override Task Sum_over_round_works_correctly_in_projection_2(bool async)
=> AssertTranslationFailed(() => base.Sum_over_round_works_correctly_in_projection_2(async));

public override Task Sum_over_truncate_works_correctly_in_projection(bool async)
=> AssertTranslationFailed(() => base.Sum_over_truncate_works_correctly_in_projection(async));

public override Task Sum_over_truncate_works_correctly_in_projection_2(bool async)
=> AssertTranslationFailed(() => base.Sum_over_truncate_works_correctly_in_projection_2(async));

public override async Task Select_math_round_int(bool async)
{
await base.Select_math_round_int(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,82 @@ public virtual Task Where_math_round(bool async)
.Where(od => Math.Round(od.UnitPrice) > 10),
entryCount: 137);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Sum_over_round_works_correctly_in_projection(bool async)
=> AssertQuery(
async,
ss => ss.Set<Order>()
.Where(o => o.OrderID < 10300)
.Select(o => new
{
o.OrderID,
Sum = o.OrderDetails.Sum(i => Math.Round(i.UnitPrice, 2))
}),
elementSorter: e => e.OrderID,
elementAsserter: (e, a) =>
{
Assert.Equal(e.OrderID, a.OrderID);
Assert.Equal(e.Sum, a.Sum);
});

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Sum_over_round_works_correctly_in_projection_2(bool async)
=> AssertQuery(
async,
ss => ss.Set<Order>()
.Where(o => o.OrderID < 10300)
.Select(o => new
{
o.OrderID,
Sum = o.OrderDetails.Select(i => i.UnitPrice * i.UnitPrice).Sum(i => Math.Round(i, 2))
}),
elementSorter: e => e.OrderID,
elementAsserter: (e, a) =>
{
Assert.Equal(e.OrderID, a.OrderID);
Assert.Equal(e.Sum, a.Sum);
});

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Sum_over_truncate_works_correctly_in_projection(bool async)
=> AssertQuery(
async,
ss => ss.Set<Order>()
.Where(o => o.OrderID < 10300)
.Select(o => new
{
o.OrderID,
Sum = o.OrderDetails.Sum(i => Math.Truncate(i.UnitPrice))
}),
elementSorter: e => e.OrderID,
elementAsserter: (e, a) =>
{
Assert.Equal(e.OrderID, a.OrderID);
Assert.Equal(e.Sum, a.Sum);
});

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Sum_over_truncate_works_correctly_in_projection_2(bool async)
=> AssertQuery(
async,
ss => ss.Set<Order>()
.Where(o => o.OrderID < 10300)
.Select(o => new
{
o.OrderID,
Sum = o.OrderDetails.Select(i => i.UnitPrice * i.UnitPrice).Sum(i => Math.Truncate(i))
}),
elementSorter: e => e.OrderID,
elementAsserter: (e, a) =>
{
Assert.Equal(e.OrderID, a.OrderID);
Assert.Equal(e.Sum, a.Sum);
});

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Select_math_round_int(bool async)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,58 @@ FROM [Order Details] AS [o]
WHERE [o].[Quantity] < CAST(5 AS smallint) AND ROUND([o].[UnitPrice], 0) > 10.0");
}

public override async Task Sum_over_round_works_correctly_in_projection(bool async)
{
await base.Sum_over_round_works_correctly_in_projection(async);

AssertSql(
@"SELECT [o].[OrderID], (
SELECT COALESCE(SUM(ROUND([o0].[UnitPrice], 2)), 0.0)
FROM [Order Details] AS [o0]
WHERE [o].[OrderID] = [o0].[OrderID]) AS [Sum]
FROM [Orders] AS [o]
WHERE [o].[OrderID] < 10300");
}

public override async Task Sum_over_round_works_correctly_in_projection_2(bool async)
{
await base.Sum_over_round_works_correctly_in_projection_2(async);

AssertSql(
@"SELECT [o].[OrderID], (
SELECT COALESCE(SUM(ROUND([o0].[UnitPrice] * [o0].[UnitPrice], 2)), 0.0)
FROM [Order Details] AS [o0]
WHERE [o].[OrderID] = [o0].[OrderID]) AS [Sum]
FROM [Orders] AS [o]
WHERE [o].[OrderID] < 10300");
}

public override async Task Sum_over_truncate_works_correctly_in_projection(bool async)
{
await base.Sum_over_truncate_works_correctly_in_projection(async);

AssertSql(
@"SELECT [o].[OrderID], (
SELECT COALESCE(SUM(ROUND([o0].[UnitPrice], 0, 1)), 0.0)
FROM [Order Details] AS [o0]
WHERE [o].[OrderID] = [o0].[OrderID]) AS [Sum]
FROM [Orders] AS [o]
WHERE [o].[OrderID] < 10300");
}

public override async Task Sum_over_truncate_works_correctly_in_projection_2(bool async)
{
await base.Sum_over_truncate_works_correctly_in_projection_2(async);

AssertSql(
@"SELECT [o].[OrderID], (
SELECT COALESCE(SUM(ROUND([o0].[UnitPrice] * [o0].[UnitPrice], 0, 1)), 0.0)
FROM [Order Details] AS [o0]
WHERE [o].[OrderID] = [o0].[OrderID]) AS [Sum]
FROM [Orders] AS [o]
WHERE [o].[OrderID] < 10300");
}

public override async Task Select_math_round_int(bool async)
{
await base.Select_math_round_int(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ public override Task Where_math_square(bool async)
public override Task Where_math_round(bool async)
=> AssertTranslationFailed(() => base.Where_math_round(async));

public override Task Sum_over_round_works_correctly_in_projection(bool async)
=> AssertTranslationFailed(() => base.Sum_over_round_works_correctly_in_projection(async));

public override Task Sum_over_round_works_correctly_in_projection_2(bool async)
=> AssertTranslationFailed(() => base.Sum_over_round_works_correctly_in_projection_2(async));

public override Task Sum_over_truncate_works_correctly_in_projection(bool async)
=> AssertTranslationFailed(() => base.Sum_over_truncate_works_correctly_in_projection(async));

public override Task Sum_over_truncate_works_correctly_in_projection_2(bool async)
=> AssertTranslationFailed(() => base.Sum_over_truncate_works_correctly_in_projection_2(async));

public override Task Where_math_round2(bool async)
=> AssertTranslationFailed(() => base.Where_math_round2(async));

Expand Down

0 comments on commit 543fa95

Please sign in to comment.