Skip to content

TimeOnly constructors support #321

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 26 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ff7c10c
Add tests for TimeOnly(ticks) ctor
alex-kulakov Mar 10, 2023
c5b26ca
SqlDml : add TimeConstruct method with ticks as parameter
alex-kulakov Mar 13, 2023
edef8e4
MSSQL: TimeConstruct with ticks
alex-kulakov Mar 13, 2023
0adc93e
SQLite: Support for TimeOnly(ticks) ctor
alex-kulakov Mar 13, 2023
e331fb6
PgSQL: Support for new TimeOnly(ticks)
alex-kulakov Mar 13, 2023
6975de4
SqlDml: Literals for DateOnly and TimeOnly
alex-kulakov Mar 14, 2023
9e027c4
Move IsTimeSpanTicks to SqlHelper
alex-kulakov Mar 14, 2023
036959d
Oracle: Support for TimeOnly(ticks) ctor
alex-kulakov Mar 14, 2023
5845d50
MySQL: Support for TimeOnly(ticks) ctor
alex-kulakov Mar 15, 2023
de92f46
Firebird: support for TimeOnly(ticks)
alex-kulakov Mar 15, 2023
db586ac
SqlDml.TimeToNanoseconds added
alex-kulakov Mar 16, 2023
4f7cdf9
Off-topic: Replace ArgumentValidator checks with built-in
alex-kulakov Mar 16, 2023
6c59d90
TimeOnly.Ticks support
alex-kulakov Mar 29, 2023
3e205f9
Firebird: TimeOnly construction with hours overflow check
alex-kulakov Mar 29, 2023
8b628b7
Oracle: TimeOnly construction with hours overflow check
alex-kulakov Apr 3, 2023
74c49c5
PostgreSql: TimeOnly construction with hours overflow check
alex-kulakov Apr 3, 2023
7c30810
PostgreSql: TimeOnly construction with hours overflow check
alex-kulakov Apr 3, 2023
df202d1
TimeOnlys.ConstructorTest: Add hours overflow test
alex-kulakov Apr 3, 2023
af695af
Mysql: No support for TimeConstruct operation
alex-kulakov Apr 3, 2023
46d168c
SQLite: No support for TimeConstruct
alex-kulakov Apr 3, 2023
65c5240
TimeOnlys.ConstructTest: Mysql and Sqlite are ignored
alex-kulakov Apr 3, 2023
ecc87b9
SqlTests: added test for SqlDml.TimeConstruct(ticks)
alex-kulakov Apr 9, 2023
32aff5e
Improve changelog
alex-kulakov Apr 10, 2023
bab8094
Remove double check
alex-kulakov Apr 11, 2023
b0e1d49
Copyright of files updated
alex-kulakov Apr 11, 2023
bfa1de2
Merge branch 'master' into master-timeonly-ticks
alex-kulakov Apr 11, 2023
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
3 changes: 2 additions & 1 deletion ChangeLog/7.1.0-RC2-dev.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
[main] Added support for DefaultExpression within Linq queries
[main] Added support for DefaultExpression within Linq queries
[main] Support for TimeOnly ctors (time parts and ticks) in Linq, except for SQLite and MySQL providers
100 changes: 82 additions & 18 deletions Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@
using Xtensive.Sql.Ddl;
using Xtensive.Sql.Dml;
using Xtensive.Core;
using System.Collections.Generic;

