Skip to content

Commit

Permalink
[small]datetime <-> numeric conversion improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkMpn committed Oct 4, 2024
1 parent 3ec0019 commit 754b014
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 11 deletions.
22 changes: 22 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/AdoProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2496,5 +2496,27 @@ public void DateTimeStringLiterals()
}
}
}

[DataTestMethod]
[DataRow("datetime")]
[DataRow("smalldatetime")]
public void DateTimeToNumeric(string type)
{
using (var con = new Sql4CdsConnection(_localDataSources))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = $@"
declare @dt {type} = '2024-10-04 12:01:00'
select cast(@dt as float), cast(@dt as int)";

using (var reader = cmd.ExecuteReader())
{
Assert.IsTrue(reader.Read());
Assert.AreEqual(45567.500694444439, reader.GetDouble(0));
Assert.AreEqual(45568, reader.GetInt32(1));
Assert.IsFalse(reader.Read());
}
}
}
}
}
38 changes: 27 additions & 11 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/SqlTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -468,25 +468,41 @@ public static bool CanChangeTypeExplicit(DataTypeReference from, DataTypeReferen
/// <returns>An expression to generate values of the required type</returns>
public static Expression Convert(Expression expr, Expression context, Type to)
{
if (expr.Type == typeof(SqlDateTime) && (to == typeof(SqlBoolean) || to == typeof(SqlByte) || to == typeof(SqlInt16) || to == typeof(SqlInt32) || to == typeof(SqlInt64) || to == typeof(SqlDecimal) || to == typeof(SqlSingle) || to == typeof(SqlDouble)))
if ((expr.Type == typeof(SqlDateTime) || expr.Type == typeof(SqlSmallDateTime)) && (to == typeof(SqlBoolean) || to == typeof(SqlByte) || to == typeof(SqlInt16) || to == typeof(SqlInt32) || to == typeof(SqlInt64) || to == typeof(SqlDecimal) || to == typeof(SqlSingle) || to == typeof(SqlDouble)))
{
// Conversion from datetime types to numeric types uses the number of days since 1900-01-01
Expression converted;

if (expr.Type == typeof(SqlDateTime))
converted = Expression.Convert(expr, typeof(DateTime));
else
converted = Expression.PropertyOrField(expr, nameof(SqlSmallDateTime.Value));

converted = Expression.PropertyOrField(
Expression.Subtract(
converted,
Expression.Constant(new DateTime(1900, 1, 1))
),
nameof(TimeSpan.TotalDays)
);

// Round the value when converting from datetime to an integer type, rather than
// using the standard double -> int truncation
if (to == typeof(SqlBoolean) || to == typeof(SqlByte) || to == typeof(SqlInt16) || to == typeof(SqlInt32) || to == typeof(SqlInt64))
{
converted = Expr.Call(() => Math.Round(Expr.Arg<double>()), converted);
}

// Convert the result to a SqlDouble and handle null values
expr = Expression.Condition(
Expression.PropertyOrField(expr, nameof(SqlDateTime.IsNull)),
Expression.Constant(SqlDouble.Null),
Expression.Convert(
Expression.PropertyOrField(
Expression.Subtract(
Expression.Convert(expr, typeof(DateTime)),
Expression.Constant(new DateTime(1900, 1, 1))
),
nameof(TimeSpan.TotalDays)
),
typeof(SqlDouble)
Expression.Convert(converted, typeof(SqlDouble)
)
);
}

if ((expr.Type == typeof(SqlBoolean) || expr.Type == typeof(SqlByte) || expr.Type == typeof(SqlInt16) || expr.Type == typeof(SqlInt32) || expr.Type == typeof(SqlInt64) || expr.Type == typeof(SqlDecimal) || expr.Type == typeof(SqlSingle) || expr.Type == typeof(SqlDouble)) && to == typeof(SqlDateTime))
if ((expr.Type == typeof(SqlBoolean) || expr.Type == typeof(SqlByte) || expr.Type == typeof(SqlInt16) || expr.Type == typeof(SqlInt32) || expr.Type == typeof(SqlInt64) || expr.Type == typeof(SqlDecimal) || expr.Type == typeof(SqlSingle) || expr.Type == typeof(SqlDouble)) && (to == typeof(SqlDateTime) || to == typeof(SqlSmallDateTime)))
{
expr = Expression.Condition(
NullCheck(expr),
Expand Down

0 comments on commit 754b014

Please sign in to comment.