Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve TruncateTime canonical function performance #948

Merged
merged 4 commits into from
Jul 1, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 31 additions & 28 deletions src/EntityFramework.SqlServer/SqlGen/SqlFunctionCallHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1409,49 +1409,52 @@ private static void AppendConvertToVarchar(SqlGenerator sqlgen, SqlBuilder resul

// <summary>
// TruncateTime(DateTime X)
// PreKatmai: TRUNCATETIME(X) => CONVERT(DATETIME, CONVERT(VARCHAR(255), expression, 102), 102)
// Katmai: TRUNCATETIME(X) => CONVERT(DATETIME2, CONVERT(VARCHAR(255), expression, 102), 102)
// PreKatmai: TRUNCATETIME(X) => DATEADD(d, DATEDIFF(d, 0, expression), 0)
// Katmai: TRUNCATETIME(X) => CAST(CAST(expression AS DATE) as DATETIME2)
// TruncateTime(DateTimeOffset X)
// TRUNCATETIME(X) => CONVERT(datetimeoffset, CONVERT(VARCHAR(255), expression, 102)
// + ' 00:00:00 ' + Right(convert(varchar(255), @arg, 121), 6), 102)
// Katmai only: TRUNCATETIME(X) => TODATETIMEOFFSET(CAST(expression AS DATE), DATEPART(tz, expression)))
// </summary>
private static ISqlFragment HandleCanonicalFunctionTruncateTime(SqlGenerator sqlgen, DbFunctionExpression e)
{
//The type that we need to return is based on the argument type.
string typeName = null;
var isDateTimeOffset = false;

var typeKind = e.Arguments[0].ResultType.GetPrimitiveTypeKind();
var isDateTimeOffset = (typeKind == PrimitiveTypeKind.DateTimeOffset);

if (typeKind == PrimitiveTypeKind.DateTime)
{
typeName = sqlgen.IsPreKatmai ? "datetime" : "datetime2";
}
else if (typeKind == PrimitiveTypeKind.DateTimeOffset)
{
typeName = "datetimeoffset";
isDateTimeOffset = true;
}
else
Debug.Assert(isDateTimeOffset || typeKind == PrimitiveTypeKind.DateTime, "Unexpected type to TruncateTime" + typeKind.ToString());

if (sqlgen.IsPreKatmai && isDateTimeOffset)
{
Debug.Assert(true, "Unexpected type to TruncateTime" + typeKind.ToString());
throw new NotSupportedException(Strings.SqlGen_CanonicalFunctionNotSupportedPriorSql10(e.Function.Name));
}


var result = new SqlBuilder();
result.Append("convert (");
result.Append(typeName);
result.Append(", convert(varchar(255), ");
result.Append(e.Arguments[0].Accept(sqlgen));
result.Append(", 102) ");
var argumentFragment = e.Arguments[0].Accept(sqlgen);

if (isDateTimeOffset)
if (sqlgen.IsPreKatmai)
{
result.Append("+ ' 00:00:00 ' + Right(convert(varchar(255), ");
result.Append(e.Arguments[0].Accept(sqlgen));
result.Append(", 121), 6) ");
result.Append("dateadd(d, datediff(d, 0, ");
result.Append(argumentFragment);
result.Append("), 0)");
}
else
{
if (!isDateTimeOffset)
{
result.Append("cast(cast(");
result.Append(argumentFragment);
result.Append(" as date) as datetime2)");
}
else
{
result.Append("todatetimeoffset(cast(");
result.Append(argumentFragment);
result.Append(" as date), datepart(tz, ");
result.Append(argumentFragment);
result.Append("))");
}
}

result.Append(", 102)");
return result;
}

Expand Down
12 changes: 3 additions & 9 deletions src/EntityFramework.SqlServerCompact/SqlGen/SqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3144,13 +3144,10 @@ private static void AppendConvertToNVarchar(SqlGenerator sqlgen, SqlBuilder resu

// <summary>
// TruncateTime(DateTime X)
// TRUNCATETIME(X) => CONVERT(DATETIME, CONVERT(VARCHAR(255), expression, 102), 102)
// TRUNCATETIME(X) => DATEADD(d, DATEDIFF(d, 0, expression), 0)
// </summary>
private static ISqlFragment HandleCanonicalFunctionTruncateTime(SqlGenerator sqlgen, DbFunctionExpression e)
{
//The type that we need to return is based on the argument type.
string typeName = "datetime";

PrimitiveTypeKind typeKind;
TypeHelpers.TryGetPrimitiveTypeKind(e.ResultType, out typeKind);

Expand All @@ -3160,13 +3157,10 @@ private static ISqlFragment HandleCanonicalFunctionTruncateTime(SqlGenerator sql
}

var result = new SqlBuilder();
result.Append("convert (");
result.Append(typeName);
result.Append(", convert(nvarchar(255), ");
result.Append("dateadd(d, datediff(d, 0, ");
result.Append(e.Arguments[0].Accept(sqlgen));
result.Append(", 102) ");
result.Append("), 0)");

result.Append(", 102)");
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ public void CanonicalFunction_truncatetime_translated_properly_to_sql_function()
using (var context = GetArubaCeContext())
{
var query = context.CreateQuery<DateTime>(@"SELECT VALUE Edm.TruncateTime(A.c5_datetime) FROM ArubaCeContext.AllTypes AS A");
Assert.Contains("CONVERT", query.ToTraceString().ToUpperInvariant());
Assert.Contains("CAST(CAST([EXTENT1].[C5_DATETIME] AS DATE) AS DATETIME2) ", query.ToTraceString().ToUpperInvariant());
Assert.Equal(new DateTime(1990, 2, 2, 0, 0, 0), query.First());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,46 @@ public void GenerateFunctionCallSql_generates_expected_sql_for_CreateDateTimeOff
Assert.Equal(expectedSql, BuildSqlForDateTimeOffset(dateTimeOffset, SqlVersion.Sql11));
}


[Fact]
public void GenerateFunctionCallSql_generates_expected_sql_for_TruncateTime_DateTime()
{
var dateTime = new DateTime(2012, 2, 29, 6, 12, 37);

const string expectedSqlPreKatmai =
"dateadd(d, datediff(d, 0, convert(datetime, '2012-02-29 06:12:37.000', 121)), 0)";
const string expectedSqlKatmai =
"cast(cast(convert(datetime2, '2012-02-29 06:12:37.0000000', 121) as date) as datetime2)";

Assert.Equal(expectedSqlPreKatmai, BuildSqlForTruncateTime(dateTime, SqlVersion.Sql8));
Assert.Equal(expectedSqlPreKatmai, BuildSqlForTruncateTime(dateTime, SqlVersion.Sql9));
Assert.Equal(expectedSqlKatmai, BuildSqlForTruncateTime(dateTime, SqlVersion.Sql10));
Assert.Equal(expectedSqlKatmai, BuildSqlForTruncateTime(dateTime, SqlVersion.Sql11));
}

[Fact]
public void GenerateFunctionCallSql_generates_expected_sql_for_TruncateTime_DateTimeOffset()
{
var dateTimeOffset = new DateTimeOffset(2012, 2, 29, 6, 12, 37, TimeSpan.FromHours(3));

const string expectedSql =
"todatetimeoffset(cast(convert(DateTimeOffset, '2012-02-29 06:12:37.0000000 +03:00', 121) as date), datepart(tz, convert(DateTimeOffset, '2012-02-29 06:12:37.0000000 +03:00', 121)))";

Assert.Equal(Strings.SqlGen_CanonicalFunctionNotSupportedPriorSql10("TruncateTime"),
Assert.Throws<NotSupportedException>(
() =>
BuildSqlForTruncateTime(dateTimeOffset, SqlVersion.Sql8)).Message);

Assert.Equal(Strings.SqlGen_CanonicalFunctionNotSupportedPriorSql10("TruncateTime"),
Assert.Throws<NotSupportedException>(
() =>
BuildSqlForTruncateTime(dateTimeOffset, SqlVersion.Sql9)).Message);

Assert.Equal(expectedSql, BuildSqlForTruncateTime(dateTimeOffset, SqlVersion.Sql10));
Assert.Equal(expectedSql, BuildSqlForTruncateTime(dateTimeOffset, SqlVersion.Sql11));
}


private static string BuildSqlForDateTime(DateTime dateTime, SqlVersion sqlVersion)
{
var builder = new StringBuilder();
Expand Down Expand Up @@ -275,6 +315,48 @@ private static string BuildSqlForDateTimeOffset(DateTimeOffset dateTimeOffset, S
return builder.ToString();
}


private static string BuildSqlForTruncateTime(DateTime dateTime, SqlVersion sqlVersion)
{
var builder = new StringBuilder();

var sqlGenerator = new SqlGenerator(sqlVersion);

var functionExpression = EdmFunctions.TruncateTime(
DbExpression.FromDateTime(dateTime));

var sqlFragment = SqlFunctionCallHandler.GenerateFunctionCallSql(
sqlGenerator, functionExpression);

using (var sqlWriter = new SqlWriter(builder))
{
sqlFragment.WriteSql(sqlWriter, sqlGenerator);
}

return builder.ToString();
}

private static string BuildSqlForTruncateTime(DateTimeOffset dateTimeOffset, SqlVersion sqlVersion)
{
var builder = new StringBuilder();

var sqlGenerator = new SqlGenerator(sqlVersion);

var functionExpression = EdmFunctions.TruncateTime(
DbExpression.FromDateTimeOffset(dateTimeOffset));

var sqlFragment = SqlFunctionCallHandler.GenerateFunctionCallSql(
sqlGenerator, functionExpression);

using (var sqlWriter = new SqlWriter(builder))
{
sqlFragment.WriteSql(sqlWriter, sqlGenerator);
}

return builder.ToString();
}


private static Mock<SqlGenerator> CreateMockSqlGenerator(string storeType)
{
var mockEdmType = new Mock<EdmType>();
Expand Down