namespace Xtensive.Sql.Drivers.Firebird.v2_5
{
internal class Compiler : SqlCompiler
{
protected static readonly long NanosecondsPerDay = TimeSpan.FromDays(1).Ticks * 100;
protected static readonly long NanosecondsPerSecond = 1000000000;
protected static readonly long NanosecondsPerMillisecond = 1000000;
protected static readonly long MillisecondsPerDay = (long) TimeSpan.FromDays(1).TotalMilliseconds;
protected static readonly long MillisecondsPerSecond = 1000L;
protected const long NanosecondsPerDay = 86400000000000;
protected const long NanosecondsPerHour = 3600000000000;
protected const long NanosecondsPerMinute = 60000000000;
protected const long NanosecondsPerSecond = 1000000000;
protected const long NanosecondsPerMillisecond = 1000000;
protected const long MillisecondsPerDay = 86400000;
protected const long MillisecondsPerSecond = 1000L;

private bool case_SqlDateTimePart_DayOfYear;
private bool case_SqlDateTimePart_Second;

Expand Down Expand Up @@ -228,10 +232,7 @@ public override void Visit(SqlFunctionCall node)
Visit(DateAddYear(arguments[0], arguments[1]));
return;
case SqlFunctionType.DateTimeConstruct:
Visit(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Cast(SqlDml.Literal(new DateTime(2001, 1, 1)), SqlType.DateTime),
arguments[0] - 2001),
arguments[1] - 1),
arguments[2] - 1));
ConstructDateTime(arguments).AcceptVisitor(this);
return;
#if NET6_0_OR_GREATER
case SqlFunctionType.DateAddYears:
Expand All @@ -244,10 +245,7 @@ public override void Visit(SqlFunctionCall node)
Visit(DateAddDay(arguments[0], arguments[1]));
return;
case SqlFunctionType.DateConstruct:
Visit(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Cast(SqlDml.Literal(new DateOnly(2001, 1, 1)), SqlType.Date),
arguments[0] - 2001),
arguments[1] - 1),
arguments[2] - 1));
ConstructDate(arguments).AcceptVisitor(this);
return;
case SqlFunctionType.TimeAddHours:
Visit(DateAddHour(node.Arguments[0], node.Arguments[1]));
Expand All @@ -256,11 +254,10 @@ public override void Visit(SqlFunctionCall node)
Visit(DateAddMinute(node.Arguments[0], node.Arguments[1]));
return;
case SqlFunctionType.TimeConstruct:
Visit(DateAddMillisecond(DateAddSecond(DateAddMinute(DateAddHour(SqlDml.Cast(SqlDml.Literal(new TimeOnly(0, 0, 0)), SqlType.Time),
arguments[0]),
arguments[1]),
arguments[2]),
arguments[3]));
ConstructTime(arguments).AcceptVisitor(this);
return;
case SqlFunctionType.TimeToNanoseconds:
TimeToNanoseconds(arguments[0]).AcceptVisitor(this);
return;
case SqlFunctionType.DateToString:
Visit(DateToString(arguments[0]));
Expand Down Expand Up @@ -299,6 +296,73 @@ public override void Visit(SqlAlterSequence node)
translator.Translate(context, node, NodeSection.Exit);
}

protected virtual SqlExpression ConstructDateTime(IReadOnlyList<SqlExpression> arguments)
{
return DateAddDay(
DateAddMonth(
DateAddYear(
SqlDml.Cast(SqlDml.Literal(new DateTime(2001, 1, 1)), SqlType.DateTime),
arguments[0] - 2001),
arguments[1] - 1),
arguments[2] - 1);
}
#if NET6_0_OR_GREATER

protected virtual SqlExpression ConstructDate(IReadOnlyList<SqlExpression> arguments)
{
return DateAddDay(
DateAddMonth(
DateAddYear(
SqlDml.Cast(SqlDml.Literal(new DateOnly(2001, 1, 1)), SqlType.Date),
arguments[0] - 2001),
arguments[1] - 1),
arguments[2] - 1);
}

protected virtual SqlExpression ConstructTime(IReadOnlyList<SqlExpression> arguments)
{
SqlExpression hour, minute, second, millisecond;
if (arguments.Count == 4) {
hour = arguments[0];
minute = arguments[1];
second = arguments[2];
millisecond = arguments[3] * 10;
}
else if (arguments.Count == 1) {
var ticks = arguments[0];
// try to optimize and reduce calculations when TimeSpan.Ticks where used for TimeOnly(ticks) ctor
ticks = SqlHelper.IsTimeSpanTicks(ticks, out var sourceInterval) ? sourceInterval / 100 : ticks;
hour = SqlDml.Cast(ticks / 36000000000, SqlType.Int32);
minute = SqlDml.Cast((ticks / 600000000) % 60, SqlType.Int32);
second = SqlDml.Cast((ticks / 10000000) % 60, SqlType.Int32);
millisecond = SqlDml.Cast((ticks % 10000000) / 1000, SqlType.Int32);
}
else {
throw new InvalidOperationException("Unsupported count of parameters");
}

// using string version of time allows to control hours overflow
// we cannot add hours, minutes and other parts to 00:00:00.0000 time
// because hours might step over 24 hours and start counting from 0.
var hourString = SqlDml.Cast(hour, new SqlValueType(SqlType.VarChar, 3));
var minuteString = SqlDml.Cast(minute, new SqlValueType(SqlType.VarChar, 2));
var secondString = SqlDml.Cast(second, new SqlValueType(SqlType.VarChar, 2));
var millisecondString = SqlDml.Cast(millisecond, new SqlValueType(SqlType.VarChar, 4));
var composedTimeString = SqlDml.Concat(hourString, SqlDml.Literal(":"), minuteString, SqlDml.Literal(":"), secondString, SqlDml.Literal("."), millisecondString);
return SqlDml.Cast(composedTimeString, SqlType.Time);
}

