Skip to content

Commit d440d3e

Browse files
authored
Merge pull request #134 from DataObjects-NET/6.0-datetimeoffset-date-extraction-issue
Improves compilation of DateTimeOffset parts extraction for MS SQL Server
2 parents 0973687 + 51f1919 commit d440d3e

File tree

3 files changed

+141
-137
lines changed

3 files changed

+141
-137
lines changed

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

Lines changed: 101 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// Copyright (C) 2003-2010 Xtensive LLC.
2-
// All rights reserved.
3-
// For conditions of distribution and use, see license.
1+
// Copyright (C) 2009-2021 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
44
// Created by: Denis Krjuchkov
55
// Created: 2009.07.07
66

@@ -11,20 +11,20 @@ namespace Xtensive.Sql.Drivers.SqlServer.v10
1111
{
1212
internal class Compiler : v09.Compiler
1313
{
14-
protected static SqlUserFunctionCall DateAddNanosecond(SqlExpression date, SqlExpression nanoseconds)
15-
{
16-
return SqlDml.FunctionCall("DATEADD", SqlDml.Native("NS"), nanoseconds, date);
17-
}
14+
protected const string UtcTimeZone = "+00:00";
15+
protected const string SqlDateTypeName = "date";
16+
protected const string SqlDateTime2TypeName = "datetime2";
1817

19-
protected static SqlUserFunctionCall DateDiffNanosecond(SqlExpression date1, SqlExpression date2)
20-
{
21-
return SqlDml.FunctionCall("DATEDIFF", SqlDml.Native("NS"), date1, date2);
22-
}
18+
protected static SqlUserFunctionCall DateAddNanosecond(SqlExpression date, SqlExpression nanoseconds) =>
19+
SqlDml.FunctionCall("DATEADD", SqlDml.Native("NS"), nanoseconds, date);
2320

24-
protected override SqlExpression DateTimeTruncate(SqlExpression date)
25-
{
26-
return SqlDml.Cast(SqlDml.Cast(date, new SqlValueType("Date")), new SqlValueType("DateTime2"));
27-
}
21+
protected static SqlUserFunctionCall DateDiffNanosecond(SqlExpression date1, SqlExpression date2) =>
22+
SqlDml.FunctionCall("DATEDIFF", SqlDml.Native("NS"), date1, date2);
23+
24+
protected override SqlExpression DateTimeTruncate(SqlExpression date) =>
25+
SqlDml.Cast(
26+
SqlDml.Cast(date, new SqlValueType(SqlDateTypeName)),
27+
new SqlValueType(SqlDateTime2TypeName));
2828

2929
protected override SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2)
3030
{
@@ -52,32 +52,31 @@ protected override SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpr
5252

5353
public override void Visit(SqlExtract node)
5454
{
55-
if (node.DateTimeOffsetPart==SqlDateTimeOffsetPart.DayOfWeek) {
56-
Visit((DatePartWeekDay(node.Operand) + DateFirst + 6) % 7);
57-
return;
58-
}
5955
switch (node.DateTimeOffsetPart) {
60-
case SqlDateTimeOffsetPart.TimeZoneHour:
61-
Visit(DateTimeOffsetTimeZoneInMinutes(node.Operand) / 60);
62-
return;
63-
case SqlDateTimeOffsetPart.TimeZoneMinute:
64-
Visit(DateTimeOffsetTimeZoneInMinutes(node.Operand) % 60);
65-
return;
66-
case SqlDateTimeOffsetPart.Date:
67-
DateTimeOffsetTruncate(node.Operand).AcceptVisitor(this);
68-
return;
69-
case SqlDateTimeOffsetPart.DateTime:
70-
DateTimeOffsetTruncateOffset(node.Operand).AcceptVisitor(this);
71-
return;
72-
case SqlDateTimeOffsetPart.LocalDateTime:
73-
DateTimeOffsetToLocalDateTime(node.Operand).AcceptVisitor(this);
74-
return;
75-
case SqlDateTimeOffsetPart.UtcDateTime:
76-
SqlDml.Cast(Switchoffset(node.Operand, "+00:00"), SqlType.DateTime).AcceptVisitor(this);
77-
return;
78-
case SqlDateTimeOffsetPart.Offset:
79-
DateTimeOffsetPartOffset(node.Operand).AcceptVisitor(this);
80-
return;
56+
case SqlDateTimeOffsetPart.DayOfWeek:
57+
Visit((DatePartWeekDay(node.Operand) + DateFirst + 6) % 7);
58+
return;
59+
case SqlDateTimeOffsetPart.TimeZoneHour:
60+
Visit(DateTimeOffsetTimeZoneInMinutes(node.Operand) / 60);
61+
return;
62+
case SqlDateTimeOffsetPart.TimeZoneMinute:
63+
Visit(DateTimeOffsetTimeZoneInMinutes(node.Operand) % 60);
64+
return;
65+
case SqlDateTimeOffsetPart.Date:
66+
DateTimeOffsetTruncate(node.Operand).AcceptVisitor(this);
67+
return;
68+
case SqlDateTimeOffsetPart.DateTime:
69+
DateTimeOffsetTruncateOffset(node.Operand).AcceptVisitor(this);
70+
return;
71+
case SqlDateTimeOffsetPart.LocalDateTime:
72+
DateTimeOffsetToLocalDateTime(node.Operand).AcceptVisitor(this);
73+
return;
74+
case SqlDateTimeOffsetPart.UtcDateTime:
75+
SqlDml.Cast(Switchoffset(node.Operand, UtcTimeZone), SqlType.DateTime).AcceptVisitor(this);
76+
return;
77+
case SqlDateTimeOffsetPart.Offset:
78+
DateTimeOffsetPartOffset(node.Operand).AcceptVisitor(this);
79+
return;
8180
}
8281
base.Visit(node);
8382
}
@@ -86,27 +85,27 @@ public override void Visit(SqlExtract node)
8685
public override void Visit(SqlFunctionCall node)
8786
{
8887
switch (node.FunctionType) {
89-
case SqlFunctionType.DateTimeOffsetAddMonths:
90-
Visit(DateAddMonth(node.Arguments[0], node.Arguments[1]));
91-
return;
92-
case SqlFunctionType.DateTimeOffsetAddYears:
93-
Visit(DateAddYear(node.Arguments[0], node.Arguments[1]));
94-
return;
95-
case SqlFunctionType.DateTimeOffsetTimeOfDay:
96-
DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this);
97-
return;
98-
case SqlFunctionType.DateTimeOffsetConstruct:
99-
Visit(ToDateTimeOffset(node.Arguments[0], node.Arguments[1]));
100-
return;
101-
case SqlFunctionType.DateTimeOffsetToLocalTime:
102-
DateTimeOffsetToLocalTime(node.Arguments[0]).AcceptVisitor(this);
103-
return;
104-
case SqlFunctionType.DateTimeOffsetToUtcTime:
105-
DateTimeOffsetToUtcTime(node.Arguments[0]).AcceptVisitor(this);
106-
return;
107-
case SqlFunctionType.DateTimeToDateTimeOffset:
108-
DateTimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this);
109-
return;
88+
case SqlFunctionType.DateTimeOffsetAddMonths:
89+
Visit(DateAddMonth(node.Arguments[0], node.Arguments[1]));
90+
return;
91+
case SqlFunctionType.DateTimeOffsetAddYears:
92+
Visit(DateAddYear(node.Arguments[0], node.Arguments[1]));
93+
return;
94+
case SqlFunctionType.DateTimeOffsetTimeOfDay:
95+
DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this);
96+
return;
97+
case SqlFunctionType.DateTimeOffsetConstruct:
98+
Visit(ToDateTimeOffset(node.Arguments[0], node.Arguments[1]));
99+
return;
100+
case SqlFunctionType.DateTimeOffsetToLocalTime:
101+
DateTimeOffsetToLocalTime(node.Arguments[0]).AcceptVisitor(this);
102+
return;
103+
case SqlFunctionType.DateTimeOffsetToUtcTime:
104+
DateTimeOffsetToUtcTime(node.Arguments[0]).AcceptVisitor(this);
105+
return;
106+
case SqlFunctionType.DateTimeToDateTimeOffset:
107+
DateTimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this);
108+
return;
110109
}
111110

