Skip to content

Sql Server v13+ Datetime subtraction issue fix #214

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

Merged
merged 1 commit into from
Jan 28, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 24 additions & 12 deletions Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 7 additions & 5 deletions Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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;

Expand All @@ -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()")));
Expand All @@ -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
Expand Down
65 changes: 51 additions & 14 deletions Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v13/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <inheritdoc/>
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)
Expand Down