protected virtual SqlExpression TimeToNanoseconds(SqlExpression time)
{
var nPerHour = SqlDml.Extract(SqlTimePart.Hour, time) * NanosecondsPerHour;
var nPerMinute = SqlDml.Extract(SqlTimePart.Minute, time) * NanosecondsPerMinute;
var nPerSecond = SqlDml.Extract(SqlTimePart.Second, time) * NanosecondsPerSecond;
var nPerMillisecond = SqlDml.Extract(SqlTimePart.Millisecond, time) * NanosecondsPerMillisecond;

return nPerHour + nPerMinute + nPerSecond + nPerMillisecond;
}
#endif

#region Static helpers

protected static SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2)
Expand Down
64 changes: 44 additions & 20 deletions Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@
using Xtensive.Sql.Dml;
using Xtensive.Sql.Model;
using Xtensive.Core;
using System.Collections.Generic;

namespace Xtensive.Sql.Drivers.MySql.v5_0
{
internal class Compiler : SqlCompiler
{
protected static readonly long NanosecondsPerDay = TimeSpan.FromDays(1).Ticks * 100;
protected static readonly long NanosecondsPerSecond = 1000000000;
protected static readonly long NanosecondsPerMillisecond = 1000000;
protected static readonly long NanosecondsPerMicrosecond = 1000;
protected static readonly long MillisecondsPerDay = (long) TimeSpan.FromDays(1).TotalMilliseconds;
protected static readonly long MillisecondsPerSecond = 1000L;
protected const long NanosecondsPerDay = 86400000000000;
protected const long NanosecondsPerHour = 3600000000000;
protected const long NanosecondsPerMinute = 60000000000;
protected const long NanosecondsPerSecond = 1000000000;
protected const long NanosecondsPerMillisecond = 1000000;
protected const long NanosecondsPerMicrosecond = 1000;
protected const long MillisecondsPerDay = 86400000;

/// <inheritdoc/>
public override void Visit(SqlSelect node)
Expand Down Expand Up @@ -182,10 +184,7 @@ public override void Visit(SqlFunctionCall node)
Visit(DateTimeAddYear(arguments[0], arguments[1]));
return;
case SqlFunctionType.DateTimeConstruct:
Visit(DateTimeAddDay(DateTimeAddMonth(DateTimeAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)),
arguments[0] - 2001),
arguments[1] - 1),
arguments[2] - 1));
ConstructDateTime(arguments).AcceptVisitor(this);
return;
#if NET6_0_OR_GREATER
case SqlFunctionType.DateAddYears:
Expand All @@ -198,10 +197,7 @@ public override void Visit(SqlFunctionCall node)
Visit(DateAddDay(arguments[0], arguments[1]));
return;
case SqlFunctionType.DateConstruct:
Visit(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateOnly(2001, 1, 1)),
arguments[0] - 2001),
arguments[1] - 1),
arguments[2] - 1));
ConstructDate(arguments).AcceptVisitor(this);
return;
case SqlFunctionType.TimeAddHours:
Visit(SqlDml.FunctionCall("TIME", SqlDml.FunctionCall(
Expand All @@ -219,12 +215,8 @@ public override void Visit(SqlFunctionCall node)
SqlDml.RawConcat(SqlDml.Native("INTERVAL "), SqlDml.FunctionCall("TIME_TO_SEC", arguments[0]) + arguments[1] * 60),
SqlDml.Native("SECOND")))));
return;
case SqlFunctionType.TimeConstruct:
Visit(SqlDml.FunctionCall("TIME", TimeAddMillisecond(TimeAddSecond(TimeAddMinute(TimeAddHour(SqlDml.Literal(new DateTime(2001, 1, 1)),
arguments[0]),
arguments[1]),
arguments[2]),
arguments[3])));
case SqlFunctionType.TimeToNanoseconds:
TimeToNanoseconds(arguments[0]).AcceptVisitor(this);
return;
case SqlFunctionType.DateToString:
Visit(DateToString(arguments[0]));
Expand Down Expand Up @@ -302,6 +294,17 @@ public override void Visit(SqlExtract node)
base.Visit(node);
}

