From 806bafd0573d6c86304b3220aaad47adc2dd8411 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 18 Jan 2022 09:48:54 +0500 Subject: [PATCH] Return back old DateTime substraction formula for some scenarios --- .../Sql.Drivers.SqlServer/v09/Compiler.cs | 36 ++++++---- .../Sql.Drivers.SqlServer/v10/Compiler.cs | 12 ++-- .../Sql.Drivers.SqlServer/v13/Compiler.cs | 65 +++++++++++++++---- 3 files changed, 82 insertions(+), 31 deletions(-) diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs index afa56e569f..8bbbab60d3 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs @@ -18,6 +18,18 @@ namespace Xtensive.Sql.Drivers.SqlServer.v09 { internal class Compiler : SqlCompiler { + #region Date parts + protected const string NanosecondPart = "NS"; + protected const string MillisecondPart = "MS"; + protected const string SecondPart = "SECOND"; + protected const string MinutePart = "MINUTE"; + protected const string HourPart = "HOUR"; + protected const string DayPart = "DAY"; + protected const string MonthPart = "MONTH"; + protected const string YearPart = "YEAR"; + protected const string WeekdayPart = "WEEKDAY"; + #endregion + protected static readonly long NanosecondsPerDay = TimeSpan.FromDays(1).Ticks*100; protected static readonly long NanosecondsPerSecond = 1000000000; protected static readonly long NanosecondsPerMillisecond = 1000000; @@ -379,64 +391,64 @@ public override void Visit(SqlCreateIndex node, IndexColumn item) #region Static helpers - private static SqlCast CastToLong(SqlExpression arg) + protected static SqlCast CastToLong(SqlExpression arg) { return SqlDml.Cast(arg, SqlType.Int64); } - private static SqlCast CastToDecimal(SqlExpression arg, short precision, short scale) + protected static SqlCast CastToDecimal(SqlExpression arg, short precision, short scale) { return SqlDml.Cast(arg, SqlType.Decimal, precision, scale); } protected static SqlUserFunctionCall DatePartWeekDay(SqlExpression date) { - return SqlDml.FunctionCall("DATEPART", SqlDml.Native("WEEKDAY"), date); + return SqlDml.FunctionCall("DATEPART", SqlDml.Native(WeekdayPart), date); } protected static SqlUserFunctionCall DateDiffDay(SqlExpression date1, SqlExpression date2) { - return SqlDml.FunctionCall("DATEDIFF", SqlDml.Native("DAY"), date1, date2); + return SqlDml.FunctionCall("DATEDIFF", SqlDml.Native(DayPart), date1, date2); } protected static SqlUserFunctionCall DateDiffMillisecond(SqlExpression date1, SqlExpression date2) { - return SqlDml.FunctionCall("DATEDIFF", SqlDml.Native("MS"), date1, date2); + return SqlDml.FunctionCall("DATEDIFF", SqlDml.Native(MillisecondPart), date1, date2); } protected static SqlUserFunctionCall DateAddYear(SqlExpression date, SqlExpression years) { - return SqlDml.FunctionCall("DATEADD", SqlDml.Native("YEAR"),years, date); + return SqlDml.FunctionCall("DATEADD", SqlDml.Native(YearPart),years, date); } protected static SqlUserFunctionCall DateAddMonth(SqlExpression date, SqlExpression months) { - return SqlDml.FunctionCall("DATEADD", SqlDml.Native("MONTH"), months, date); + return SqlDml.FunctionCall("DATEADD", SqlDml.Native(MonthPart), months, date); } protected static SqlUserFunctionCall DateAddDay(SqlExpression date, SqlExpression days) { - return SqlDml.FunctionCall("DATEADD", SqlDml.Native("DAY"), days, date); + return SqlDml.FunctionCall("DATEADD", SqlDml.Native(DayPart), days, date); } protected static SqlUserFunctionCall DateAddHour(SqlExpression date, SqlExpression hours) { - return SqlDml.FunctionCall("DATEADD", SqlDml.Native("HOUR"), hours, date); + return SqlDml.FunctionCall("DATEADD", SqlDml.Native(HourPart), hours, date); } protected static SqlUserFunctionCall DateAddMinute(SqlExpression date, SqlExpression minutes) { - return SqlDml.FunctionCall("DATEADD", SqlDml.Native("MINUTE"), minutes, date); + return SqlDml.FunctionCall("DATEADD", SqlDml.Native(MinutePart), minutes, date); } protected static SqlUserFunctionCall DateAddSecond(SqlExpression date, SqlExpression seconds) { - return SqlDml.FunctionCall("DATEADD", SqlDml.Native("SECOND"), seconds, date); + return SqlDml.FunctionCall("DATEADD", SqlDml.Native(SecondPart), seconds, date); } protected static SqlUserFunctionCall DateAddMillisecond(SqlExpression date, SqlExpression milliseconds) { - return SqlDml.FunctionCall("DATEADD", SqlDml.Native("MS"), milliseconds, date); + return SqlDml.FunctionCall("DATEADD", SqlDml.Native(MillisecondPart), milliseconds, date); } protected static SqlUserFunctionCall DateTimeToStringIso(SqlExpression dateTime) diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Compiler.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Compiler.cs index 259733adf4..27fca521bf 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Compiler.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Compiler.cs @@ -11,15 +11,17 @@ namespace Xtensive.Sql.Drivers.SqlServer.v10 { internal class Compiler : v09.Compiler { + protected const string OffsetPart = "TZoffset"; protected const string UtcTimeZone = "+00:00"; + protected const string ZeroTime = "'00:00:00.0000000'"; protected const string SqlDateTypeName = "date"; protected const string SqlDateTime2TypeName = "datetime2"; protected static SqlUserFunctionCall DateAddNanosecond(SqlExpression date, SqlExpression nanoseconds) => - SqlDml.FunctionCall("DATEADD", SqlDml.Native("NS"), nanoseconds, date); + SqlDml.FunctionCall("DATEADD", SqlDml.Native(NanosecondPart), nanoseconds, date); protected static SqlUserFunctionCall DateDiffNanosecond(SqlExpression date1, SqlExpression date2) => - SqlDml.FunctionCall("DATEDIFF", SqlDml.Native("NS"), date1, date2); + SqlDml.FunctionCall("DATEDIFF", SqlDml.Native(NanosecondPart), date1, date2); protected override SqlExpression DateTimeTruncate(SqlExpression date) => SqlDml.Cast( @@ -144,7 +146,7 @@ private static SqlExpression DateTimeOffsetPartOffset(SqlExpression dateTimeOffs private static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset) => DateDiffMillisecond( - SqlDml.Native("'00:00:00.0000000'"), + SqlDml.Native(ZeroTime), SqlDml.Cast(dateTimeOffset, new SqlValueType("time"))) * NanosecondsPerMillisecond; @@ -158,7 +160,7 @@ private static SqlExpression Switchoffset(SqlExpression dateTimeOffset, SqlExpre SqlDml.FunctionCall("SWITCHOFFSET", dateTimeOffset, offset); private static SqlUserFunctionCall DateTimeOffsetTimeZoneInMinutes(SqlExpression date) => - SqlDml.FunctionCall("DATEPART", SqlDml.Native("TZoffset"), date); + SqlDml.FunctionCall("DATEPART", SqlDml.Native(OffsetPart), date); private static SqlExpression DateTimeOffsetToLocalTime(SqlExpression dateTimeOffset) => Switchoffset(dateTimeOffset, DateTimeOffsetTimeZoneInMinutes(SqlDml.Native("SYSDATETIMEOFFSET()"))); @@ -170,7 +172,7 @@ private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime) => SqlDml.FunctionCall("TODATETIMEOFFSET", dateTime, SqlDml.FunctionCall("DATEPART", - SqlDml.Native("TZoffset"), + SqlDml.Native(OffsetPart), SqlDml.Native("SYSDATETIMEOFFSET()"))); #endregion diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v13/Compiler.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v13/Compiler.cs index 0c94d57e43..3354935396 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v13/Compiler.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v13/Compiler.cs @@ -10,37 +10,74 @@ namespace Xtensive.Sql.Drivers.SqlServer.v13 { internal class Compiler : v12.Compiler { - protected const string DayPart = "DAY"; - protected const string MillisecondPart = "MS"; - protected const string NanosecondPart = "NS"; - protected const string ZeroTime = "'00:00:00.0000000'"; + protected const string MicrosecondPart = "MCS"; + protected const long NanosecondsPerMicrosecond = 1000; /// public override void Visit(SqlFunctionCall node) { - if (node.FunctionType == SqlFunctionType.DateTimeOffsetTimeOfDay) { - DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this); - } - else { - base.Visit(node); + switch (node.FunctionType) { + case SqlFunctionType.DateTimeOffsetTimeOfDay: + DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this); + break; + case SqlFunctionType.IntervalToMilliseconds: { + if (node.Arguments[0] is SqlBinary binary + && (binary.NodeType == SqlNodeType.DateTimeMinusDateTime || binary.NodeType == SqlNodeType.DateTimeOffsetMinusDateTimeOffset)) { + Visit(DateDiffBigMicrosecond(binary.Right, binary.Left) / CastToLong(1000)); + } + else { + base.Visit(node); + } + break; + } + case SqlFunctionType.IntervalToNanoseconds: { + if (node.Arguments[0] is SqlBinary binary + && (binary.NodeType == SqlNodeType.DateTimeMinusDateTime || binary.NodeType == SqlNodeType.DateTimeOffsetMinusDateTimeOffset)) { + // we have to use time consuming algorithm here because + // DATEDIFF_BIG can throw arithmetic overflow on nanoseconds + // so we should handle it by this big formula + Visit(CastToLong(DateTimeSubtractDateTimeExpensive(binary.Right, binary.Left))); + } + else { + base.Visit(node); + } + break; + } + default: + base.Visit(node); break; } } protected override SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2) { - return DateDiffBigNanosecond(date2, date1); + return CastToDecimal(DateDiffBigMicrosecond(date2, date1), 18, 0) * CastToLong(1000); } - #region Static Helpers + private SqlExpression DateTimeSubtractDateTimeExpensive(SqlExpression date1, SqlExpression date2) + { + return CastToDecimal(DateDiffBigDay(date2, date1), 18, 0) * NanosecondsPerDay + + CastToDecimal(DateDiffBigMillisecond(DateAddDay(date2, DateDiffBigDay(date2, date1)), date1), 18, 0) * NanosecondsPerMillisecond; + } - protected static SqlUserFunctionCall DateDiffBigNanosecond(SqlExpression date1, SqlExpression date2) => - SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(NanosecondPart), date1, date2); + #region Static Helpers - private static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset) => + protected static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset) => DateDiffBigNanosecond( SqlDml.Native(ZeroTime), SqlDml.Cast(dateTimeOffset, new SqlValueType("time"))); + protected static SqlUserFunctionCall DateDiffBigNanosecond(SqlExpression date1, SqlExpression date2) => + SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(NanosecondPart), date1, date2); + + protected static SqlUserFunctionCall DateDiffBigMicrosecond(SqlExpression date1, SqlExpression date2) => + SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(MicrosecondPart), date1, date2); + + protected static SqlUserFunctionCall DateDiffBigMillisecond(SqlExpression date1, SqlExpression date2) => + SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(MillisecondPart), date1, date2); + + protected static SqlUserFunctionCall DateDiffBigDay(SqlExpression date1, SqlExpression date2) => + SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(DayPart), date1, date2); + #endregion public Compiler(SqlDriver driver)