diff --git a/ChangeLog/7.1.0-RC2-dev.txt b/ChangeLog/7.1.0-RC2-dev.txt index 093aeabea3..8c8dadf00b 100644 --- a/ChangeLog/7.1.0-RC2-dev.txt +++ b/ChangeLog/7.1.0-RC2-dev.txt @@ -1 +1,2 @@ -[main] Added support for DefaultExpression within Linq queries \ No newline at end of file +[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 diff --git a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Compiler.cs b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Compiler.cs index 1c00fdfaea..f7cc4dad8d 100644 --- a/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Compiler.cs +++ b/Orm/Xtensive.Orm.Firebird/Sql.Drivers.Firebird/v2_5/Compiler.cs @@ -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; @@ -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: @@ -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])); @@ -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])); @@ -299,6 +296,73 @@ public override void Visit(SqlAlterSequence node) translator.Translate(context, node, NodeSection.Exit); } + protected virtual SqlExpression ConstructDateTime(IReadOnlyList 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 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 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) diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs index ae20d304fa..76d68cbfe1 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs @@ -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; /// public override void Visit(SqlSelect node) @@ -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: @@ -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( @@ -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])); @@ -302,6 +294,17 @@ public override void Visit(SqlExtract node) base.Visit(node); } + protected virtual SqlExpression ConstructDateTime(IReadOnlyList 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) @@ -317,6 +320,27 @@ protected virtual SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpre } #if NET6_0_OR_GREATER + protected virtual SqlExpression ConstructDate(IReadOnlyList 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, diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/Compiler.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/Compiler.cs index debe6cd67f..3e6227b834 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/Compiler.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_6/Compiler.cs @@ -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 @@ -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; @@ -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 diff --git a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Compiler.cs b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Compiler.cs index 168462fb70..674f40003b 100644 --- a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Compiler.cs +++ b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v09/Compiler.cs @@ -11,6 +11,7 @@ using Xtensive.Sql.Dml; using Xtensive.Sql.Model; using Xtensive.Sql.Drivers.Oracle.Resources; +using System.Collections.Generic; namespace Xtensive.Sql.Drivers.Oracle.v09 { @@ -43,12 +44,19 @@ internal class Compiler : SqlCompiler protected const string ToDSIntervalFunctionName = "TO_DSINTERVAL"; protected const string TimeFormat = "HH24:MI:SS.FF7"; + protected const long NanosecondsPerHour = 3600000000000; + protected const long NanosecondsPerMinute = 60000000000; + protected const long NanosecondsPerSecond = 1000000000; + protected const long NanosecondsPerMillisecond = 1000000; + private static readonly SqlExpression SundayNumber = SqlDml.Native( "TO_NUMBER(TO_CHAR(TIMESTAMP '2009-07-26 00:00:00.000', 'D'))"); private static readonly SqlNative RefTimestamp = SqlDml.Native("timestamp '2009-01-01 00:00:00.0000000'"); public override void Visit(SqlFunctionCall node) { + var arguments = node.Arguments; + switch (node.FunctionType) { case SqlFunctionType.PadLeft: case SqlFunctionType.PadRight: @@ -56,107 +64,110 @@ public override void Visit(SqlFunctionCall node) return; case SqlFunctionType.DateTimeOffsetAddYears: case SqlFunctionType.DateTimeAddYears: - DateTimeAddYMInterval(node.Arguments[0], node.Arguments[1], YearIntervalPart).AcceptVisitor(this); + DateTimeAddYMInterval(arguments[0], arguments[1], YearIntervalPart).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetAddMonths: case SqlFunctionType.DateTimeAddMonths: - DateTimeAddYMInterval(node.Arguments[0], node.Arguments[1], MonthIntervalPart).AcceptVisitor(this); + DateTimeAddYMInterval(arguments[0], arguments[1], MonthIntervalPart).AcceptVisitor(this); return; case SqlFunctionType.IntervalConstruct: - IntervalConstruct(node.Arguments[0]).AcceptVisitor(this); + IntervalConstruct(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeConstruct: - DateTimeConstruct(node.Arguments[0], node.Arguments[1], node.Arguments[2]).AcceptVisitor(this); + DateTimeConstruct(arguments[0], arguments[1], arguments[2]).AcceptVisitor(this); return; #if NET6_0_OR_GREATER case SqlFunctionType.DateConstruct: - DateConstruct(node.Arguments[0], node.Arguments[1], node.Arguments[2]).AcceptVisitor(this); + DateConstruct(arguments[0], arguments[1], arguments[2]).AcceptVisitor(this); return; case SqlFunctionType.TimeConstruct: - TimeConstruct(node.Arguments[0], node.Arguments[1], node.Arguments[2], node.Arguments[3]).AcceptVisitor(this); + TimeConstruct(arguments).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToNanoseconds: + TimeToNanoseconds(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateAddYears: - DateTimeAddYMInterval(node.Arguments[0], node.Arguments[1], YearIntervalPart).AcceptVisitor(this); + DateTimeAddYMInterval(arguments[0], arguments[1], YearIntervalPart).AcceptVisitor(this); return; case SqlFunctionType.DateAddMonths: - DateTimeAddYMInterval(node.Arguments[0], node.Arguments[1], MonthIntervalPart).AcceptVisitor(this); + DateTimeAddYMInterval(arguments[0], arguments[1], MonthIntervalPart).AcceptVisitor(this); return; case SqlFunctionType.DateAddDays: - DateTimeAddDSInterval(node.Arguments[0], node.Arguments[1], DayIntervalPart).AcceptVisitor(this); + DateTimeAddDSInterval(arguments[0], arguments[1], DayIntervalPart).AcceptVisitor(this); return; case SqlFunctionType.TimeAddHours: - TimeAddHourOrMinute(node.Arguments[0], node.Arguments[1], true).AcceptVisitor(this); + TimeAddHourOrMinute(arguments[0], arguments[1], true).AcceptVisitor(this); return; case SqlFunctionType.TimeAddMinutes: - TimeAddHourOrMinute(node.Arguments[0], node.Arguments[1], false).AcceptVisitor(this); + TimeAddHourOrMinute(arguments[0], arguments[1], false).AcceptVisitor(this); return; case SqlFunctionType.DateToString: - DateToString(node.Arguments[0]).AcceptVisitor(this); + DateToString(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.TimeToString: - TimeToString(node.Arguments[0]).AcceptVisitor(this); + TimeToString(arguments[0]).AcceptVisitor(this); return; #endif case SqlFunctionType.IntervalAbs: - SqlHelper.IntervalAbs(node.Arguments[0]).AcceptVisitor(this); + SqlHelper.IntervalAbs(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.IntervalToMilliseconds: - SqlHelper.IntervalToMilliseconds(node.Arguments[0]).AcceptVisitor(this); + SqlHelper.IntervalToMilliseconds(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.IntervalToNanoseconds: - SqlHelper.IntervalToNanoseconds(node.Arguments[0]).AcceptVisitor(this); + SqlHelper.IntervalToNanoseconds(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.Position: - Position(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this); + Position(arguments[0], arguments[1]).AcceptVisitor(this); return; case SqlFunctionType.CharLength: - SqlDml.Coalesce(SqlDml.FunctionCall("LENGTH", node.Arguments[0]), 0).AcceptVisitor(this); + SqlDml.Coalesce(SqlDml.FunctionCall("LENGTH", arguments[0]), 0).AcceptVisitor(this); return; case SqlFunctionType.DateTimeToStringIso: - DateTimeToStringIso(node.Arguments[0]).AcceptVisitor(this); + DateTimeToStringIso(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetConstruct: - DateTimeOffsetConstruct(node.Arguments[0], node.Arguments[1]).AcceptVisitor(this); + DateTimeOffsetConstruct(arguments[0], arguments[1]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetTimeOfDay: - DateTimeOffsetTimeOfDay(node.Arguments[0]).AcceptVisitor(this); + DateTimeOffsetTimeOfDay(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetToLocalTime: - DateTimeOffsetToLocalTime(node.Arguments[0]).AcceptVisitor(this); + DateTimeOffsetToLocalTime(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeToDateTimeOffset: - DateTimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + DateTimeToDateTimeOffset(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetToDateTime: - DateTimeOffsetToDateTime(node.Arguments[0]).AcceptVisitor(this); + DateTimeOffsetToDateTime(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetToUtcTime: - DateTimeOffsetToUtcTime(node.Arguments[0]).AcceptVisitor(this); + DateTimeOffsetToUtcTime(arguments[0]).AcceptVisitor(this); return; #if NET6_0_OR_GREATER case SqlFunctionType.DateTimeToDate: - DateTimeToDate(node.Arguments[0]).AcceptVisitor(this); + DateTimeToDate(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateToDateTime: - DateToDateTime(node.Arguments[0]).AcceptVisitor(this); + DateToDateTime(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeToTime: - DateTimeToTime(node.Arguments[0]).AcceptVisitor(this); + DateTimeToTime(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.TimeToDateTime: - TimeToDateTime(node.Arguments[0]).AcceptVisitor(this); + TimeToDateTime(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetToDate: - DateTimeOffsetToDate(node.Arguments[0]).AcceptVisitor(this); + DateTimeOffsetToDate(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeOffsetToTime: - DateTimeOffsetToTime(node.Arguments[0]).AcceptVisitor(this); + DateTimeOffsetToTime(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateToDateTimeOffset: - DateToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + DateToDateTimeOffset(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.TimeToDateTimeOffset: - TimeToDateTimeOffset(node.Arguments[0]).AcceptVisitor(this); + TimeToDateTimeOffset(arguments[0]).AcceptVisitor(this); return; #endif default: @@ -375,6 +386,60 @@ private static SqlExpression DateConstruct(SqlExpression years, SqlExpression mo SqlDml.FunctionCall(ToCharFunctionName, ((years * 100) + months) * 100 + days), AnsiString("YYYYMMDD")); + private static SqlExpression TimeConstruct(IReadOnlyList arguments) + { + SqlExpression hour, minute, second, microsecond; + if (arguments.Count == 4) { + hour = arguments[0]; + minute = arguments[1]; + second = arguments[2]; + microsecond = arguments[3] * 10000; + } + else if (arguments.Count == 1) { + var ticks = arguments[0]; + if (SqlHelper.IsTimeSpanTicks(ticks, out var sourceExpression)) { + // try to optimize and reduce calculations when TimeSpan.Ticks where used for TimeOnly(ticks) ctor + var days = SqlDml.Extract(SqlIntervalPart.Day, sourceExpression); + var hours = days * 24 + SqlDml.Extract(SqlIntervalPart.Hour, sourceExpression); + + var hourString1 = SqlDml.Cast(hours, new SqlValueType(SqlType.VarChar, 3)); + var sourceExpressionAsString = SqlDml.FunctionCall(ToCharFunctionName, sourceExpression); + var minuteToSecondsSubstring = SqlDml.Substring(sourceExpressionAsString, SqlDml.FunctionCall("INSTR", sourceExpressionAsString, AnsiString(":")) - 1 , 16); + var composedTimeString1 = SqlDml.Concat(AnsiString("0 "), hourString1, minuteToSecondsSubstring); + return SqlDml.FunctionCall(ToDSIntervalFunctionName, new[] { composedTimeString1 }); + } + else { + hour = SqlDml.Cast(ticks / 36000000000, new SqlValueType(SqlType.Decimal, 10, 0)); + minute = SqlDml.Cast((ticks / 600000000) % 60, new SqlValueType(SqlType.Decimal, 10, 0)); + second = SqlDml.Cast((ticks / 10000000) % 60, new SqlValueType(SqlType.Decimal, 10, 0)); + microsecond = SqlDml.Cast(ticks % 10000000, new SqlValueType(SqlType.Decimal, 10, 0)); + } + } + 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.000 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 microsecondString = SqlDml.Cast(microsecond, new SqlValueType(SqlType.VarChar, 7)); + var composedTimeString = SqlDml.Concat(AnsiString("0 "), hourString, SqlDml.Literal(":"), minuteString, SqlDml.Literal(":"), secondString, SqlDml.Literal("."), microsecondString); + return SqlDml.FunctionCall(ToDSIntervalFunctionName, new[] { composedTimeString }); + } + + private static 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; + } + private static SqlExpression TimeAddHourOrMinute(SqlExpression time, SqlExpression hourOrMinute, bool isHour) { var intervalLiteral = isHour ? "INTERVAL '1' HOUR" : "INTERVAL '1' MINUTE"; @@ -392,12 +457,6 @@ private static SqlExpression TimeAddInterval(SqlExpression time, SqlExpression i return castToCorrectInterval; } - private static SqlExpression TimeConstruct( - SqlExpression hours, SqlExpression minutes, SqlExpression seconds, SqlExpression milliseconds) => - SqlDml.FunctionCall(NumToDSIntervalFunctionName, - seconds + (minutes * 60) + (hours * 3600) + (milliseconds / 1000), - AnsiString(SecondIntervalPart)); - private static SqlExpression DateToString(SqlExpression date) => SqlDml.FunctionCall(ToCharFunctionName, date, "YYYY-MM-DD"); diff --git a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v11/ServerInfoProvider.cs b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v11/ServerInfoProvider.cs index 8a5e662116..3457be75a6 100644 --- a/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v11/ServerInfoProvider.cs +++ b/Orm/Xtensive.Orm.Oracle/Sql.Drivers.Oracle/v11/ServerInfoProvider.cs @@ -1,15 +1,34 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2009-2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov // Created: 2009.07.17 +using Xtensive.Sql.Info; + namespace Xtensive.Sql.Drivers.Oracle.v11 { internal class ServerInfoProvider : v10.ServerInfoProvider { // Constructors + public override DataTypeCollection GetDataTypesInfo() + { + const DataTypeFeatures common = DataTypeFeatures.Default | DataTypeFeatures.Nullable | + DataTypeFeatures.NonKeyIndexing | DataTypeFeatures.Grouping | DataTypeFeatures.Ordering | + DataTypeFeatures.Multiple; + const DataTypeFeatures index = DataTypeFeatures.Indexing | DataTypeFeatures.Clustering | + DataTypeFeatures.FillFactor | DataTypeFeatures.KeyConstraint; + + var baseCollection = base.GetDataTypesInfo(); + + baseCollection.Interval = DataTypeInfo.Range(SqlType.Interval, common | index, + ValueRange.TimeSpan, "INTERVAL DAY(6) TO SECONDS(7)"); + + return baseCollection; + } + + public ServerInfoProvider(SqlDriver driver) : base(driver) { diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/Compiler.cs index 3f3c550c59..48554a1910 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v10_0/Compiler.cs @@ -4,32 +4,43 @@ // Created by: Alexey Kulakov // Created: 2019.09.25 +using System; +using System.Collections.Generic; using Xtensive.Sql.Dml; namespace Xtensive.Sql.Drivers.PostgreSql.v10_0 { internal class Compiler : v9_1.Compiler { - public override void Visit(SqlFunctionCall node) - { - var arguments = node.Arguments; - switch (node.FunctionType) { - case SqlFunctionType.DateTimeConstruct: - Visit(MakeDateTime(arguments[0], arguments[1], arguments[2])); - return; + protected override SqlUserFunctionCall ConstructDateTime(IReadOnlyList arguments) => MakeDateTime(arguments[0], arguments[1], arguments[2]); #if NET6_0_OR_GREATER - case SqlFunctionType.DateConstruct: - Visit(MakeDate(arguments[0], arguments[1], arguments[2])); - return; - case SqlFunctionType.TimeConstruct: - Visit(MakeTime(arguments[0], arguments[1], arguments[2], arguments[3])); - return; -#endif - default: - base.Visit(node); - return; + + protected override SqlUserFunctionCall ConstructDate(IReadOnlyList arguments) => MakeDate(arguments[0], arguments[1], arguments[2]); + + protected override SqlExpression ConstructTime(IReadOnlyList arguments) + { + if (arguments.Count == 4) { + return MakeTime(arguments[0], arguments[1], arguments[2], arguments[3], true); + } + else if (arguments.Count == 1) { + var ticks = arguments[0]; + if (SqlHelper.IsTimeSpanTicks(ticks, out var sourceInterval)) { + // try to optimize and reduce calculations when TimeSpan.Ticks where used for TimeOnly(ticks) ctor + return SqlDml.Cast(SqlDml.Cast(sourceInterval, SqlType.VarChar), SqlType.Time); + } + else { + var hour = SqlDml.Cast(ticks / 36000000000, SqlType.Int32); + var minute = SqlDml.Cast((ticks / 600000000) % 60, SqlType.Int32); + var second = SqlDml.Cast((ticks / 10000000) % 60, SqlType.Int32); + var microsecond = SqlDml.Cast((ticks % 10000000) / 10, SqlType.Int32); + return MakeTime(hour, minute, second, microsecond, false); + } + } + else { + throw new InvalidOperationException("Unsupported count of parameters"); } } +#endif protected static SqlUserFunctionCall MakeDateTime(SqlExpression year, SqlExpression month, SqlExpression day) => SqlDml.FunctionCall("MAKE_TIMESTAMP", year, month, day, SqlDml.Literal(0), SqlDml.Literal(0), SqlDml.Literal(0.0)); @@ -39,8 +50,10 @@ protected static SqlUserFunctionCall MakeDate(SqlExpression year, SqlExpression SqlDml.FunctionCall("MAKE_DATE", year, month, day); protected static SqlUserFunctionCall MakeTime( - SqlExpression hours, SqlExpression minutes, SqlExpression seconds, SqlExpression milliseconds) => - SqlDml.FunctionCall("MAKE_TIME", hours, minutes, seconds + (SqlDml.Cast(milliseconds, SqlType.Double) / 1000)); + SqlExpression hours, SqlExpression minutes, SqlExpression seconds, SqlExpression secondFractions, in bool isMilliseconds) => + (isMilliseconds) + ? SqlDml.FunctionCall("MAKE_TIME", hours, minutes, seconds + (SqlDml.Cast(secondFractions, SqlType.Double) / 1000)) + : SqlDml.FunctionCall("MAKE_TIME", hours, minutes, seconds + (SqlDml.Cast(secondFractions, SqlType.Double) / 1000000)); #endif // Constructors diff --git a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Compiler.cs b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Compiler.cs index 24d0d626aa..1ccbf10bf2 100644 --- a/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Compiler.cs +++ b/Orm/Xtensive.Orm.PostgreSql/Sql.Drivers.PostgreSql/v8_0/Compiler.cs @@ -3,6 +3,7 @@ // See the License.txt file in the project root for more information. using System; +using System.Collections.Generic; using System.Linq; using Xtensive.Orm.Providers.PostgreSql; using Xtensive.Sql.Compiler; @@ -19,6 +20,11 @@ internal class Compiler : SqlCompiler private const string TimeFormat = "HH24:MI:SS.US0"; #endif + private const long NanosecondsPerHour = 3600000000000; + private const long NanosecondsPerMinute = 60000000000; + private const long NanosecondsPerSecond = 1000000000; + private const long NanosecondsPerMillisecond = 1000000; + private static readonly Type SqlPlaceholderType = typeof(SqlPlaceholder); private static readonly SqlNative OneYearInterval = SqlDml.Native("interval '1 year'"); @@ -125,24 +131,17 @@ public override void Visit(SqlFunctionCall node) SqlHelper.IntervalAbs(node.Arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeConstruct: - var newNode = ReferenceDateTimeLiteral - + (OneYearInterval * (node.Arguments[0] - 2001)) - + (OneMonthInterval * (node.Arguments[1] - 1)) - + (OneDayInterval * (node.Arguments[2] - 1)); - newNode.AcceptVisitor(this); + ConstructDateTime(node.Arguments).AcceptVisitor(this); return; #if NET6_0_OR_GREATER case SqlFunctionType.DateConstruct: - (ReferenceDateLiteral - + (OneYearInterval * (node.Arguments[0] - 2001)) - + (OneMonthInterval * (node.Arguments[1] - 1)) - + (OneDayInterval * (node.Arguments[2] - 1))).AcceptVisitor(this); + ConstructDate(node.Arguments).AcceptVisitor(this); return; case SqlFunctionType.TimeConstruct: - ((ZeroTimeLiteral - + (OneHourInterval * (node.Arguments[0])) - + (OneMinuteInterval * (node.Arguments[1])) - + (OneSecondInterval * (node.Arguments[2] + (SqlDml.Cast(node.Arguments[3], SqlType.Double) / 1000))))).AcceptVisitor(this); + ConstructTime(node.Arguments).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToNanoseconds: + TimeToNanoseconds(node.Arguments[0]).AcceptVisitor(this); return; #endif case SqlFunctionType.DateTimeTruncate: @@ -397,6 +396,72 @@ public override void Visit(SqlExtract node) base.Visit(node); } + protected virtual SqlExpression ConstructDateTime(IReadOnlyList arguments) + { + return ReferenceDateTimeLiteral + + (OneYearInterval * (arguments[0] - 2001)) + + (OneMonthInterval * (arguments[1] - 1)) + + (OneDayInterval * (arguments[2] - 1)); + } +#if NET6_0_OR_GREATER + + protected virtual SqlExpression ConstructDate(IReadOnlyList arguments) + { + return ReferenceDateLiteral + + (OneYearInterval * (arguments[0] - 2001)) + + (OneMonthInterval * (arguments[1] - 1)) + + (OneDayInterval * (arguments[2] - 1)); + } + + protected virtual SqlExpression ConstructTime(IReadOnlyList arguments) + { + SqlExpression hour, minute, second, microsecond; + if (arguments.Count == 4) { + hour = arguments[0]; + minute = arguments[1]; + second = arguments[2]; + microsecond = arguments[3] * 1000; + } + else if (arguments.Count == 1) { + var ticks = arguments[0]; + if (SqlHelper.IsTimeSpanTicks(ticks, out var sourceInterval)) { + // try to optimize and reduce calculations when TimeSpan.Ticks where used for TimeOnly(ticks) ctor + return SqlDml.Cast(SqlDml.Cast(sourceInterval, SqlType.VarChar), SqlType.Time); + } + else { + hour = SqlDml.Cast(ticks / 36000000000, SqlType.Int32); + minute = SqlDml.Cast((ticks / 600000000) % 60, SqlType.Int32); + second = SqlDml.Cast((ticks / 10000000) % 60, SqlType.Int32); + microsecond = SqlDml.Cast((ticks % 10000000) / 10, 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.000000 time + // because hours might step over 24 hours and start counting from 0. + // Starting from v10 new function is in use, which controlls overflow + 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 microsecondString = SqlDml.Cast(microsecond, new SqlValueType(SqlType.VarChar, 7)); + var composedTimeString = SqlDml.Concat(hourString, SqlDml.Literal(":"), minuteString, SqlDml.Literal(":"), secondString, SqlDml.Literal("."), microsecondString); + 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 + protected SqlExpression DateTimeOffsetExtractDate(SqlExpression timestamp) => SqlDml.FunctionCall("DATE", timestamp); diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs index fa4798592b..4b129c77fd 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v09/Compiler.cs @@ -6,7 +6,7 @@ using System; using System.Linq; -using System.Text; +using System.Collections.Generic; using Xtensive.Sql.Compiler; using Xtensive.Sql.Info; using Xtensive.Sql.Model; @@ -30,11 +30,14 @@ internal class Compiler : SqlCompiler 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; - 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; + protected static readonly SqlExpression DateFirst = SqlDml.Native("@@DATEFIRST"); /// @@ -220,24 +223,17 @@ public override void Visit(SqlFunctionCall node) DateTimeTruncate(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateTimeConstruct: - Visit(DateAddDay(DateAddMonth(DateAddYear(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.DateConstruct: - Visit(SqlDml.Cast(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateOnly(2001, 1, 1)), - arguments[0] - 2001), - arguments[1] - 1), - arguments[2] - 1), SqlType.Date)); + ConstructDate(arguments).AcceptVisitor(this); return; case SqlFunctionType.TimeConstruct: - Visit(SqlDml.Cast(DateAddMillisecond(DateAddSecond(DateAddMinute(DateAddHour(SqlDml.Literal(new TimeOnly(0, 0, 0)), - arguments[0]), - arguments[1]), - arguments[2]), - arguments[3]), SqlType.Time)); + ConstructTime(arguments).AcceptVisitor(this); + return; + case SqlFunctionType.TimeToNanoseconds: + TimeToNanoseconds(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.DateToString: Visit(DateToString(arguments[0])); @@ -481,14 +477,99 @@ protected virtual SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpre DateAddDay(date, interval / NanosecondsPerDay), (interval / NanosecondsPerMillisecond) % (MillisecondsPerDay)); } + + /// + /// Creates expression that represents construction of datetime value + /// from arguments (year, month, day). + /// + /// Expressions representing year, month, and day. + /// Result expression. + protected virtual SqlExpression ConstructDateTime(IReadOnlyList arguments) + { + return DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateTime(2001, 1, 1)), + arguments[0] - 2001), + arguments[1] - 1), + arguments[2] - 1); + } + #if NET6_0_OR_GREATER + /// + /// Creates expression that represents construction of date value + /// from arguments (year, month, day). + /// + /// Expressions representing year, month, and day. + /// Result expression. + protected virtual SqlExpression ConstructDate(IReadOnlyList arguments) + { + return SqlDml.Cast(DateAddDay(DateAddMonth(DateAddYear(SqlDml.Literal(new DateOnly(2001, 1, 1)), + arguments[0] - 2001), + arguments[1] - 1), + arguments[2] - 1), SqlType.Date); + } + + /// + /// Creates expression that represents construction of time value from arguments. + /// + /// Expressions to construct time from. + /// Result expression. + /// + /// + protected virtual SqlExpression ConstructTime(IReadOnlyList arguments) + { + SqlExpression hour, minute, second, microsecond; + if (arguments.Count == 4) { + hour = arguments[0]; + minute = arguments[1]; + second = arguments[2]; + microsecond = arguments[3] * 10000; + } + 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); + microsecond = SqlDml.Cast(ticks % 10000000, 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.000000 time + // because hours might step over 24 hours and start counting from 0. + // Starting from v11 built-in function with hour overflow control is used. + 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 microsecondString = SqlDml.Cast(microsecond, new SqlValueType(SqlType.VarChar, 7)); + var composedTimeString = SqlDml.Concat(hourString, SqlDml.Literal(":"), minuteString, SqlDml.Literal(":"), secondString, SqlDml.Literal("."), microsecondString); + return SqlDml.Cast(composedTimeString, SqlType.Time); + } + + /// + /// Creates expression that represents conversion of time value to nanoseconds. + /// + /// Time value to convert. + /// Result expression. + 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 n = SqlDml.Extract(SqlTimePart.Nanosecond, time); + + return nPerHour + nPerMinute + nPerSecond + n; + } + /// /// Creates expression that represents addition to the given . /// /// Time expression. /// Interval expression to add. - /// + /// Result expression. protected virtual SqlExpression TimeAddInterval(SqlExpression time, SqlExpression interval) => DateAddMillisecond(time, (interval / NanosecondsPerMillisecond) % (MillisecondsPerDay)); @@ -498,7 +579,6 @@ protected virtual SqlExpression TimeAddInterval(SqlExpression time, SqlExpressio /// First expression. /// Second expression. /// Result expression. - /// protected virtual SqlExpression TimeSubtractTime(SqlExpression time1, SqlExpression time2) => SqlDml.Modulo( NanosecondsPerDay + CastToDecimal(DateDiffMillisecond(time2, time1), 18,0) * NanosecondsPerMillisecond, diff --git a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v11/Compiler.cs b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v11/Compiler.cs index be5c543cd3..948bd847b5 100644 --- a/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v11/Compiler.cs +++ b/Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/v11/Compiler.cs @@ -1,9 +1,11 @@ -// Copyright (C) 2012-2022 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2012-2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov // Created: 2012.04.02 +using System; +using System.Collections.Generic; using Xtensive.Sql.Compiler; using Xtensive.Sql.Dml; @@ -44,30 +46,40 @@ protected override void VisitSelectLimitOffset(SqlSelect node) } } - public override void Visit(SqlFunctionCall node) - { - var arguments = node.Arguments; - switch (node.FunctionType) { - case SqlFunctionType.DateTimeConstruct: - Visit(SqlDml.FunctionCall("DATETIME2FROMPARTS", arguments[0], arguments[1], arguments[2], 0, 0, 0, 0, 7)); - return; + protected override SqlUserFunctionCall ConstructDateTime(IReadOnlyList arguments) => + SqlDml.FunctionCall("DATETIME2FROMPARTS", arguments[0], arguments[1], arguments[2], 0, 0, 0, 0, 7); #if NET6_0_OR_GREATER - case SqlFunctionType.DateConstruct: { - Visit(SqlDml.FunctionCall("DATEFROMPARTS", arguments[0], arguments[1], arguments[2])); - return; - } - case SqlFunctionType.TimeConstruct: { - // argument[3] * 10000 operation is based on statement that millisaconds use 3 digits - // default precision of time is 7, and if we use raw argument[3] value the result will be .0000xxx, - // to prevent this and make fractions part valid .xxx0000 we multiply - Visit(SqlDml.FunctionCall("TIMEFROMPARTS", arguments[0], arguments[1], arguments[2], arguments[3] * 10000, 7)); - return; - } -#endif - } - base.Visit(node); + protected override SqlUserFunctionCall ConstructDate(IReadOnlyList arguments) => + SqlDml.FunctionCall("DATEFROMPARTS", arguments[0], arguments[1], arguments[2]); + + protected override SqlExpression ConstructTime(IReadOnlyList arguments) + { + SqlExpression hour, minute, second, microsecond; + if (arguments.Count == 4) { + // argument[3] * 10000 operation is based on statement that millisaconds use 3 digits + // default precision of time is 7, and if we use raw argument[3] value the result will be .0000xxx, + // to prevent this and make fractions part valid .xxx0000 we multiply + hour = arguments[0]; + minute = arguments[1]; + second = arguments[2]; + microsecond = arguments[3] * 10000; + } + 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); + microsecond = SqlDml.Cast(ticks % 10000000, SqlType.Int32); + } + else { + throw new InvalidOperationException("Unsupported count of parameters"); + } + return SqlDml.FunctionCall("TIMEFROMPARTS", hour, minute, second, microsecond, 7); } +#endif public Compiler(SqlDriver driver) : base(driver) diff --git a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs index b00bcffaf5..03a735e79b 100644 --- a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs +++ b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs @@ -17,6 +17,12 @@ namespace Xtensive.Sql.Drivers.Sqlite.v3 internal class Compiler : SqlCompiler { private const long NanosecondsPerMillisecond = 1000000L; + private const long NanosecondsPerDay = 86400000000000; + private const long NanosecondsPerHour = 3600000000000; + private const long NanosecondsPerMinute = 60000000000; + private const long NanosecondsPerSecond = 1000000000; + private const long MillisecondsPerSecond = 1000; + private const string DateWithZeroTimeFormat = "%Y-%m-%d 00:00:00.000"; #if NET6_0_OR_GREATER private const string DateFormat = "%Y-%m-%d"; @@ -26,11 +32,8 @@ internal class Compiler : SqlCompiler private const string DateTimeFormat = "%Y-%m-%d %H:%M:%f"; private const string DateTimeIsoFormat = "%Y-%m-%dT%H:%M:%S"; private const string DateTimeOffsetExampleString = "2001-02-03 04:05:06.789+02.45"; + private const string STRFTIMEFunctionName = "STRFTIME"; - private static readonly long NanosecondsPerDay = (long) TimeSpan.FromDays(1).TotalMilliseconds * NanosecondsPerMillisecond; - private static readonly long NanosecondsPerHour = (long) TimeSpan.FromHours(1).TotalMilliseconds * NanosecondsPerMillisecond; - private static readonly long NanosecondsPerSecond = (long) TimeSpan.FromSeconds(1).TotalMilliseconds * NanosecondsPerMillisecond; - private static readonly long MillisecondsPerSecond = (long) TimeSpan.FromSeconds(1).TotalMilliseconds; private static readonly int StartOffsetIndex = DateTimeOffsetExampleString.IndexOf('+'); protected override bool VisitCreateTableConstraints(SqlCreateTable node, IEnumerable constraints, bool hasItems) @@ -215,11 +218,8 @@ public override void Visit(SqlFunctionCall node) arguments[1] - 1), arguments[2] - 1).AcceptVisitor(this); return; - case SqlFunctionType.TimeConstruct: - TimeAddSeconds(TimeAddMinutes(TimeAddHours(SqlDml.Literal(new TimeOnly(0, 0, 0, 0)), - arguments[0]), - arguments[1]), - arguments[2], arguments[3]).AcceptVisitor(this); + case SqlFunctionType.TimeToNanoseconds: + TimeToNanoseconds(arguments[0]).AcceptVisitor(this); return; case SqlFunctionType.TimeAddHours: TimeAddHours(arguments[0], arguments[1]).AcceptVisitor(this); @@ -459,9 +459,9 @@ private static SqlExpression DateTimeAddInterval(SqlExpression date, SqlExpressi DateTimeAddSeconds(date, interval / Convert.ToDouble(NanosecondsPerSecond)); private static SqlExpression DateTimeTruncate(SqlExpression date) => - DateTime(SqlDml.FunctionCall("STRFTIME", DateWithZeroTimeFormat, date)); + DateTime(SqlDml.FunctionCall(STRFTIMEFunctionName, DateWithZeroTimeFormat, date)); - private static SqlExpression DateTime(SqlExpression date) => SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date); + private static SqlExpression DateTime(SqlExpression date) => SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeFormat, date); private static SqlCast CastToInt(SqlExpression arg) => SqlDml.Cast(arg, SqlType.Int32); @@ -502,53 +502,62 @@ private static SqlExpression DateTimeOffsetExtractOffsetAsTotalNanoseconds(SqlEx DateTimeSubtractDateTime(DateTimeOffsetExtractDateTimeAsString(dateTimeOffset), dateTimeOffset); private static SqlExpression DateTimeOffsetToUtcDateTime(SqlExpression dateTimeOffset) => - SqlDml.FunctionCall("STRFTIME", DateTimeFormat, dateTimeOffset, "LOCALTIME", "UTC"); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeFormat, dateTimeOffset, "LOCALTIME", "UTC"); private static SqlExpression DateTimeOffsetToLocalDateTime(SqlExpression dateTimeOffset) => - SqlDml.FunctionCall("STRFTIME", DateTimeFormat, dateTimeOffset, "LOCALTIME"); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeFormat, dateTimeOffset, "LOCALTIME"); private static SqlExpression DateTimeToDateTimeOffset(SqlExpression dateTime) => SqlDml.Concat(DateTime(dateTime), ServerOffsetAsString()); private static SqlExpression DateTimeOffsetToDateTime(SqlExpression dateTimeOffset) => SqlDml.Cast(SqlDml.Extract(SqlDateTimeOffsetPart.DateTime, dateTimeOffset), SqlType.DateTime); - //SqlDml.Concat(DateTime(node.Arguments[0]), ServerOffsetAsString()); private static SqlExpression DateTimeToStringIso(SqlExpression dateTime) => - SqlDml.FunctionCall("STRFTIME", DateTimeIsoFormat, dateTime); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeIsoFormat, dateTime); private static SqlExpression DateTimeAddYear(SqlExpression date, SqlExpression years) => - SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(years, " ", "YEARS")); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeFormat, date, SqlDml.Concat(years, " ", "YEARS")); private static SqlExpression DateTimeAddMonth(SqlExpression date, SqlExpression months) => - SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(months, " ", "MONTHS")); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeFormat, date, SqlDml.Concat(months, " ", "MONTHS")); private static SqlExpression DateTimeAddDay(SqlExpression date, SqlExpression days) => - SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(days, " ", "DAYS")); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeFormat, date, SqlDml.Concat(days, " ", "DAYS")); private static SqlExpression DateTimeAddSeconds(SqlExpression date, SqlExpression seconds) => - SqlDml.FunctionCall("STRFTIME", DateTimeFormat, date, SqlDml.Concat(seconds, " ", "SECONDS")); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeFormat, date, SqlDml.Concat(seconds, " ", "SECONDS")); #if NET6_0_OR_GREATER private static SqlExpression DateAddYear(SqlExpression date, SqlExpression years) => - SqlDml.FunctionCall("STRFTIME", DateFormat, date, SqlDml.Concat(years, " ", "YEARS")); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateFormat, date, SqlDml.Concat(years, " ", "YEARS")); private static SqlExpression DateAddMonth(SqlExpression date, SqlExpression months) => - SqlDml.FunctionCall("STRFTIME", DateFormat, date, SqlDml.Concat(months, " ", "MONTHS")); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateFormat, date, SqlDml.Concat(months, " ", "MONTHS")); private static SqlExpression DateAddDay(SqlExpression date, SqlExpression days) => - SqlDml.FunctionCall("STRFTIME", DateFormat, date, SqlDml.Concat(days, " ", "DAYS")); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateFormat, date, SqlDml.Concat(days, " ", "DAYS")); + + private static 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; + } - private static SqlExpression TimeAddHours(SqlExpression time, SqlExpression seconds) => - SqlDml.FunctionCall("STRFTIME", TimeFormat, time, SqlDml.Concat(seconds, " ", "HOURS")); + private static SqlExpression TimeAddHours(SqlExpression time, SqlExpression hours) => + SqlDml.FunctionCall(STRFTIMEFunctionName, TimeFormat, time, SqlDml.Concat(hours, " ", "HOURS")); - private static SqlExpression TimeAddMinutes(SqlExpression time, SqlExpression seconds) => - SqlDml.FunctionCall("STRFTIME", TimeFormat, time, SqlDml.Concat(seconds, " ", "MINUTES")); + private static SqlExpression TimeAddMinutes(SqlExpression time, SqlExpression minutes) => + SqlDml.FunctionCall(STRFTIMEFunctionName, TimeFormat, time, SqlDml.Concat(minutes, " ", "MINUTES")); private static SqlExpression TimeAddSeconds(SqlExpression time, SqlExpression seconds, SqlExpression milliseconds) => - SqlDml.FunctionCall("STRFTIME", TimeFormat, time, SqlDml.Concat(seconds, ".", milliseconds, " ", "SECONDS")); + SqlDml.FunctionCall(STRFTIMEFunctionName, TimeFormat, time, SqlDml.Concat(seconds, ".", milliseconds, " ", "SECONDS")); private static SqlExpression TimeAddSeconds(SqlExpression time, SqlExpression seconds) => - SqlDml.FunctionCall("STRFTIME", TimeFormat, time, SqlDml.Concat(seconds, " ", "SECONDS")); + SqlDml.FunctionCall(STRFTIMEFunctionName, TimeFormat, time, SqlDml.Concat(seconds, " ", "SECONDS")); private static SqlExpression TimeAddInterval(SqlExpression time, SqlExpression interval) => TimeAddSeconds(time, interval / Convert.ToDouble(NanosecondsPerSecond)); @@ -561,8 +570,8 @@ private static SqlExpression TimeSubtractTime(SqlExpression time1, SqlExpression var minutesInSecs1 = SqlDml.Extract(SqlTimePart.Minute, time1) * 60; var minutesInSecs2 = SqlDml.Extract(SqlTimePart.Minute, time2) * 60; - var seconds1 = SqlDml.FunctionCall("STRFTIME", "%f", time1); - var seconds2 = SqlDml.FunctionCall("STRFTIME", "%f", time2); + var seconds1 = SqlDml.FunctionCall(STRFTIMEFunctionName, "%f", time1); + var seconds2 = SqlDml.FunctionCall(STRFTIMEFunctionName, "%f", time2); var difference = ((hoursInSecs1 + minutesInSecs1 + seconds1) * NanosecondsPerSecond) - ((hoursInSecs2 + minutesInSecs2 + seconds2) * NanosecondsPerSecond); @@ -571,41 +580,41 @@ private static SqlExpression TimeSubtractTime(SqlExpression time1, SqlExpression } private static SqlExpression DateToString(SqlExpression dateTime) => - SqlDml.FunctionCall("STRFTIME", DateFormat, dateTime); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateFormat, dateTime); private static SqlExpression TimeToString(SqlExpression dateTime) => - SqlDml.FunctionCall("STRFTIME", TimeToStringFormat, dateTime); + SqlDml.FunctionCall(STRFTIMEFunctionName, TimeToStringFormat, dateTime); private static SqlExpression DateTimeToDate(SqlExpression dateTime) => - SqlDml.FunctionCall("STRFTIME", DateFormat, dateTime); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateFormat, dateTime); private static SqlExpression DateToDateTime(SqlExpression date) => - SqlDml.FunctionCall("STRFTIME", DateTimeFormat, SqlDml.Concat(date, " 00:00:00")); + SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeFormat, SqlDml.Concat(date, " 00:00:00")); private static SqlExpression DateTimeToTime(SqlExpression dateTime) => - SqlDml.FunctionCall("STRFTIME", TimeFormat, dateTime); + SqlDml.FunctionCall(STRFTIMEFunctionName, TimeFormat, dateTime); private static SqlExpression TimeToDateTime(SqlExpression time) => - SqlDml.FunctionCall("STRFTIME", "1900-01-01 " + TimeFormat, time); + SqlDml.FunctionCall(STRFTIMEFunctionName, "1900-01-01 " + TimeFormat, time); private static SqlExpression DateTimeOffsetToTime(SqlExpression dateTimeOffset) => - DateTimeToTime(SqlDml.FunctionCall("STRFTIME", TimeFormat, DateTimeOffsetExtractDateTimeAsString(dateTimeOffset))); + DateTimeToTime(SqlDml.FunctionCall(STRFTIMEFunctionName, TimeFormat, DateTimeOffsetExtractDateTimeAsString(dateTimeOffset))); private static SqlExpression DateTimeOffsetToDate(SqlExpression dateTimeOffset) => - DateTimeToDate(SqlDml.FunctionCall("STRFTIME", DateFormat, DateTimeOffsetExtractDateTimeAsString(dateTimeOffset))); + DateTimeToDate(SqlDml.FunctionCall(STRFTIMEFunctionName, DateFormat, DateTimeOffsetExtractDateTimeAsString(dateTimeOffset))); private static SqlExpression TimeToDateTimeOffset(SqlExpression time) => - SqlDml.Concat(SqlDml.FunctionCall("STRFTIME", DateTimeFormat, SqlDml.Concat("1900-01-01 ", time)), ServerOffsetAsString()); + SqlDml.Concat(SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeFormat, SqlDml.Concat("1900-01-01 ", time)), ServerOffsetAsString()); private static SqlExpression DateToDateTimeOffset(SqlExpression date) => - SqlDml.Concat(SqlDml.FunctionCall("STRFTIME", DateTimeFormat, SqlDml.Concat(date, " 00:00:00")), ServerOffsetAsString()); + SqlDml.Concat(SqlDml.FunctionCall(STRFTIMEFunctionName, DateTimeFormat, SqlDml.Concat(date, " 00:00:00")), ServerOffsetAsString()); #endif private static SqlExpression DateOrTimeGetMilliseconds(SqlExpression date) => - CastToLong(SqlDml.FunctionCall("STRFTIME", "%f", date) * MillisecondsPerSecond) - - CastToLong(SqlDml.FunctionCall("STRFTIME", "%S", date) * MillisecondsPerSecond); + CastToLong(SqlDml.FunctionCall(STRFTIMEFunctionName, "%f", date) * MillisecondsPerSecond) - + CastToLong(SqlDml.FunctionCall(STRFTIMEFunctionName, "%S", date) * MillisecondsPerSecond); private static SqlExpression DateOrTimeGetTotalSeconds(SqlExpression date) => - SqlDml.FunctionCall("STRFTIME", "%s", date); + SqlDml.FunctionCall(STRFTIMEFunctionName, "%s", date); private static SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlExpression date2) => (((DateOrTimeGetTotalSeconds(date1) - DateOrTimeGetTotalSeconds(date2)) * MillisecondsPerSecond) @@ -614,7 +623,7 @@ private static SqlExpression DateTimeSubtractDateTime(SqlExpression date1, SqlEx private static SqlExpression ServerOffsetAsString() { const string constDateTime = "2016-01-01 12:00:00"; - return OffsetToOffsetAsString((SqlDml.FunctionCall("STRFTIME", "%s", constDateTime) - SqlDml.FunctionCall("STRFTIME", "%s", constDateTime, "UTC")) / 60); + return OffsetToOffsetAsString((SqlDml.FunctionCall(STRFTIMEFunctionName, "%s", constDateTime) - SqlDml.FunctionCall(STRFTIMEFunctionName, "%s", constDateTime, "UTC")) / 60); } private static SqlDateTimePart ConvertDateTimeOffsetPartToDateTimePart(SqlDateTimeOffsetPart dateTimeOffsetPart) diff --git a/Orm/Xtensive.Orm.Tests.Sql/DateTimeIntervalTest.cs b/Orm/Xtensive.Orm.Tests.Sql/DateTimeIntervalTest.cs index 5b6ed42d5b..3f15647c83 100644 --- a/Orm/Xtensive.Orm.Tests.Sql/DateTimeIntervalTest.cs +++ b/Orm/Xtensive.Orm.Tests.Sql/DateTimeIntervalTest.cs @@ -254,13 +254,34 @@ public virtual void TimeOnlyExtractMillisecondTest() } [Test] - public virtual void TimeOnlyConstructTest() + public virtual void TimeOnlyConstructTest1() { + Require.ProviderIsNot(StorageProvider.Sqlite | StorageProvider.MySql); + CheckEquality( SqlDml.TimeConstruct(DefaultTimeOnly.Hour, DefaultTimeOnly.Minute, DefaultTimeOnly.Second, DefaultTimeOnly.Millisecond), DefaultTimeOnly); } + [Test] + public virtual void TimeOnlyConstructTest2() + { + Require.ProviderIsNot(StorageProvider.Sqlite | StorageProvider.MySql); + + var ticksPerHour = new TimeOnly(1, 0).Ticks; + var ticksPerMinute = new TimeOnly(0, 1).Ticks; + var ticksPerSecond = new TimeOnly(0, 0, 1).Ticks; + var ticksPerMillisecond = new TimeOnly(0, 0, 0, 1).Ticks; + var testTicks = ticksPerHour * DefaultTimeOnly.Hour + + ticksPerMinute * DefaultTimeOnly.Minute + + ticksPerSecond * DefaultTimeOnly.Second + + ticksPerMillisecond * DefaultTimeOnly.Millisecond; + + CheckEquality( + SqlDml.TimeConstruct(testTicks), + DefaultTimeOnly); + } + [Test] public virtual void TimeOnlySubtractTimeOnlyTest() { diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs index 40cea806fd..365a04277e 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/Model.cs @@ -5,8 +5,6 @@ // Created: 2016.08.01 using System; -using System.Net.Sockets; -using Org.BouncyCastle.Crypto.Digests; namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.Model { @@ -194,7 +192,12 @@ public class AllPossiblePartsEntity : Entity public int Microsecond { get; set; } [Field] - public long Ticks { get; set; } + public long DateTimeTicks { get; set; } +#if NET6_0_OR_GREATER + + [Field] + public long TimeOnlyTicks { get; set; } +#endif [Field] [Validation.RangeConstraint(Min = -23, Max = 23)] @@ -204,6 +207,9 @@ public class AllPossiblePartsEntity : Entity [Validation.RangeConstraint(Min = 0, Max = 59)] public int OffsetMinute { get; set; } + [Field] + public TimeSpan TimeSpan { get; set; } + public static AllPossiblePartsEntity FromDateTime(Session session, DateTime dateTime, int microsecond) { return new AllPossiblePartsEntity(session) { @@ -217,7 +223,11 @@ public static AllPossiblePartsEntity FromDateTime(Session session, DateTime date Microsecond = microsecond, OffsetHour = 0, OffsetMinute = 0, - Ticks = dateTime.Ticks + DateTimeTicks = dateTime.Ticks, +#if NET6_0_OR_GREATER + TimeOnlyTicks = TimeOnly.FromDateTime(dateTime).Ticks, + TimeSpan = TimeOnly.FromDateTime(dateTime).ToTimeSpan(), +#endif }; } @@ -234,7 +244,11 @@ public static AllPossiblePartsEntity FromDateTimeOffset(Session session, DateTim Microsecond = microsecond, OffsetHour = dateTimeOffset.Offset.Hours, OffsetMinute = dateTimeOffset.Offset.Minutes, - Ticks = dateTimeOffset.Ticks + DateTimeTicks = dateTimeOffset.Ticks, +#if NET6_0_OR_GREATER + TimeOnlyTicks = TimeOnly.FromDateTime(dateTimeOffset.DateTime).Ticks, + TimeSpan = TimeOnly.FromDateTime(dateTimeOffset.DateTime).ToTimeSpan(), +#endif }; } @@ -288,7 +302,7 @@ public class TimeOnlyEntity : Entity public TimeOnly? NullableTimeOnly { get; set; } public TimeOnlyEntity(Session session) - :base(session) + : base(session) { } } diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/ConstructorTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/ConstructorTest.cs index 3c9f91931c..b9c88e9196 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/ConstructorTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/ConstructorTest.cs @@ -14,6 +14,8 @@ namespace Xtensive.Orm.Tests.Linq.DateTimeAndDateTimeOffset.TimeOnlys { public class ConstructorTest : DateTimeBaseTest { + protected override void CheckRequirements() => Require.ProviderIsNot(StorageProvider.MySql | StorageProvider.Sqlite); + [Test] public void CtorHMSM() { @@ -22,7 +24,8 @@ public void CtorHMSM() .Select(e => new { Entity = e, ConstructedTime = new TimeOnly(e.Hour, e.Minute, e.Second, e.Millisecond) }) - .Where(a => a.ConstructedTime == FirstMillisecondTimeOnly).OrderBy(a => a.Entity.Id).ToList(3); + .Where(a => a.ConstructedTime == FirstMillisecondTimeOnly) + .OrderBy(a => a.Entity.Id).ToList(3); Assert.That(result.Count, Is.EqualTo(1)); }); } @@ -36,7 +39,8 @@ public void CtorHMS() Entity = e, ConstructedTime = new TimeOnly(e.Hour, e.Minute, e.Second) }) - .Where(a => a.ConstructedTime == FirstTimeOnly).OrderBy(a => a.Entity.Id).ToList(3); + .Where(a => a.ConstructedTime == FirstTimeOnly) + .OrderBy(a => a.Entity.Id).ToList(3); Assert.That(result.Count, Is.EqualTo(1)); }); } @@ -52,6 +56,146 @@ public void CtorHM() Assert.That(result.Count, Is.EqualTo(1)); }); } + + [Test] + public void CtorTicksLiteralValue() + { + var ticksPerHour = new TimeOnly(1, 0).Ticks; + var ticksPerMinute = new TimeOnly(0, 1).Ticks; + var ticksPerSecond = new TimeOnly(0, 0, 1).Ticks; + var testTicks = ticksPerHour * FirstTimeOnly.Hour + + ticksPerMinute * FirstTimeOnly.Minute + + ticksPerSecond * FirstTimeOnly.Second; + + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { Entity = e, ConstructedTime = new TimeOnly(testTicks) }) + .Where(a => a.ConstructedTime == FirstTimeOnly) + .OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + + [Test] + public void CtorTicksLiteralExpressions() + { + var ticksPerHour = new TimeOnly(1, 0).Ticks; + var ticksPerMinute = new TimeOnly(0, 1).Ticks; + var ticksPerSecond = new TimeOnly(0, 0, 1).Ticks; + var testTicks = ticksPerHour * FirstTimeOnly.Hour + + ticksPerMinute * FirstTimeOnly.Minute + + ticksPerSecond * FirstTimeOnly.Second; + + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { Entity = e, ConstructedTime = new TimeOnly(testTicks + 1000 - 1000) }) + .Where(a => a.ConstructedTime == FirstTimeOnly) + .OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + + [Test] + public void CtorTicksFromIntervalTicks() + { + Require.ProviderIsNot(StorageProvider.MySql); + + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { Entity = e, ConstructedTime = new TimeOnly(e.TimeSpan.Ticks) }) + .Where(a => a.ConstructedTime == FirstMillisecondTimeOnly) + .OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + + [Test] + public void TicksFromColumnsBasedExpression() + { + var ticksPerHour = new TimeOnly(1, 0).Ticks; + var ticksPerMinute = new TimeOnly(0, 1).Ticks; + var ticksPerSecond = new TimeOnly(0, 0, 1).Ticks; + var testTicks = ticksPerHour * FirstTimeOnly.Hour + + ticksPerMinute * FirstTimeOnly.Minute + + ticksPerSecond * FirstTimeOnly.Second; + + Assert.That(new TimeOnly(testTicks).Ticks, Is.EqualTo(testTicks)); + Assert.That(new TimeOnly(testTicks), Is.EqualTo(FirstTimeOnly)); + + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { + Entity = e, + ConstructedTime = new TimeOnly(e.Hour * ticksPerHour + e.Minute * ticksPerMinute + e.Second * ticksPerSecond) + }) + .Where(a => a.ConstructedTime == FirstTimeOnly) + .OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + + [Test] + public void TicksFromColumnsBasedExpressionMilliseconds() + { + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.MySql)) { + Require.ProviderVersionAtLeast(StorageProviderVersion.MySql56); + } + + var ticksPerHour = new TimeOnly(1, 0).Ticks; + var ticksPerMinute = new TimeOnly(0, 1).Ticks; + var ticksPerSecond = new TimeOnly(0, 0, 1).Ticks; + var ticksPerMillisecond = new TimeOnly(0,0,0,1).Ticks; + var testTicks = ticksPerHour * FirstMillisecondTimeOnly.Hour + + ticksPerMinute * FirstMillisecondTimeOnly.Minute + + ticksPerSecond * FirstMillisecondTimeOnly.Second + + ticksPerMillisecond * FirstMillisecondTimeOnly.Millisecond; + + Assert.That(new TimeOnly(testTicks).Ticks, Is.EqualTo(testTicks)); + Assert.That(new TimeOnly(testTicks), Is.EqualTo(FirstMillisecondTimeOnly)); + + ExecuteInsideSession((s) => { + var result = s.Query.All() + .Select(e => new { + Entity = e, + ConstructedTime = new TimeOnly( + e.Hour * ticksPerHour + e.Minute * ticksPerMinute + e.Second * ticksPerSecond + e.Millisecond * ticksPerMillisecond) + }) + .Where(a => a.ConstructedTime == FirstMillisecondTimeOnly) + .OrderBy(a => a.Entity.Id).ToList(3); + Assert.That(result.Count, Is.EqualTo(1)); + }); + } + + [Test] + public void HourOverflowTest() + { + Require.ProviderIsNot(StorageProvider.MySql | StorageProvider.Sqlite, + "These providers don't throw exceptions on hour value overflow but return NULL or Max possible value, so no support for time constructor"); + + var ticksPerHour = new TimeOnly(1, 0).Ticks; + var testTicks = FirstMillisecondTimeOnly.Hour + ticksPerHour * 25; + + _ = Assert.Throws(() => new TimeOnly(testTicks)); + + ExecuteInsideSession((s) => { + _ = Assert.Throws(GetExceptionType(), + () => s.Query.All() + .Select(e => new { + Entity = e, + ConstructedTime = new TimeOnly(e.Hour + ticksPerHour * 25) + }) + .Where(a => a.ConstructedTime == FirstMillisecondTimeOnly) + .OrderBy(a => a.Entity.Id).Run()); + }); + + static Type GetExceptionType() + { + return StorageProviderInfo.Instance.Provider switch { + StorageProvider.SqlServer => typeof(SyntaxErrorException), + _ => typeof(StorageException) + }; + } + } } } #endif \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/PartsExtractionTest.cs b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/PartsExtractionTest.cs index 082bc272d5..75d2740dec 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/PartsExtractionTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/DateTimeAndDateTimeOffset/TimeOnly/PartsExtractionTest.cs @@ -77,14 +77,12 @@ public void MysqlExtractMillisecondTest() } [Test] - [Ignore("Not implemented yet.")] public void ExtractTicksTest() { - Require.ProviderIsNot(StorageProvider.PostgreSql | StorageProvider.Oracle); - ExecuteInsideSession((s) => { - RunTest(s, c => c.TimeOnly.Ticks == FirstTimeOnly.Ticks); - RunWrongTest(s, c => c.TimeOnly.Ticks < FirstTimeOnly.Ticks); + var firstMillisecondTimeOnly = FirstMillisecondTimeOnly.AdjustTimeOnlyForCurrentProvider(); + RunTest(s, c => c.MillisecondTimeOnly.Ticks == firstMillisecondTimeOnly.Ticks); + RunWrongTest(s, c => c.MillisecondTimeOnly.Ticks < FirstTimeOnly.Ticks); }); } } diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeOnlyCompilers.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeOnlyCompilers.cs index 50f38321fe..e79ef4a10b 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeOnlyCompilers.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeOnlyCompilers.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Xtensive LLC. +// Copyright (C) 2022-2023 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. @@ -35,6 +35,10 @@ public static SqlExpression TimeOnlySecond(SqlExpression _this) => public static SqlExpression TimeOnlyMillisecond(SqlExpression _this) => ExpressionTranslationHelpers.ToInt(SqlDml.Extract(SqlTimePart.Millisecond, _this)); + [Compiler(typeof(TimeOnly), "Ticks", TargetKind.PropertyGet)] + public static SqlExpression TimeSpanTicks(SqlExpression _this) => + ExpressionTranslationHelpers.ToLong(SqlDml.TimeToNanoseconds(_this) / NanosecondsPerTick); + #endregion #region Constructors @@ -59,9 +63,9 @@ public static SqlExpression TimeOnlyCtor( [Type(typeof(int))] SqlExpression minute) => SqlDml.TimeConstruct(hour, minute, 0, 0); - //[Compiler(typeof(TimeOnly), null, TargetKind.Constructor)] - //public static SqlExpression TimeOnlyCtor([Type(typeof(long))] SqlExpression ticks) => - // new SqlFunctionCall(SqlFunctionType.TimeConstruct, ticks); + [Compiler(typeof(TimeOnly), null, TargetKind.Constructor)] + public static SqlExpression TimeOnlyCtor([Type(typeof(long))] SqlExpression ticks) => + SqlDml.TimeConstruct(ticks); #endregion diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeSpanCompilers.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeSpanCompilers.cs index 935c475289..f98d916419 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeSpanCompilers.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/TimeSpanCompilers.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2021 Xtensive LLC. +// Copyright (C) 2009-2023 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov @@ -163,6 +163,7 @@ public static SqlExpression TimeSpanDays(SqlExpression _this) [Compiler(typeof(TimeSpan), "Ticks", TargetKind.PropertyGet)] public static SqlExpression TimeSpanTicks(SqlExpression _this) { + // there are some operations that rely on this structure. return ExpressionTranslationHelpers.ToLong(SqlDml.IntervalToNanoseconds(_this) / NanosecondsPerTick); } diff --git a/Orm/Xtensive.Orm/Sql/Dml/SqlFunctionType.cs b/Orm/Xtensive.Orm/Sql/Dml/SqlFunctionType.cs index 05a696c0d0..0e0518d3ac 100644 --- a/Orm/Xtensive.Orm/Sql/Dml/SqlFunctionType.cs +++ b/Orm/Xtensive.Orm/Sql/Dml/SqlFunctionType.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2007-2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. using System; @@ -74,6 +74,7 @@ public enum SqlFunctionType TimeToString, TimeToDateTime, TimeToDateTimeOffset, + TimeToNanoseconds, #endif DateTimeConstruct, diff --git a/Orm/Xtensive.Orm/Sql/SqlDml.cs b/Orm/Xtensive.Orm/Sql/SqlDml.cs index edd801eca3..ba19eb69d5 100644 --- a/Orm/Xtensive.Orm/Sql/SqlDml.cs +++ b/Orm/Xtensive.Orm/Sql/SqlDml.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2007-2023 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. using System; using System.Collections.Generic; @@ -614,10 +614,16 @@ public static SqlFunctionCall TimeConstruct(SqlExpression hour, SqlValidator.EnsureIsArithmeticExpression(second); SqlValidator.EnsureIsArithmeticExpression(millisecond); - //var m = milliseconds + 1000L * (seconds + 60L * (minutes + 60L * hours)); - //var ticks = 10_000 * m; return new SqlFunctionCall(SqlFunctionType.TimeConstruct, hour, minute, second, millisecond); } + + public static SqlFunctionCall TimeConstruct(SqlExpression ticks) + { + ArgumentNullException.ThrowIfNull(ticks); + SqlValidator.EnsureIsArithmeticExpression(ticks); + + return new SqlFunctionCall(SqlFunctionType.TimeConstruct, ticks); + } #endif public static SqlBinary DateTimePlusInterval(SqlExpression left, SqlExpression right) @@ -630,15 +636,15 @@ public static SqlBinary DateTimePlusInterval(SqlExpression left, SqlExpression r public static SqlBinary TimePlusInterval(SqlExpression left, SqlExpression right) { - ArgumentValidator.EnsureArgumentNotNull(left, "left"); - ArgumentValidator.EnsureArgumentNotNull(right, "right"); + ArgumentNullException.ThrowIfNull(left, nameof(left)); + ArgumentNullException.ThrowIfNull(right, nameof(right)); return new SqlBinary(SqlNodeType.TimePlusInterval, left, right); } public static SqlBinary TimeMinusTime(SqlExpression left, SqlExpression right) { - ArgumentValidator.EnsureArgumentNotNull(left, "left"); - ArgumentValidator.EnsureArgumentNotNull(right, "right"); + ArgumentNullException.ThrowIfNull(left, nameof(left)); + ArgumentNullException.ThrowIfNull(right, nameof(right)); return new SqlBinary(SqlNodeType.TimeMinusTime, left, right); } #endif @@ -674,84 +680,90 @@ public static SqlFunctionCall DateTimeAddMonths(SqlExpression source, SqlExpress #if NET6_0_OR_GREATER public static SqlFunctionCall DateTimeToTime(SqlExpression expression) { - ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + ArgumentNullException.ThrowIfNull(expression, nameof(expression)); return new SqlFunctionCall(SqlFunctionType.DateTimeToTime, expression); } public static SqlFunctionCall DateAddYears(SqlExpression source, SqlExpression years) { - ArgumentValidator.EnsureArgumentNotNull(source, "source"); - ArgumentValidator.EnsureArgumentNotNull(years, "years"); + ArgumentNullException.ThrowIfNull(source, nameof(source)); + ArgumentNullException.ThrowIfNull(years, nameof(years)); return new SqlFunctionCall(SqlFunctionType.DateAddYears, source, years); } public static SqlFunctionCall DateAddMonths(SqlExpression source, SqlExpression months) { - ArgumentValidator.EnsureArgumentNotNull(source, "source"); - ArgumentValidator.EnsureArgumentNotNull(months, "months"); + ArgumentNullException.ThrowIfNull(source, nameof(source)); + ArgumentNullException.ThrowIfNull(months, nameof(months)); return new SqlFunctionCall(SqlFunctionType.DateAddMonths, source, months); } public static SqlFunctionCall DateAddDays(SqlExpression source, SqlExpression days) { - ArgumentValidator.EnsureArgumentNotNull(source, "source"); - ArgumentValidator.EnsureArgumentNotNull(days, "days"); + ArgumentNullException.ThrowIfNull(source, nameof(source)); + ArgumentNullException.ThrowIfNull(days, nameof(days)); return new SqlFunctionCall(SqlFunctionType.DateAddDays, source, days); } public static SqlFunctionCall DateToString(SqlExpression expression) { - ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + ArgumentNullException.ThrowIfNull(expression, nameof(expression)); return new SqlFunctionCall(SqlFunctionType.DateToString, expression); } public static SqlFunctionCall DateToDateTime(SqlExpression expression) { - ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + ArgumentNullException.ThrowIfNull(expression, nameof(expression)); return new SqlFunctionCall(SqlFunctionType.DateToDateTime, expression); } public static SqlFunctionCall DateTimeToDate(SqlExpression expression) { - ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + ArgumentNullException.ThrowIfNull(expression, nameof(expression)); return new SqlFunctionCall(SqlFunctionType.DateTimeToDate, expression); } public static SqlFunctionCall DateToDateTimeOffset(SqlExpression expression) { - ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + ArgumentNullException.ThrowIfNull(expression, nameof(expression)); return new SqlFunctionCall(SqlFunctionType.DateToDateTimeOffset, expression); } public static SqlFunctionCall TimeAddHours(SqlExpression source, SqlExpression hours) { - ArgumentValidator.EnsureArgumentNotNull(source, "source"); - ArgumentValidator.EnsureArgumentNotNull(hours, "hours"); + ArgumentNullException.ThrowIfNull(source, nameof(source)); + ArgumentNullException.ThrowIfNull(hours, nameof(hours)); return new SqlFunctionCall(SqlFunctionType.TimeAddHours, source, hours); } public static SqlFunctionCall TimeAddMinutes(SqlExpression source, SqlExpression minutes) { - ArgumentValidator.EnsureArgumentNotNull(source, "source"); - ArgumentValidator.EnsureArgumentNotNull(minutes, "minutes"); + ArgumentNullException.ThrowIfNull(source, nameof(source)); + ArgumentNullException.ThrowIfNull(minutes, nameof(minutes)); return new SqlFunctionCall(SqlFunctionType.TimeAddMinutes, source, minutes); } public static SqlFunctionCall TimeToString(SqlExpression expression) { - ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + ArgumentNullException.ThrowIfNull(expression, nameof(expression)); return new SqlFunctionCall(SqlFunctionType.TimeToString, expression); } public static SqlFunctionCall TimeToDateTime(SqlExpression expression) { - ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + ArgumentNullException.ThrowIfNull(expression, nameof(expression)); return new SqlFunctionCall(SqlFunctionType.TimeToDateTime, expression); } + public static SqlExpression TimeToNanoseconds(SqlExpression source) + { + ArgumentNullException.ThrowIfNull(source, nameof(source)); + return new SqlFunctionCall(SqlFunctionType.TimeToNanoseconds, source); + } + public static SqlFunctionCall TimeToDateTimeOffset(SqlExpression expression) { - ArgumentValidator.EnsureArgumentNotNull(expression, "expression"); + ArgumentNullException.ThrowIfNull(expression, nameof(expression)); return new SqlFunctionCall(SqlFunctionType.TimeToDateTimeOffset, expression); } #endif @@ -777,13 +789,13 @@ public static SqlFunctionCall IntervalConstruct(SqlExpression nanoseconds) public static SqlFunctionCall IntervalToMilliseconds(SqlExpression source) { ArgumentValidator.EnsureArgumentNotNull(source, "source"); - return new SqlFunctionCall(SqlFunctionType.IntervalToMilliseconds, source); + return new SqlFunctionCall(SqlFunctionType.IntervalToMilliseconds, source); } public static SqlFunctionCall IntervalToNanoseconds(SqlExpression source) { ArgumentValidator.EnsureArgumentNotNull(source, "source"); - return new SqlFunctionCall(SqlFunctionType.IntervalToNanoseconds, source); + return new SqlFunctionCall(SqlFunctionType.IntervalToNanoseconds, source); } public static SqlFunctionCall IntervalAbs(SqlExpression source) @@ -892,13 +904,14 @@ public static SqlFunctionCall DateTimeOffsetToDateTime(SqlExpression dateTimeOff public static SqlFunctionCall DateTimeOffsetToTime(SqlExpression dateTimeOffset) { - ArgumentValidator.EnsureArgumentNotNull(dateTimeOffset, nameof(dateTimeOffset)); + ArgumentNullException.ThrowIfNull(dateTimeOffset, nameof(dateTimeOffset)); return new SqlFunctionCall(SqlFunctionType.DateTimeOffsetToTime, dateTimeOffset); } public static SqlFunctionCall DateTimeOffsetToDate(SqlExpression dateTimeOffset) { ArgumentValidator.EnsureArgumentNotNull(dateTimeOffset, nameof(dateTimeOffset)); + ArgumentNullException.ThrowIfNull(dateTimeOffset, nameof(dateTimeOffset)); return new SqlFunctionCall(SqlFunctionType.DateTimeOffsetToDate, dateTimeOffset); } #endif @@ -1128,6 +1141,18 @@ public static SqlLiteral Literal(DateTime value) { return new SqlLiteral(value); } +#if NET6_0_OR_GREATER + + public static SqlLiteral Literal(DateOnly value) + { + return new SqlLiteral(value); + } + + public static SqlLiteral Literal(TimeOnly value) + { + return new SqlLiteral(value); + } +#endif public static SqlLiteral Literal(TimeSpan value) { diff --git a/Orm/Xtensive.Orm/Sql/SqlHelper.cs b/Orm/Xtensive.Orm/Sql/SqlHelper.cs index 8316e25290..c9e4a8151c 100644 --- a/Orm/Xtensive.Orm/Sql/SqlHelper.cs +++ b/Orm/Xtensive.Orm/Sql/SqlHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2021 Xtensive LLC. +// Copyright (C) 2009-2023 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov @@ -171,6 +171,31 @@ public static SqlExpression IntervalToNanoseconds(SqlExpression interval) return IntervalToMilliseconds(interval) * 1000000L + nanoseconds; } + /// + /// Checks if is indeed a representation of TimeSpan.Ticks + /// created by + /// + /// Expression to check + /// Source interval expression + /// + public static bool IsTimeSpanTicks(SqlExpression expressionToCheck, out SqlExpression sourceInterval) + { + sourceInterval = null; + + if (expressionToCheck is SqlCast sqlCast + && (sqlCast.Type.Type == SqlType.Int64 || sqlCast.Type.Type == SqlType.Decimal)) { + var operand = sqlCast.Operand; + if (operand is SqlBinary sqlBinary && sqlBinary.NodeType == SqlNodeType.Divide) { + var left = sqlBinary.Left; + if (left is SqlFunctionCall functionCall && functionCall.FunctionType == SqlFunctionType.IntervalToNanoseconds) { + sourceInterval = functionCall.Arguments[0]; + return true; + } + } + } + return false; + } + /// /// Converts the specified interval expression to expression /// that represents absolute value (duration) of the specified interval.