Skip to content

Commit

Permalink
Fixed implicit type conversion when combining non-numeric and numeric…
Browse files Browse the repository at this point in the history
… values in binary operations

Throw correct error when truncating guid data in string conversion
  • Loading branch information
MarkMpn committed Oct 10, 2024
1 parent be5c5b8 commit 02122e5
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 5 deletions.
80 changes: 80 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/AdoProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2855,5 +2855,85 @@ public void ErrorNumberPersistedBetweenExecutions()
}
}
}

[TestMethod]
public void ConvertNumericToStringRespectsScale()
{
using (var con = new Sql4CdsConnection(_localDataSources))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = @"
DECLARE @i int = 473
SELECT CAST(@i / 1000.0 as VARCHAR(10))";

var actual = (string)cmd.ExecuteScalar();
Assert.AreEqual("0.473000", actual);
}
}

[TestMethod]
public void ErrorOnTruncateNumeric()
{
using (var con = new Sql4CdsConnection(_localDataSources))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = @"
DECLARE @i int = 473
SELECT CAST(@i / 1000.0 as VARCHAR(7))";

try
{
_ = (string)cmd.ExecuteScalar();
Assert.Fail();
}
catch (Sql4CdsException ex)
{
Assert.AreEqual(8115, ex.Number);
}
}
}

[TestMethod]
public void ErrorOnTruncateGuid()
{
using (var con = new Sql4CdsConnection(_localDataSources))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "SELECT CAST(NEWID() AS varchar)";

try
{
_ = (string)cmd.ExecuteScalar();
Assert.Fail();
}
catch (Sql4CdsException ex)
{
Assert.AreEqual(8170, ex.Number);
}
}
}

[TestMethod]
public void ErrorOnTruncateEntityReferenc()
{
using (var con = new Sql4CdsConnection(_localDataSources))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "INSERT INTO account (name) VALUES ('Data8')";
cmd.ExecuteNonQuery();

cmd.CommandText = "SELECT CAST(accountid AS varchar) FROM account";

try
{
_ = (string)cmd.ExecuteScalar();
Assert.Fail();
}
catch (Sql4CdsException ex)
{
Assert.AreEqual(8170, ex.Number);
}
}
}
}
}
5 changes: 5 additions & 0 deletions MarkMpn.Sql4Cds.Engine/Ado/Sql4CdsError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,11 @@ internal static Sql4CdsError GuidParseError(TSqlFragment fragment)
return Create(8169, fragment);
}

internal static Sql4CdsError GuidTruncationError(TSqlFragment fragment)
{
return Create(8170, fragment);
}

internal static Sql4CdsError AsJsonRequiresNVarCharMax(DataTypeReference fragment)
{
return Create(13618, fragment);
Expand Down
20 changes: 15 additions & 5 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -749,12 +749,22 @@ private static Expression ToExpression(Microsoft.SqlServer.TransactSql.ScriptDom

// For decimal types, need to work out the precision and scale of the result depending on the type of operation
if (type is SqlDataTypeReference sqlTargetType &&
(sqlTargetType.SqlDataTypeOption == SqlDataTypeOption.Numeric || sqlTargetType.SqlDataTypeOption == SqlDataTypeOption.Decimal) &&
lhsSqlType is SqlDataTypeReference sqlLhsType &&
(sqlLhsType.SqlDataTypeOption == SqlDataTypeOption.Numeric || sqlLhsType.SqlDataTypeOption == SqlDataTypeOption.Decimal) &&
rhsSqlType is SqlDataTypeReference sqlRhsType &&
(sqlRhsType.SqlDataTypeOption == SqlDataTypeOption.Numeric || sqlRhsType.SqlDataTypeOption == SqlDataTypeOption.Decimal))
(sqlTargetType.SqlDataTypeOption == SqlDataTypeOption.Numeric || sqlTargetType.SqlDataTypeOption == SqlDataTypeOption.Decimal))
{
var lhsIsNumeric = lhsSqlType is SqlDataTypeReference sqlLhsType && sqlLhsType.SqlDataTypeOption.IsNumeric();
var rhsIsNumeric = rhsSqlType is SqlDataTypeReference sqlRhsType && sqlRhsType.SqlDataTypeOption.IsNumeric();

if (!lhsIsNumeric && rhsIsNumeric)
{
lhs = SqlTypeConverter.Convert(lhs, contextParam, lhsSqlType, rhsSqlType);
lhsSqlType = rhsSqlType;
}
else if (lhsIsNumeric && !rhsIsNumeric)
{
rhs = SqlTypeConverter.Convert(rhs, contextParam, rhsSqlType, lhsSqlType);
rhsSqlType = lhsSqlType;
}

var p1 = lhsSqlType.GetPrecision();
var s1 = lhsSqlType.GetScale();
var p2 = rhsSqlType.GetPrecision();
Expand Down
4 changes: 4 additions & 0 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/SqlTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,10 @@ public static Expression Convert(Expression expr, Expression context, DataTypeRe
{
errorOnTruncate = _ => Sql4CdsError.ArithmeticOverflow(from, toSqlType, convert);
}
else if (sourceType == typeof(SqlGuid) || sourceType == typeof(SqlEntityReference))
{
errorOnTruncate = _ => Sql4CdsError.GuidTruncationError(convert);
}
else if (throwOnTruncate)
{
errorOnTruncate = truncated => Sql4CdsError.StringTruncation(convert, table, column, truncated);
Expand Down

0 comments on commit 02122e5

Please sign in to comment.