Skip to content

Commit 566de7c

Browse files
authored
Merge pull request #321 from DataObjects-NET/master-timeonly-ticks
TimeOnly constructors support
2 parents cc0cb81 + bfa1de2 commit 566de7c

File tree

20 files changed

+837
-265
lines changed

20 files changed

+837
-265
lines changed

ChangeLog/7.1.0-RC2-dev.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
[main] Added support for DefaultExpression within Linq queries
1+
[main] Added support for DefaultExpression within Linq queries
2+
[main] Support for TimeOnly ctors (time parts and ticks) in Linq, except for SQLite and MySQL providers

Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Compiler.cs

+82-18
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@
1010
using Xtensive.Sql.Ddl;
1111
using Xtensive.Sql.Dml;
1212
using Xtensive.Core;
13+
using System.Collections.Generic;
1314

1415
namespace Xtensive.Sql.Drivers.Firebird.v2_5
1516
{
1617
internal class Compiler : SqlCompiler
1718
{
18-
protected static readonly long NanosecondsPerDay = TimeSpan.FromDays(1).Ticks * 100;
19-
protected static readonly long NanosecondsPerSecond = 1000000000;
20-
protected static readonly long NanosecondsPerMillisecond = 1000000;
21-
protected static readonly long MillisecondsPerDay = (long) TimeSpan.FromDays(1).TotalMilliseconds;
22-
protected static readonly long MillisecondsPerSecond = 1000L;
19+
protected const long NanosecondsPerDay = 86400000000000;
20+
protected const long NanosecondsPerHour = 3600000000000;
21+
protected const long NanosecondsPerMinute = 60000000000;
22+
protected const long NanosecondsPerSecond = 1000000000;
23+
protected const long NanosecondsPerMillisecond = 1000000;
24+
protected const long MillisecondsPerDay = 86400000;
25+
protected const long MillisecondsPerSecond = 1000L;
26+
2327
private bool case_SqlDateTimePart_DayOfYear;
2428
private bool case_SqlDateTimePart_Second;
2529

@@ -228,10 +232,7 @@ public override void Visit(SqlFunctionCall node)
228232
Visit(DateAddYear(arguments[0], arguments[1]));
229233
return;
230234
case SqlFunctionType.DateTimeConstruct:
231-
Visit(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Cast(SqlDml.Literal(new DateTime(2001, 1, 1)), SqlType.DateTime),
232-
arguments[0] - 2001),
233-
arguments[1] - 1),
234-
arguments[2] - 1));
235+
ConstructDateTime(arguments).AcceptVisitor(this);
235236
return;
236237
#if NET6_0_OR_GREATER
237238
case SqlFunctionType.DateAddYears:
@@ -244,10 +245,7 @@ public override void Visit(SqlFunctionCall node)
244245
Visit(DateAddDay(arguments[0], arguments[1]));
245246
return;
246247
case SqlFunctionType.DateConstruct:
247-
Visit(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Cast(SqlDml.Literal(new DateOnly(2001, 1, 1)), SqlType.Date),
248-
arguments[0] - 2001),
249-
arguments[1] - 1),
250-
arguments[2] - 1));
248+
ConstructDate(arguments).AcceptVisitor(this);
251249
return;
252250
case SqlFunctionType.TimeAddHours:
253251
Visit(DateAddHour(node.Arguments[0], node.Arguments[1]));
@@ -256,11 +254,10 @@ public override void Visit(SqlFunctionCall node)
256254
Visit(DateAddMinute(node.Arguments[0], node.Arguments[1]));
257255
return;
258256
case SqlFunctionType.TimeConstruct:
259-
Visit(DateAddMillisecond(DateAddSecond(DateAddMinute(DateAddHour(SqlDml.Cast(SqlDml.Literal(new TimeOnly(0, 0, 0)), SqlType.Time),
260-
arguments[0]),
261-
arguments[1]),
262-
arguments[2]),
263-
arguments[3]));
257+
ConstructTime(arguments).AcceptVisitor(this);
258+
return;
259+
case SqlFunctionType.TimeToNanoseconds:
260+
TimeToNanoseconds(arguments[0]).AcceptVisitor(this);
264261
return;
265262
case SqlFunctionType.DateToString:
266263
Visit(DateToString(arguments[0]));
@@ -299,6 +296,73 @@ public override void Visit(SqlAlterSequence node)
299296
translator.Translate(context, node, NodeSection.Exit);
300297
}
301298