112111
base.Visit(node);
@@ -115,93 +114,64 @@ public override void Visit(SqlFunctionCall node)
115114
public override void Visit(SqlBinary node)
116115
{
117116
switch (node.NodeType) {
118-
case SqlNodeType.DateTimeOffsetPlusInterval:
119-
DateTimeAddInterval(node.Left, node.Right).AcceptVisitor(this);
120-
return;
121-
case SqlNodeType.DateTimeOffsetMinusDateTimeOffset:
122-
DateTimeOffsetSubtractDateTimeOffset(node.Left, node.Right).AcceptVisitor(this);
123-
return;
124-
case SqlNodeType.DateTimeOffsetMinusInterval:
125-
DateTimeAddInterval(node.Left, -node.Right).AcceptVisitor(this);
126-
return;
117+
case SqlNodeType.DateTimeOffsetPlusInterval:
118+
DateTimeAddInterval(node.Left, node.Right).AcceptVisitor(this);
119+
return;
120+
case SqlNodeType.DateTimeOffsetMinusDateTimeOffset:
121+
DateTimeOffsetSubtractDateTimeOffset(node.Left, node.Right).AcceptVisitor(this);
122+
return;
123+
case SqlNodeType.DateTimeOffsetMinusInterval:
124+
DateTimeAddInterval(node.Left, -node.Right).AcceptVisitor(this);
125+
return;
127126
}
128127
base.Visit(node);
129128
}
130129