protected virtual SqlExpression ConstructDateTime(IReadOnlyList<SqlExpression> arguments)
{
return DateTimeAddDay(
DateTimeAddMonth(
DateTimeAddYear(
SqlDml.Literal(new DateTime(2001, 1, 1)),
arguments[0] - 2001),
arguments[1] - 1),
arguments[2] - 1);
}

protected virtual SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2)
{
return (CastToDecimal(DateDiffDay(date1, date2), 18, 0) * NanosecondsPerDay)
Expand All @@ -317,6 +320,27 @@ protected virtual SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpre
}
#if NET6_0_OR_GREATER

protected virtual SqlExpression ConstructDate(IReadOnlyList<SqlExpression> arguments)
{
return DateAddDay(
DateAddMonth(
DateAddYear(
SqlDml.Literal(new DateOnly(2001, 1, 1)),
arguments[0] - 2001),
arguments[1] - 1),
arguments[2] - 1);
}

protected virtual SqlExpression TimeToNanoseconds(SqlExpression time)
{
var nPerHour = SqlDml.Extract(SqlTimePart.Hour, time) * NanosecondsPerHour;
var nPerMinute = SqlDml.Extract(SqlTimePart.Minute, time) * NanosecondsPerMinute;
var nPerSecond = SqlDml.Extract(SqlTimePart.Second, time) * NanosecondsPerSecond;
var nPerMillisecond = SqlDml.Extract(SqlTimePart.Millisecond, time) * NanosecondsPerMillisecond;

return nPerHour + nPerMinute + nPerSecond + nPerMillisecond;
}

protected virtual SqlExpression TimeSubtractTime(SqlExpression time1, SqlExpression time2) =>
SqlDml.Modulo(
NanosecondsPerDay + CastToDecimal(SqlDml.FunctionCall("TIME_TO_SEC", time1) - SqlDml.FunctionCall("TIME_TO_SEC", time2), 18, 0) * NanosecondsPerSecond,
Expand Down
13 changes: 3 additions & 10 deletions Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created: 2013.12.30

using System;
using System.Collections.Generic;
using Xtensive.Sql.Dml;

namespace Xtensive.Sql.Drivers.MySql.v5_6
Expand Down Expand Up @@ -40,10 +41,6 @@ public override void Visit(SqlFunctionCall node)
SqlDml.Cast(arguments[0], SqlType.DateTime),
arguments[1] * 100))); // 100 = 0:01:00
return;
case SqlFunctionType.TimeConstruct: {
Visit(MakeTime(arguments[0], arguments[1], arguments[2], arguments[3]));
return;
}
case SqlFunctionType.TimeToDateTime:
Visit(SqlDml.Cast(arguments[0], SqlType.DateTime));
return;
Expand All @@ -53,21 +50,17 @@ public override void Visit(SqlFunctionCall node)
}
}

protected override SqlExpression TimeAddInterval(SqlExpression time, SqlExpression interval)
protected override SqlUserFunctionCall TimeAddInterval(SqlExpression time, SqlExpression interval)
{
var timeAsDate = SqlDml.Cast(time, SqlType.DateTime);
return DateTimeAddMicrosecond(timeAsDate,
(interval / NanosecondsPerMillisecond * NanosecondsPerMicrosecond) % (MillisecondsPerDay * NanosecondsPerMicrosecond));
}

protected override SqlExpression TimeSubtractTime(SqlExpression time1, SqlExpression time2) =>
protected override SqlBinary TimeSubtractTime(SqlExpression time1, SqlExpression time2) =>
SqlDml.Modulo(
NanosecondsPerDay + CastToDecimal(DateTimeDiffMicrosecond(time2, time1), 18, 0) * NanosecondsPerMicrosecond,
NanosecondsPerDay);

protected SqlUserFunctionCall MakeTime(
SqlExpression hours, SqlExpression minutes, SqlExpression seconds, SqlExpression milliseconds) =>
SqlDml.FunctionCall("MAKETIME", hours, minutes, seconds + (milliseconds / 1000));
#endif

// Constructors
Expand Down
Loading