299+
protected virtual SqlExpression ConstructDateTime(IReadOnlyList<SqlExpression> arguments)
300+
{
301+
return DateAddDay(
302+
DateAddMonth(
303+
DateAddYear(
304+
SqlDml.Cast(SqlDml.Literal(new DateTime(2001, 1, 1)), SqlType.DateTime),
305+
arguments[0] - 2001),
306+
arguments[1] - 1),
307+
arguments[2] - 1);
308+
}
309+
#if NET6_0_OR_GREATER
310+
311+
protected virtual SqlExpression ConstructDate(IReadOnlyList<SqlExpression> arguments)
312+
{
313+
return DateAddDay(
314+
DateAddMonth(
315+
DateAddYear(
316+
SqlDml.Cast(SqlDml.Literal(new DateOnly(2001, 1, 1)), SqlType.Date),
317+
arguments[0] - 2001),
318+
arguments[1] - 1),
319+
arguments[2] - 1);
320+
}
321+
322+
protected virtual SqlExpression ConstructTime(IReadOnlyList<SqlExpression> arguments)
323+
{
324+
SqlExpression hour, minute, second, millisecond;
325+
if (arguments.Count == 4) {
326+
hour = arguments[0];
327+
minute = arguments[1];
328+
second = arguments[2];
329+
millisecond = arguments[3] * 10;
330+
}
331+
else if (arguments.Count == 1) {
332+
var ticks = arguments[0];
333+
// try to optimize and reduce calculations when TimeSpan.Ticks where used for TimeOnly(ticks) ctor
334+
ticks = SqlHelper.IsTimeSpanTicks(ticks, out var sourceInterval) ? sourceInterval / 100 : ticks;
335+
hour = SqlDml.Cast(ticks / 36000000000, SqlType.Int32);
336+
minute = SqlDml.Cast((ticks / 600000000) % 60, SqlType.Int32);
337+
second = SqlDml.Cast((ticks / 10000000) % 60, SqlType.Int32);
338+
millisecond = SqlDml.Cast((ticks % 10000000) / 1000, SqlType.Int32);
339+
}
340+
else {
341+
throw new InvalidOperationException("Unsupported count of parameters");
342+
}
343+
344+
// using string version of time allows to control hours overflow
345+
// we cannot add hours, minutes and other parts to 00:00:00.0000 time
346+
// because hours might step over 24 hours and start counting from 0.
347+
var hourString = SqlDml.Cast(hour, new SqlValueType(SqlType.VarChar, 3));
348+
var minuteString = SqlDml.Cast(minute, new SqlValueType(SqlType.VarChar, 2));
349+
var secondString = SqlDml.Cast(second, new SqlValueType(SqlType.VarChar, 2));
350+
var millisecondString = SqlDml.Cast(millisecond, new SqlValueType(SqlType.VarChar, 4));
351+
var composedTimeString = SqlDml.Concat(hourString, SqlDml.Literal(":"), minuteString, SqlDml.Literal(":"), secondString, SqlDml.Literal("."), millisecondString);
352+
return SqlDml.Cast(composedTimeString, SqlType.Time);
353+
}
354+
355+
protected virtual SqlExpression TimeToNanoseconds(SqlExpression time)
356+
{
357+
var nPerHour = SqlDml.Extract(SqlTimePart.Hour, time) * NanosecondsPerHour;
358+
var nPerMinute = SqlDml.Extract(SqlTimePart.Minute, time) * NanosecondsPerMinute;
359+
var nPerSecond = SqlDml.Extract(SqlTimePart.Second, time) * NanosecondsPerSecond;
360+
var nPerMillisecond = SqlDml.Extract(SqlTimePart.Millisecond, time) * NanosecondsPerMillisecond;
361+
362+
return nPerHour + nPerMinute + nPerSecond + nPerMillisecond;
363+
}
364+
#endif
365+
302366
#region Static helpers
303367

304368
protected static SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2)

Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs

+44-20
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@
1010
using Xtensive.Sql.Dml;
1111
using Xtensive.Sql.Model;
1212
using Xtensive.Core;
13+
using System.Collections.Generic;
1314

1415
namespace Xtensive.Sql.Drivers.MySql.v5_0
1516
{
1617
internal class Compiler : SqlCompiler
1718
{
18-
protected static readonly long NanosecondsPerDay = TimeSpan.FromDays(1).Ticks * 100;
19-
protected static readonly long NanosecondsPerSecond = 1000000000;
20-
protected static readonly long NanosecondsPerMillisecond = 1000000;
21-
protected static readonly long NanosecondsPerMicrosecond = 1000;
22-
protected static readonly long MillisecondsPerDay = (long) TimeSpan.FromDays(1).TotalMilliseconds;
23-
protected static readonly long MillisecondsPerSecond = 1000L;
19+
protected const long NanosecondsPerDay = 86400000000000;
20+
protected const long NanosecondsPerHour = 3600000000000;
21+
protected const long NanosecondsPerMinute = 60000000000;
22+
protected const long NanosecondsPerSecond = 1000000000;
23+
protected const long NanosecondsPerMillisecond = 1000000;
24+
protected const long NanosecondsPerMicrosecond = 1000;
25+
protected const long MillisecondsPerDay = 86400000;
2426

2527
/// <inheritdoc/>
2628
public override void Visit(SqlSelect node)
@@ -182,10 +184,7 @@ public override void Visit(SqlFunctionCall node)
182184
Visit(DateTimeAddYear(arguments[0], arguments[1]));
183185
return;
184186
case SqlFunctionType.DateTimeConstruct:
185-
Visit(DateTimeAddDay(DateTimeAddMonth(DateTimeAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)),
186-
arguments[0] - 2001),
187-
arguments[1] - 1),
188-
arguments[2] - 1));
187+
ConstructDateTime(arguments).AcceptVisitor(this);
189188
return;
190189
#if NET6_0_OR_GREATER
191190
case SqlFunctionType.DateAddYears:
@@ -198,10 +197,7 @@ public override void Visit(SqlFunctionCall node)
198197
Visit(DateAddDay(arguments[0], arguments[1]));
199198
return;
200199
case SqlFunctionType.DateConstruct:
201-
Visit(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateOnly(2001, 1, 1)),
202-
arguments[0] - 2001),
203-
arguments[1] - 1),
204-
arguments[2] - 1));
200+
ConstructDate(arguments).AcceptVisitor(this);
205201
return;
206202
case SqlFunctionType.TimeAddHours:
207203
Visit(SqlDml.FunctionCall("TIME", SqlDml.FunctionCall(
@@ -219,12 +215,8 @@ public override void Visit(SqlFunctionCall node)
219215
SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.FunctionCall("TIME_TO_SEC", arguments[0]) + arguments[1] * 60),
220216
SqlDml.Native("SECOND")))));
221217
return;
222-
case SqlFunctionType.TimeConstruct:
223-
Visit(SqlDml.FunctionCall("TIME", TimeAddMillisecond(TimeAddSecond(TimeAddMinute(TimeAddHour(SqlDml.Literal(new DateTime(2001, 1, 1)),
224-
arguments[0]),
225-
arguments[1]),
226-
arguments[2]),
227-
arguments[3])));
218+
case SqlFunctionType.TimeToNanoseconds:
219+
TimeToNanoseconds(arguments[0]).AcceptVisitor(this);
228220
return;
229221
case SqlFunctionType.DateToString:
230222
Visit(DateToString(arguments[0]));
@@ -302,6 +294,17 @@ public override void Visit(SqlExtract node)
302294
base.Visit(node);
303295
}
304296

297+
protected virtual SqlExpression ConstructDateTime(IReadOnlyList<SqlExpression> arguments)
298+
{
299+
return DateTimeAddDay(
300+
DateTimeAddMonth(
301+
DateTimeAddYear(
302+
SqlDml.Literal(new DateTime(2001, 1, 1)),
303+
arguments[0] - 2001),
304+
arguments[1] - 1),
305+
arguments[2] - 1);
306+
}
307+
305308
protected virtual SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2)
306309
{
307310
return (CastToDecimal(DateDiffDay(date1, date2), 18, 0) * NanosecondsPerDay)
@@ -317,6 +320,27 @@ protected virtual SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpre
317320
}
318321
#if NET6_0_OR_GREATER
319322