131130
#region Static helpers
132131

133-
private static SqlExpression DateTimeOffsetTruncate(SqlExpression dateTimeOffset)
134-
{
135-
return SqlDml.Cast(
136-
DateAddMillisecond(
137-
DateAddSecond(
138-
DateAddMinute(
139-
DateAddHour(dateTimeOffset,
140-
-SqlDml.Extract(SqlDateTimeOffsetPart.Hour, dateTimeOffset)),
141-
-SqlDml.Extract(SqlDateTimeOffsetPart.Minute, dateTimeOffset)),
142-
-SqlDml.Extract(SqlDateTimeOffsetPart.Second, dateTimeOffset)),
143-
-SqlDml.Extract(SqlDateTimeOffsetPart.Millisecond, dateTimeOffset)),
144-
SqlType.DateTime);
145-
}
132+
private static SqlExpression DateTimeOffsetTruncate(SqlExpression dateTimeOffset) =>
133+
SqlDml.Cast(
134+
SqlDml.Cast(dateTimeOffset, new SqlValueType(SqlDateTypeName)),
135+
new SqlValueType(SqlDateTime2TypeName));
146136

147-
private static SqlExpression DateTimeOffsetTruncateOffset(SqlExpression dateTimeOffset)
148-
{
149-
return SqlDml.Cast(dateTimeOffset, SqlType.DateTime);
150-
}
137+
private static SqlExpression DateTimeOffsetTruncateOffset(SqlExpression dateTimeOffset) =>
138+
SqlDml.Cast(dateTimeOffset, SqlType.DateTime);
151139

152-
private static SqlExpression DateTimeOffsetPartOffset(SqlExpression dateTimeOffset)
153-
{
154-
return SqlDml.DateTimeOffsetMinusDateTimeOffset(
140+
private static SqlExpression DateTimeOffsetPartOffset(SqlExpression dateTimeOffset) =>
141+
SqlDml.DateTimeOffsetMinusDateTimeOffset(
155142
DateTimeOffsetTruncateOffset(dateTimeOffset),
156-
Switchoffset(dateTimeOffset, "+00:00"));
157-
}
143+
Switchoffset(dateTimeOffset, UtcTimeZone));
158144

