Skip to content

Commit 1df8aea

Browse files
authored
Merge pull request #214 from DataObjects-NET/7.0-mssql-datediffbig-issue-fix
Sql Server v13+ Datetime subtraction issue fix
2 parents 04eda88 + 806bafd commit 1df8aea

File tree

3 files changed

+82
-31
lines changed

3 files changed

+82
-31
lines changed

Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,18 @@ namespace Xtensive.Sql.Drivers.SqlServer.v09
1818
{
1919
internal class Compiler : SqlCompiler
2020
{
21+
#region Date parts
22+
protected const string NanosecondPart = "NS";
23+
protected const string MillisecondPart = "MS";
24+
protected const string SecondPart = "SECOND";
25+
protected const string MinutePart = "MINUTE";
26+
protected const string HourPart = "HOUR";
27+
protected const string DayPart = "DAY";
28+
protected const string MonthPart = "MONTH";
29+
protected const string YearPart = "YEAR";
30+
protected const string WeekdayPart = "WEEKDAY";
31+
#endregion
32+
2133
protected static readonly long NanosecondsPerDay = TimeSpan.FromDays(1).Ticks*100;
2234
protected static readonly long NanosecondsPerSecond = 1000000000;
2335
protected static readonly long NanosecondsPerMillisecond = 1000000;
@@ -379,64 +391,64 @@ public override void Visit(SqlCreateIndex node, IndexColumn item)
379391

380392
#region Static helpers
381393

382-
private static SqlCast CastToLong(SqlExpression arg)
394+
protected static SqlCast CastToLong(SqlExpression arg)
383395
{
384396
return SqlDml.Cast(arg, SqlType.Int64);
385397
}
386398

387-
private static SqlCast CastToDecimal(SqlExpression arg, short precision, short scale)
399+
protected static SqlCast CastToDecimal(SqlExpression arg, short precision, short scale)
388400
{
389401
return SqlDml.Cast(arg, SqlType.Decimal, precision, scale);
390402
}
391403

392404
protected static SqlUserFunctionCall DatePartWeekDay(SqlExpression date)
393405
{
394-
return SqlDml.FunctionCall("DATEPART", SqlDml.Native("WEEKDAY"), date);
406+
return SqlDml.FunctionCall("DATEPART", SqlDml.Native(WeekdayPart), date);
395407
}
396408

397409
protected static SqlUserFunctionCall DateDiffDay(SqlExpression date1, SqlExpression date2)
398410
{
399-
return SqlDml.FunctionCall("DATEDIFF", SqlDml.Native("DAY"), date1, date2);
411+
return SqlDml.FunctionCall("DATEDIFF", SqlDml.Native(DayPart), date1, date2);
400412
}
401413

402414
protected static SqlUserFunctionCall DateDiffMillisecond(SqlExpression date1, SqlExpression date2)
403415
{
404-
return SqlDml.FunctionCall("DATEDIFF", SqlDml.Native("MS"), date1, date2);
416+
return SqlDml.FunctionCall("DATEDIFF", SqlDml.Native(MillisecondPart), date1, date2);
405417
}
406418

407419
protected static SqlUserFunctionCall DateAddYear(SqlExpression date, SqlExpression years)
408420
{
409-
return SqlDml.FunctionCall("DATEADD", SqlDml.Native("YEAR"),years, date);
421+
return SqlDml.FunctionCall("DATEADD", SqlDml.Native(YearPart),years, date);
410422
}
411423

412424
protected static SqlUserFunctionCall DateAddMonth(SqlExpression date, SqlExpression months)
413425
{
414-
return SqlDml.FunctionCall("DATEADD", SqlDml.Native("MONTH"), months, date);
426+
return SqlDml.FunctionCall("DATEADD", SqlDml.Native(MonthPart), months, date);
415427
}
416428

417429
protected static SqlUserFunctionCall DateAddDay(SqlExpression date, SqlExpression days)
418430
{
419-
return SqlDml.FunctionCall("DATEADD", SqlDml.Native("DAY"), days, date);
431+
return SqlDml.FunctionCall("DATEADD", SqlDml.Native(DayPart), days, date);
420432
}
421433

422434
protected static SqlUserFunctionCall DateAddHour(SqlExpression date, SqlExpression hours)
423435
{
424-
return SqlDml.FunctionCall("DATEADD", SqlDml.Native("HOUR"), hours, date);
436+
return SqlDml.FunctionCall("DATEADD", SqlDml.Native(HourPart), hours, date);
425437
}
426438

427439
protected static SqlUserFunctionCall DateAddMinute(SqlExpression date, SqlExpression minutes)
428440
{
429-
return SqlDml.FunctionCall("DATEADD", SqlDml.Native("MINUTE"), minutes, date);
441+
return SqlDml.FunctionCall("DATEADD", SqlDml.Native(MinutePart), minutes, date);
430442
}
431443

432444
protected static SqlUserFunctionCall DateAddSecond(SqlExpression date, SqlExpression seconds)
433445
{
434-
return SqlDml.FunctionCall("DATEADD", SqlDml.Native("SECOND"), seconds, date);
446+
return SqlDml.FunctionCall("DATEADD", SqlDml.Native(SecondPart), seconds, date);
435447
}
436448

437449
protected static SqlUserFunctionCall DateAddMillisecond(SqlExpression date, SqlExpression milliseconds)
438450
{
439-
return SqlDml.FunctionCall("DATEADD", SqlDml.Native("MS"), milliseconds, date);
451+
return SqlDml.FunctionCall("DATEADD", SqlDml.Native(MillisecondPart), milliseconds, date);
440452
}
441453

442454
protected static SqlUserFunctionCall DateTimeToStringIso(SqlExpression dateTime)

Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v10/Compiler.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@ namespace Xtensive.Sql.Drivers.SqlServer.v10
1111
{
1212
internal class Compiler : v09.Compiler
1313
{
14+
protected const string OffsetPart = "TZoffset";
1415
protected const string UtcTimeZone = "+00:00";
16+
protected const string ZeroTime = "'00:00:00.0000000'";
1517
protected const string SqlDateTypeName = "date";
1618
protected const string SqlDateTime2TypeName = "datetime2";
1719

1820
protected static SqlUserFunctionCall DateAddNanosecond(SqlExpression date, SqlExpression nanoseconds) =>
19-
SqlDml.FunctionCall("DATEADD", SqlDml.Native("NS"), nanoseconds, date);
21+
SqlDml.FunctionCall("DATEADD", SqlDml.Native(NanosecondPart), nanoseconds, date);
2022

2123
protected static SqlUserFunctionCall DateDiffNanosecond(SqlExpression date1, SqlExpression date2) =>
22-
SqlDml.FunctionCall("DATEDIFF", SqlDml.Native("NS"), date1, date2);
24+
SqlDml.FunctionCall("DATEDIFF", SqlDml.Native(NanosecondPart), date1, date2);
2325

2426
protected override SqlExpression DateTimeTruncate(SqlExpression date) =>
2527
SqlDml.Cast(
@@ -144,7 +146,7 @@ private static SqlExpression DateTimeOffsetPartOffset(SqlExpression dateTimeOffs
144146

145147
private static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset) =>
146148
DateDiffMillisecond(
147-
SqlDml.Native("'00:00:00.0000000'"),
149+
SqlDml.Native(ZeroTime),
148150
SqlDml.Cast(dateTimeOffset, new SqlValueType("time")))
149151
* NanosecondsPerMillisecond;
150152

@@ -158,7 +160,7 @@ private static SqlExpression Switchoffset(SqlExpression dateTimeOffset, SqlExpre
158160
SqlDml.FunctionCall("SWITCHOFFSET", dateTimeOffset, offset);
159161

160162
private static SqlUserFunctionCall DateTimeOffsetTimeZoneInMinutes(SqlExpression date) =>
161-
SqlDml.FunctionCall("DATEPART", SqlDml.Native("TZoffset"), date);
163+
SqlDml.FunctionCall("DATEPART", SqlDml.Native(OffsetPart), date);
162164

163165
private static SqlExpression DateTimeOffsetToLocalTime(SqlExpression dateTimeOffset) =>
164166
Switchoffset(dateTimeOffset, DateTimeOffsetTimeZoneInMinutes(SqlDml.Native("SYSDATETIMEOFFSET()")));
@@ -170,7 +172,7 @@ private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime) =>
170172
SqlDml.FunctionCall("TODATETIMEOFFSET",
171173
dateTime,
172174
SqlDml.FunctionCall("DATEPART",
173-
SqlDml.Native("TZoffset"),
175+
SqlDml.Native(OffsetPart),
174176
SqlDml.Native("SYSDATETIMEOFFSET()")));
175177

176178
#endregion

Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v13/Compiler.cs

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,74 @@ namespace Xtensive.Sql.Drivers.SqlServer.v13
1010
{
1111
internal class Compiler : v12.Compiler
1212
{
13-
protected const string DayPart = "DAY";
14-
protected const string MillisecondPart = "MS";
15-
protected const string NanosecondPart = "NS";
16-
protected const string ZeroTime = "'00:00:00.0000000'";
13+
protected const string MicrosecondPart = "MCS";
14+
protected const long NanosecondsPerMicrosecond = 1000;
1715

1816
/// <inheritdoc/>
1917
public override void Visit(SqlFunctionCall node)
2018
{
21-
if (node.FunctionType == SqlFunctionType.DateTimeOffsetTimeOfDay) {
22-
DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this);
23-
}
24-
else {
25-
base.Visit(node);
19+
switch (node.FunctionType) {
20+
case SqlFunctionType.DateTimeOffsetTimeOfDay:
21+
DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this);
22+
break;
23+
case SqlFunctionType.IntervalToMilliseconds: {
24+
if (node.Arguments[0] is SqlBinary binary
25+
&& (binary.NodeType == SqlNodeType.DateTimeMinusDateTime || binary.NodeType == SqlNodeType.DateTimeOffsetMinusDateTimeOffset)) {
26+
Visit(DateDiffBigMicrosecond(binary.Right, binary.Left) / CastToLong(1000));
27+
}
28+
else {
29+
base.Visit(node);
30+
}
31+
break;
32+
}
33+
case SqlFunctionType.IntervalToNanoseconds: {
34+
if (node.Arguments[0] is SqlBinary binary
35+
&& (binary.NodeType == SqlNodeType.DateTimeMinusDateTime || binary.NodeType == SqlNodeType.DateTimeOffsetMinusDateTimeOffset)) {
36+
// we have to use time consuming algorithm here because
37+
// DATEDIFF_BIG can throw arithmetic overflow on nanoseconds
38+
// so we should handle it by this big formula
39+
Visit(CastToLong(DateTimeSubtractDateTimeExpensive(binary.Right, binary.Left)));
40+
}
41+
else {
42+
base.Visit(node);
43+
}
44+
break;
45+
}
46+
default:
47+
base.Visit(node); break;
2648
}
2749
}
2850

2951
protected override SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2)
3052
{
31-
return DateDiffBigNanosecond(date2, date1);
53+
return CastToDecimal(DateDiffBigMicrosecond(date2, date1), 18, 0) * CastToLong(1000);
3254
}
3355

34-
#region Static Helpers
56+
private SqlExpression DateTimeSubtractDateTimeExpensive(SqlExpression date1, SqlExpression date2)
57+
{
58+
return CastToDecimal(DateDiffBigDay(date2, date1), 18, 0) * NanosecondsPerDay
59+
+ CastToDecimal(DateDiffBigMillisecond(DateAddDay(date2, DateDiffBigDay(date2, date1)), date1), 18, 0) * NanosecondsPerMillisecond;
60+
}
3561

36-
protected static SqlUserFunctionCall DateDiffBigNanosecond(SqlExpression date1, SqlExpression date2) =>
37-
SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(NanosecondPart), date1, date2);
62+
#region Static Helpers
3863

39-
private static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset) =>
64+
protected static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset) =>
4065
DateDiffBigNanosecond(
4166
SqlDml.Native(ZeroTime),
4267
SqlDml.Cast(dateTimeOffset, new SqlValueType("time")));
4368

69+
protected static SqlUserFunctionCall DateDiffBigNanosecond(SqlExpression date1, SqlExpression date2) =>
70+
SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(NanosecondPart), date1, date2);
71+
72+
protected static SqlUserFunctionCall DateDiffBigMicrosecond(SqlExpression date1, SqlExpression date2) =>
73+
SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(MicrosecondPart), date1, date2);
74+
75+
protected static SqlUserFunctionCall DateDiffBigMillisecond(SqlExpression date1, SqlExpression date2) =>
76+
SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(MillisecondPart), date1, date2);
77+
78+
protected static SqlUserFunctionCall DateDiffBigDay(SqlExpression date1, SqlExpression date2) =>
79+
SqlDml.FunctionCall("DATEDIFF_BIG", SqlDml.Native(DayPart), date1, date2);
80+
4481
#endregion
4582

4683
public Compiler(SqlDriver driver)

0 commit comments

Comments
 (0)