323+
protected virtual SqlExpression ConstructDate(IReadOnlyList<SqlExpression> arguments)
324+
{
325+
return DateAddDay(
326+
DateAddMonth(
327+
DateAddYear(
328+
SqlDml.Literal(new DateOnly(2001, 1, 1)),
329+
arguments[0] - 2001),
330+
arguments[1] - 1),
331+
arguments[2] - 1);
332+
}
333+
334+
protected virtual SqlExpression TimeToNanoseconds(SqlExpression time)
335+
{
336+
var nPerHour = SqlDml.Extract(SqlTimePart.Hour, time) * NanosecondsPerHour;
337+
var nPerMinute = SqlDml.Extract(SqlTimePart.Minute, time) * NanosecondsPerMinute;
338+
var nPerSecond = SqlDml.Extract(SqlTimePart.Second, time) * NanosecondsPerSecond;
339+
var nPerMillisecond = SqlDml.Extract(SqlTimePart.Millisecond, time) * NanosecondsPerMillisecond;
340+
341+
return nPerHour + nPerMinute + nPerSecond + nPerMillisecond;
342+
}
343+
320344
protected virtual SqlExpression TimeSubtractTime(SqlExpression time1, SqlExpression time2) =>
321345
SqlDml.Modulo(
322346
NanosecondsPerDay + CastToDecimal(SqlDml.FunctionCall("TIME_TO_SEC", time1) - SqlDml.FunctionCall("TIME_TO_SEC", time2), 18, 0) * NanosecondsPerSecond,

Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/Compiler.cs

+3-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created: 2013.12.30
66

77
using System;
8+
using System.Collections.Generic;
89
using Xtensive.Sql.Dml;
910

1011
namespace Xtensive.Sql.Drivers.MySql.v5_6
@@ -40,10 +41,6 @@ public override void Visit(SqlFunctionCall node)
4041
SqlDml.Cast(arguments[0], SqlType.DateTime),
4142
arguments[1] * 100))); // 100 = 0:01:00
4243
return;
43-
case SqlFunctionType.TimeConstruct: {
44-
Visit(MakeTime(arguments[0], arguments[1], arguments[2], arguments[3]));
45-
return;
46-
}
4744
case SqlFunctionType.TimeToDateTime:
4845
Visit(SqlDml.Cast(arguments[0], SqlType.DateTime));
4946
return;
@@ -53,21 +50,17 @@ public override void Visit(SqlFunctionCall node)
5350
}
5451
}
5552

56-
protected override SqlExpression TimeAddInterval(SqlExpression time, SqlExpression interval)
53+
protected override SqlUserFunctionCall TimeAddInterval(SqlExpression time, SqlExpression interval)
5754
{
5855
var timeAsDate = SqlDml.Cast(time, SqlType.DateTime);
5956
return DateTimeAddMicrosecond(timeAsDate,
6057
(interval / NanosecondsPerMillisecond * NanosecondsPerMicrosecond) % (MillisecondsPerDay * NanosecondsPerMicrosecond));
6158
}
6259

63-
protected override SqlExpression TimeSubtractTime(SqlExpression time1, SqlExpression time2) =>
60+
protected override SqlBinary TimeSubtractTime(SqlExpression time1, SqlExpression time2) =>
6461
SqlDml.Modulo(
6562
NanosecondsPerDay + CastToDecimal(DateTimeDiffMicrosecond(time2, time1), 18, 0) * NanosecondsPerMicrosecond,
6663
NanosecondsPerDay);
67-
68-
protected SqlUserFunctionCall MakeTime(
69-
SqlExpression hours, SqlExpression minutes, SqlExpression seconds, SqlExpression milliseconds) =>
70-
SqlDml.FunctionCall("MAKETIME", hours, minutes, seconds + (milliseconds / 1000));
7164
#endif
7265

7366
// Constructors

0 commit comments

Comments
 (0)