159-
private static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset)
160-
{
161-
return SqlDml.Extract(SqlDateTimeOffsetPart.Hour, dateTimeOffset) * (60 * 60 * NanosecondsPerSecond)
162-
+ SqlDml.Extract(SqlDateTimeOffsetPart.Minute, dateTimeOffset) * (60 * NanosecondsPerSecond)
163-
+ SqlDml.Extract(SqlDateTimeOffsetPart.Second, dateTimeOffset) * NanosecondsPerSecond
164-
+ SqlDml.Extract(SqlDateTimeOffsetPart.Millisecond, dateTimeOffset) * NanosecondsPerMillisecond;
165-
}
145+
private static SqlExpression DateTimeOffsetTimeOfDay(SqlExpression dateTimeOffset) =>
146+
DateDiffMillisecond(
147+
SqlDml.Native("'00:00:00.0000000'"),
148+
SqlDml.Cast(dateTimeOffset, new SqlValueType("time")))
149+
* NanosecondsPerMillisecond;
166150

167-
private static SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression dateTimeOffset)
168-
{
169-
return SqlDml.Cast(
170-
SqlDml.DateTimePlusInterval(
171-
Switchoffset(dateTimeOffset, "+00:00"),
172-
SqlDml.DateTimeMinusDateTime(SqlDml.Native("getdate()"), SqlDml.Native("getutcdate()"))),
173-
SqlType.DateTime);
174-
}
151+
private static SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression dateTimeOffset) =>
152+
SqlDml.Cast(DateTimeOffsetToLocalTime(dateTimeOffset), SqlType.DateTime);
175153

176-
private static SqlUserFunctionCall ToDateTimeOffset(SqlExpression dateTime, SqlExpression offsetInMinutes)
177-
{
178-
return SqlDml.FunctionCall("TODATETIMEOFFSET", dateTime, offsetInMinutes);
179-
}
154+
private static SqlUserFunctionCall ToDateTimeOffset(SqlExpression dateTime, SqlExpression offsetInMinutes) =>
155+
SqlDml.FunctionCall("TODATETIMEOFFSET", dateTime, offsetInMinutes);
180156

181-
private static SqlExpression Switchoffset(SqlExpression dateTimeOffset, SqlExpression offset)
182-
{
183-
return SqlDml.FunctionCall("SWITCHOFFSET", dateTimeOffset, offset);
184-
}
157+
private static SqlExpression Switchoffset(SqlExpression dateTimeOffset, SqlExpression offset) =>
158+
SqlDml.FunctionCall("SWITCHOFFSET", dateTimeOffset, offset);
185159

186-
private static SqlUserFunctionCall DateTimeOffsetTimeZoneInMinutes(SqlExpression date)
187-
{
188-
return SqlDml.FunctionCall("DATEPART", SqlDml.Native("TZoffset"), date);
189-
}
160+
private static SqlUserFunctionCall DateTimeOffsetTimeZoneInMinutes(SqlExpression date) =>
161+
SqlDml.FunctionCall("DATEPART", SqlDml.Native("TZoffset"), date);
190162

191-
private static SqlExpression DateTimeOffsetToLocalTime(SqlExpression dateTimeOffset)
192-
{
193-
return Switchoffset(dateTimeOffset, DateTimeOffsetTimeZoneInMinutes(SqlDml.Native("SYSDATETIMEOFFSET()")));
194-
}
163+
private static SqlExpression DateTimeOffsetToLocalTime(SqlExpression dateTimeOffset) =>
164+
Switchoffset(dateTimeOffset, DateTimeOffsetTimeZoneInMinutes(SqlDml.Native("SYSDATETIMEOFFSET()")));
195165

196-
private static SqlExpression DateTimeOffsetToUtcTime(SqlExpression dateTimeOffset)
197-
{
198-
return Switchoffset(dateTimeOffset, "+00:00");
199-
}
166+
private static SqlExpression DateTimeOffsetToUtcTime(SqlExpression dateTimeOffset) =>
167+
Switchoffset(dateTimeOffset, UtcTimeZone);
200168

201-
private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime)
202-
{
203-
return SqlDml.FunctionCall("TODATETIMEOFFSET", dateTime, SqlDml.FunctionCall("DATEPART", SqlDml.Native("TZoffset"), SqlDml.Native("SYSDATETIMEOFFSET()")));
204-
}
169+
private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime) =>
170+
SqlDml.FunctionCall("TODATETIMEOFFSET",
171+
dateTime,
172+
SqlDml.FunctionCall("DATEPART",
173+
SqlDml.Native("TZoffset"),
174+
SqlDml.Native("SYSDATETIMEOFFSET()")));
205175

206176
#endregion
207177

Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTime/PartsExtractionTest.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Copyright (C) 2016 Xtensive LLC.
2-
// All rights reserved.
3-
// For conditions of distribution and use, see license.
1+
// Copyright (C) 2016-2021 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
44
// Created by: Alex Groznov
55
// Created: 2016.08.01
66

7+
using System;
78
using NUnit.Framework;
89
using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model;
910

@@ -118,6 +119,22 @@ public void ExtractDateTest()
118119
});
119120
}
120121

122+
[Test]
123+
[TestCase("2018-10-30 12:15:32.123")]
124+
[TestCase("2018-10-30 12:15:32.1234")]
125+
[TestCase("2018-10-30 12:15:32.12345")]
126+
[TestCase("2018-10-30 12:15:32.123456")]
127+
[TestCase("2018-10-30 12:15:32.1234567")]
128+
public void ExtractDateFromMicrosecondsTest(string testValueString)
129+
{
130+
Require.ProviderIs(StorageProvider.SqlServer);
131+
ExecuteInsideSession(() => {
132+
var testDateTime = DateTime.Parse(testValueString);
133+
_ = new SingleDateTimeEntity() { MillisecondDateTime = testDateTime };
134+
RunTest<SingleDateTimeEntity>(c => c.MillisecondDateTime.Date == testDateTime.Date);
135+
});
136+
}
137+
121138
[Test]
122139
public void ExtractTimeOfDayTest()
123140
{

Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/DateTimeOffset/PartsExtractionTest.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Copyright (C) 2016 Xtensive LLC.
2-
// All rights reserved.
3-
// For conditions of distribution and use, see license.
1+
// Copyright (C) 2016-2021 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
44
// Created by: Alex Groznov
55
// Created: 2016.08.01
66

7+
using System;
78
using NUnit.Framework;
89
using Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model;
910

@@ -148,6 +149,22 @@ public void ExtractDateTest()
148149
});
149150
}
150151

152+
[Test]
153+
[TestCase("2018-10-30 12:15:32.123 +05:10")]
154+
[TestCase("2018-10-30 12:15:32.1234 +05:10")]
155+
[TestCase("2018-10-30 12:15:32.12345 +05:10")]
156+
[TestCase("2018-10-30 12:15:32.123456 +05:10")]
157+
[TestCase("2018-10-30 12:15:32.1234567 +05:10")]
158+
public void ExtractDateFromMicrosecondsTest(string testValueString)
159+
{
160+
Require.ProviderIs(StorageProvider.SqlServer);
161+
ExecuteInsideSession(() => {
162+
var testDateTimeOffset = DateTimeOffset.Parse(testValueString);
163+
_ = new SingleDateTimeOffsetEntity() { MillisecondDateTimeOffset = testDateTimeOffset };
164+
RunTest<SingleDateTimeOffsetEntity>(c => c.MillisecondDateTimeOffset.Date == testDateTimeOffset.Date);
165+
});
166+
}
167+
151168
[Test]
152169
public void ExtractTimeOfDayTest()
153170
{

0 commit comments

Comments
 (0)